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:
Nick Carenza 2017-02-20 18:01:27 -08:00 committed by ijokarumawak
parent 4f49095d75
commit f9c4d3ce3a
2 changed files with 99 additions and 8 deletions

View File

@ -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;

View File

@ -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 {