mirror of https://github.com/apache/nifi.git
NIFI-3508: Added support for PATCH identical to that of PUT/POST
This closes #1522. Signed-off-by: ijokarumawak <ijokarumawak@apache.org>
This commit is contained in:
parent
4f49095d75
commit
f9c4d3ce3a
|
@ -95,7 +95,7 @@ import org.joda.time.format.DateTimeFormatter;
|
||||||
@Tags({"http", "https", "rest", "client"})
|
@Tags({"http", "https", "rest", "client"})
|
||||||
@InputRequirement(Requirement.INPUT_ALLOWED)
|
@InputRequirement(Requirement.INPUT_ALLOWED)
|
||||||
@CapabilityDescription("An HTTP client processor which can interact with a configurable HTTP Endpoint. The destination URL and HTTP Method are configurable."
|
@CapabilityDescription("An HTTP client processor which can interact with a configurable HTTP Endpoint. The destination URL and HTTP Method are configurable."
|
||||||
+ " FlowFile attributes are converted to HTTP headers and the FlowFile contents are included as the body of the request (if the HTTP Method is PUT or POST).")
|
+ " FlowFile attributes are converted to HTTP headers and the FlowFile contents are included as the body of the request (if the HTTP Method is PUT, POST or PATCH).")
|
||||||
@WritesAttributes({
|
@WritesAttributes({
|
||||||
@WritesAttribute(attribute = "invokehttp.status.code", description = "The status code that is returned"),
|
@WritesAttribute(attribute = "invokehttp.status.code", description = "The status code that is returned"),
|
||||||
@WritesAttribute(attribute = "invokehttp.status.message", description = "The status message that is returned"),
|
@WritesAttribute(attribute = "invokehttp.status.message", description = "The status message that is returned"),
|
||||||
|
@ -138,8 +138,8 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
// properties
|
// properties
|
||||||
public static final PropertyDescriptor PROP_METHOD = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor PROP_METHOD = new PropertyDescriptor.Builder()
|
||||||
.name("HTTP Method")
|
.name("HTTP Method")
|
||||||
.description("HTTP request method (GET, POST, PUT, DELETE, HEAD, OPTIONS). Arbitrary methods are also supported. "
|
.description("HTTP request method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Arbitrary methods are also supported. "
|
||||||
+ "Methods other than POST and PUT will be sent without a message body.")
|
+ "Methods other than POST, PUT and PATCH will be sent without a message body.")
|
||||||
.required(true)
|
.required(true)
|
||||||
.defaultValue("GET")
|
.defaultValue("GET")
|
||||||
.expressionLanguageSupported(true)
|
.expressionLanguageSupported(true)
|
||||||
|
@ -238,7 +238,7 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
|
|
||||||
public static final PropertyDescriptor PROP_CONTENT_TYPE = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor PROP_CONTENT_TYPE = new PropertyDescriptor.Builder()
|
||||||
.name("Content-Type")
|
.name("Content-Type")
|
||||||
.description("The Content-Type to specify for when content is being transmitted through a PUT or POST. "
|
.description("The Content-Type to specify for when content is being transmitted through a PUT, POST or PATCH. "
|
||||||
+ "In the case of an empty value after evaluating an expression language expression, Content-Type defaults to " + DEFAULT_CONTENT_TYPE)
|
+ "In the case of an empty value after evaluating an expression language expression, Content-Type defaults to " + DEFAULT_CONTENT_TYPE)
|
||||||
.required(true)
|
.required(true)
|
||||||
.expressionLanguageSupported(true)
|
.expressionLanguageSupported(true)
|
||||||
|
@ -249,7 +249,7 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
public static final PropertyDescriptor PROP_SEND_BODY = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor PROP_SEND_BODY = new PropertyDescriptor.Builder()
|
||||||
.name("send-message-body")
|
.name("send-message-body")
|
||||||
.displayName("Send Message Body")
|
.displayName("Send Message Body")
|
||||||
.description("If true, sends the HTTP message body on POST/PUT requests (default). If false, suppresses the message body and content-type header for these requests.")
|
.description("If true, sends the HTTP message body on POST/PUT/PATCH requests (default). If false, suppresses the message body and content-type header for these requests.")
|
||||||
.defaultValue("true")
|
.defaultValue("true")
|
||||||
.allowableValues("true", "false")
|
.allowableValues("true", "false")
|
||||||
.required(false)
|
.required(false)
|
||||||
|
@ -343,7 +343,7 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
|
|
||||||
public static final PropertyDescriptor PROP_USE_CHUNKED_ENCODING = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor PROP_USE_CHUNKED_ENCODING = new PropertyDescriptor.Builder()
|
||||||
.name("Use Chunked Encoding")
|
.name("Use Chunked Encoding")
|
||||||
.description("When POST'ing or PUT'ing content set this property to true in order to not pass the 'Content-length' header and instead send 'Transfer-Encoding' with "
|
.description("When POST'ing, PUT'ing or PATCH'ing content set this property to true in order to not pass the 'Content-length' header and instead send 'Transfer-Encoding' with "
|
||||||
+ "a value of 'chunked'. This will enable the data transfer mechanism which was introduced in HTTP 1.1 to pass data of unknown lengths in chunks.")
|
+ "a value of 'chunked'. This will enable the data transfer mechanism which was introduced in HTTP 1.1 to pass data of unknown lengths in chunks.")
|
||||||
.required(true)
|
.required(true)
|
||||||
.defaultValue("false")
|
.defaultValue("false")
|
||||||
|
@ -596,7 +596,7 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
String request = context.getProperty(PROP_METHOD).evaluateAttributeExpressions().getValue().toUpperCase();
|
String request = context.getProperty(PROP_METHOD).evaluateAttributeExpressions().getValue().toUpperCase();
|
||||||
if ("POST".equals(request) || "PUT".equals(request)) {
|
if ("POST".equals(request) || "PUT".equals(request) || "PATCH".equals(request)) {
|
||||||
return;
|
return;
|
||||||
} else if (putToAttribute) {
|
} else if (putToAttribute) {
|
||||||
requestFlowFile = session.create();
|
requestFlowFile = session.create();
|
||||||
|
@ -809,6 +809,10 @@ public final class InvokeHTTP extends AbstractProcessor {
|
||||||
requestBody = getRequestBodyToSend(session, context, requestFlowFile);
|
requestBody = getRequestBodyToSend(session, context, requestFlowFile);
|
||||||
requestBuilder = requestBuilder.put(requestBody);
|
requestBuilder = requestBuilder.put(requestBody);
|
||||||
break;
|
break;
|
||||||
|
case "PATCH":
|
||||||
|
requestBody = getRequestBodyToSend(session, context, requestFlowFile);
|
||||||
|
requestBuilder = requestBuilder.patch(requestBody);
|
||||||
|
break;
|
||||||
case "HEAD":
|
case "HEAD":
|
||||||
requestBuilder = requestBuilder.head();
|
requestBuilder = requestBuilder.head();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1114,6 +1114,93 @@ public abstract class TestInvokeHttpCommon {
|
||||||
Assert.assertEquals(expected1, actual1);
|
Assert.assertEquals(expected1, actual1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatch() throws Exception {
|
||||||
|
addHandler(new MutativeMethodHandler(MutativeMethod.PATCH));
|
||||||
|
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_METHOD, "PATCH");
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_URL, url + "/patch");
|
||||||
|
|
||||||
|
createFlowFiles(runner);
|
||||||
|
|
||||||
|
runner.run();
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_SUCCESS_REQ, 1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_RESPONSE, 1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_RETRY, 0);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_NO_RETRY, 0);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_FAILURE, 0);
|
||||||
|
runner.assertPenalizeCount(0);
|
||||||
|
|
||||||
|
final MockFlowFile bundle = runner.getFlowFilesForRelationship(InvokeHTTP.REL_SUCCESS_REQ).get(0);
|
||||||
|
bundle.assertContentEquals("Hello".getBytes("UTF-8"));
|
||||||
|
bundle.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
|
||||||
|
bundle.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
|
||||||
|
bundle.assertAttributeEquals("Foo", "Bar");
|
||||||
|
|
||||||
|
final MockFlowFile bundle1 = runner.getFlowFilesForRelationship(InvokeHTTP.REL_RESPONSE).get(0);
|
||||||
|
bundle1.assertContentEquals("".getBytes("UTF-8"));
|
||||||
|
bundle1.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
|
||||||
|
bundle1.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
|
||||||
|
bundle1.assertAttributeEquals("Foo", "Bar");
|
||||||
|
bundle1.assertAttributeNotExists("Content-Type");
|
||||||
|
|
||||||
|
final String actual1 = new String(bundle1.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
final String expected1 = "";
|
||||||
|
Assert.assertEquals(expected1, actual1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchWithMimeType() throws Exception {
|
||||||
|
final String suppliedMimeType = "text/plain";
|
||||||
|
addHandler(new MutativeMethodHandler(MutativeMethod.PATCH, suppliedMimeType));
|
||||||
|
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_METHOD, "PATCH");
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_URL, url + "/patch");
|
||||||
|
|
||||||
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
|
|
||||||
|
attrs.put(CoreAttributes.MIME_TYPE.key(), suppliedMimeType);
|
||||||
|
runner.enqueue("Hello".getBytes(), attrs);
|
||||||
|
|
||||||
|
runner.run(1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_SUCCESS_REQ, 1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_RESPONSE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchWithEmptyELExpression() throws Exception {
|
||||||
|
addHandler(new MutativeMethodHandler(MutativeMethod.PATCH, InvokeHTTP.DEFAULT_CONTENT_TYPE));
|
||||||
|
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_METHOD, "PATCH");
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_URL, url + "/patch");
|
||||||
|
|
||||||
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
|
attrs.put(CoreAttributes.MIME_TYPE.key(), "");
|
||||||
|
runner.enqueue("Hello".getBytes(), attrs);
|
||||||
|
|
||||||
|
runner.run(1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_SUCCESS_REQ, 1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_RESPONSE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatchWithContentTypeProperty() throws Exception {
|
||||||
|
final String suppliedMimeType = "text/plain";
|
||||||
|
addHandler(new MutativeMethodHandler(MutativeMethod.PATCH, suppliedMimeType));
|
||||||
|
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_METHOD, "PATCH");
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_URL, url + "/patch");
|
||||||
|
runner.setProperty(InvokeHTTP.PROP_CONTENT_TYPE, suppliedMimeType);
|
||||||
|
|
||||||
|
final Map<String, String> attrs = new HashMap<>();
|
||||||
|
attrs.put(CoreAttributes.MIME_TYPE.key(), "text/csv");
|
||||||
|
runner.enqueue("Hello".getBytes(), attrs);
|
||||||
|
|
||||||
|
runner.run(1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_SUCCESS_REQ, 1);
|
||||||
|
runner.assertTransferCount(InvokeHTTP.REL_RESPONSE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDelete() throws Exception {
|
public void testDelete() throws Exception {
|
||||||
addHandler(new DeleteHandler());
|
addHandler(new DeleteHandler());
|
||||||
|
@ -1402,7 +1489,7 @@ public abstract class TestInvokeHttpCommon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum MutativeMethod { POST, PUT }
|
private enum MutativeMethod { POST, PUT, PATCH }
|
||||||
|
|
||||||
|
|
||||||
public static class MutativeMethodHandler extends AbstractHandler {
|
public static class MutativeMethodHandler extends AbstractHandler {
|
||||||
|
|
Loading…
Reference in New Issue