diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java index 66f7561683..12991ca2e3 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java @@ -95,7 +95,7 @@ import org.joda.time.format.DateTimeFormatter; @Tags({"http", "https", "rest", "client"}) @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." - + " 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({ @WritesAttribute(attribute = "invokehttp.status.code", description = "The status code 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 public static final PropertyDescriptor PROP_METHOD = new PropertyDescriptor.Builder() .name("HTTP Method") - .description("HTTP request method (GET, POST, PUT, DELETE, HEAD, OPTIONS). Arbitrary methods are also supported. " - + "Methods other than POST and PUT will be sent without a message body.") + .description("HTTP request method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS). Arbitrary methods are also supported. " + + "Methods other than POST, PUT and PATCH will be sent without a message body.") .required(true) .defaultValue("GET") .expressionLanguageSupported(true) @@ -238,7 +238,7 @@ public final class InvokeHTTP extends AbstractProcessor { public static final PropertyDescriptor PROP_CONTENT_TYPE = new PropertyDescriptor.Builder() .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) .required(true) .expressionLanguageSupported(true) @@ -249,7 +249,7 @@ public final class InvokeHTTP extends AbstractProcessor { public static final PropertyDescriptor PROP_SEND_BODY = new PropertyDescriptor.Builder() .name("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") .allowableValues("true", "false") .required(false) @@ -343,7 +343,7 @@ public final class InvokeHTTP extends AbstractProcessor { public static final PropertyDescriptor PROP_USE_CHUNKED_ENCODING = new PropertyDescriptor.Builder() .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.") .required(true) .defaultValue("false") @@ -596,7 +596,7 @@ public final class InvokeHTTP extends AbstractProcessor { } 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; } else if (putToAttribute) { requestFlowFile = session.create(); @@ -809,6 +809,10 @@ public final class InvokeHTTP extends AbstractProcessor { requestBody = getRequestBodyToSend(session, context, requestFlowFile); requestBuilder = requestBuilder.put(requestBody); break; + case "PATCH": + requestBody = getRequestBodyToSend(session, context, requestFlowFile); + requestBuilder = requestBuilder.patch(requestBody); + break; case "HEAD": requestBuilder = requestBuilder.head(); break; diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestInvokeHttpCommon.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestInvokeHttpCommon.java index 5b1e404b4d..5d460fee43 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestInvokeHttpCommon.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TestInvokeHttpCommon.java @@ -1114,6 +1114,93 @@ public abstract class TestInvokeHttpCommon { 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 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 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 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 public void testDelete() throws Exception { 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 {