diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/http-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/http-component.adoc index 094bfccca34a2..2b461b9b1921d 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/http-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/http-component.adoc @@ -46,6 +46,116 @@ include::partial$component-endpoint-headers.adoc[] == Usage +=== Which HTTP method will be used + +IMPORTANT: Without the `httpMethod` option, the message body decides the method: a message with a body is sent as a +`POST`, and a message without a body as a `GET`. After `unmarshal`, `split` or `setBody` the message has a body, so a +call meant as a GET is sent as a POST. Set `httpMethod: GET` on the endpoint when you mean a GET; a GET sends no body +(unless `getWithBody` is true). + +The method is chosen in this order: + +. The `httpMethod` option of the endpoint. +. The `CamelHttpMethod` header. +. `GET` when the `CamelHttpQuery` header, or the `CamelHttpUri` header, has a query string. +. `GET` when the endpoint URI has a query string. +. `POST` when the message has a body. +. `GET` otherwise. + +For example, a stock lookup for each line of an order is a GET, whatever the line in the body is: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:checkStock") + .split(simple("${body[lines]}")) + .toD("http://localhost:8080/stock/${body[sku]}?httpMethod=GET") + .log("Stock answer: ${body}") + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + ${body[lines]} + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:checkStock + steps: + - split: + expression: + simple: + expression: "${body[lines]}" + steps: + - toD: + uri: "http://localhost:8080/stock/${body[sku]}" + parameters: + httpMethod: GET + - log: + message: "Stock answer: ${body}" +---- +==== + +To send the order as JSON, marshal the body first, and say that it is a POST: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:createOrder") + .marshal().json() + .to("http://localhost:8080/orders?httpMethod=POST"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:createOrder + steps: + - marshal: + json: {} + - to: + uri: http://localhost:8080/orders + parameters: + httpMethod: POST +---- +==== + === Message Body Camel will store the HTTP response from the external server on the _OUT_ body. @@ -131,16 +241,6 @@ a `HttpOperationFailedException` is thrown immediately (unless * Response body as a `java.lang.String`, if server provided a body as response -=== Which HTTP method will be used - -The following algorithm is used to determine what HTTP method should be used: + - 1. Use method provided as endpoint configuration (`httpMethod`). + - 2. Use method provided in header (`Exchange.HTTP_METHOD`). + - 3. `GET` if query string is provided in header. + - 4. `GET` if endpoint is configured with a query string. + - 5. `POST` if there is data to send (body is not `null`). + - 6. `GET` otherwise. - === Configuring URI to call You can set the HTTP producer's URI directly from the endpoint URI. In @@ -314,8 +414,9 @@ YAML:: === How to set the http method (GET/PATCH/POST/PUT/DELETE/HEAD/OPTIONS/TRACE) to the HTTP producer -The HTTP component provides a way to set the HTTP request method by -setting the message header. Here is an example: +Set the `httpMethod` option of the endpoint, as in <<_which_http_method_will_be_used,Which HTTP method will be used>>. +The `CamelHttpMethod` header sets the method for each message, when the endpoint has no `httpMethod` option. +Here is an example with the header: [tabs] ==== diff --git a/components/camel-http/src/main/docs/http-component.adoc b/components/camel-http/src/main/docs/http-component.adoc index 094bfccca34a2..2b461b9b1921d 100644 --- a/components/camel-http/src/main/docs/http-component.adoc +++ b/components/camel-http/src/main/docs/http-component.adoc @@ -46,6 +46,116 @@ include::partial$component-endpoint-headers.adoc[] == Usage +=== Which HTTP method will be used + +IMPORTANT: Without the `httpMethod` option, the message body decides the method: a message with a body is sent as a +`POST`, and a message without a body as a `GET`. After `unmarshal`, `split` or `setBody` the message has a body, so a +call meant as a GET is sent as a POST. Set `httpMethod: GET` on the endpoint when you mean a GET; a GET sends no body +(unless `getWithBody` is true). + +The method is chosen in this order: + +. The `httpMethod` option of the endpoint. +. The `CamelHttpMethod` header. +. `GET` when the `CamelHttpQuery` header, or the `CamelHttpUri` header, has a query string. +. `GET` when the endpoint URI has a query string. +. `POST` when the message has a body. +. `GET` otherwise. + +For example, a stock lookup for each line of an order is a GET, whatever the line in the body is: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:checkStock") + .split(simple("${body[lines]}")) + .toD("http://localhost:8080/stock/${body[sku]}?httpMethod=GET") + .log("Stock answer: ${body}") + .end(); +---- + +XML:: ++ +[source,xml] +---- + + + + ${body[lines]} + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:checkStock + steps: + - split: + expression: + simple: + expression: "${body[lines]}" + steps: + - toD: + uri: "http://localhost:8080/stock/${body[sku]}" + parameters: + httpMethod: GET + - log: + message: "Stock answer: ${body}" +---- +==== + +To send the order as JSON, marshal the body first, and say that it is a POST: + +[tabs] +==== +Java:: ++ +[source,java] +---- +from("direct:createOrder") + .marshal().json() + .to("http://localhost:8080/orders?httpMethod=POST"); +---- + +XML:: ++ +[source,xml] +---- + + + + + + + +---- + +YAML:: ++ +[source,yaml] +---- +- route: + from: + uri: direct:createOrder + steps: + - marshal: + json: {} + - to: + uri: http://localhost:8080/orders + parameters: + httpMethod: POST +---- +==== + === Message Body Camel will store the HTTP response from the external server on the _OUT_ body. @@ -131,16 +241,6 @@ a `HttpOperationFailedException` is thrown immediately (unless * Response body as a `java.lang.String`, if server provided a body as response -=== Which HTTP method will be used - -The following algorithm is used to determine what HTTP method should be used: + - 1. Use method provided as endpoint configuration (`httpMethod`). + - 2. Use method provided in header (`Exchange.HTTP_METHOD`). + - 3. `GET` if query string is provided in header. + - 4. `GET` if endpoint is configured with a query string. + - 5. `POST` if there is data to send (body is not `null`). + - 6. `GET` otherwise. - === Configuring URI to call You can set the HTTP producer's URI directly from the endpoint URI. In @@ -314,8 +414,9 @@ YAML:: === How to set the http method (GET/PATCH/POST/PUT/DELETE/HEAD/OPTIONS/TRACE) to the HTTP producer -The HTTP component provides a way to set the HTTP request method by -setting the message header. Here is an example: +Set the `httpMethod` option of the endpoint, as in <<_which_http_method_will_be_used,Which HTTP method will be used>>. +The `CamelHttpMethod` header sets the method for each message, when the endpoint has no `httpMethod` option. +Here is an example with the header: [tabs] ==== diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java index ea868f48218fc..d0a494bc72cb5 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpProducer.java @@ -746,18 +746,27 @@ private boolean isCreateNewURL(Exchange exchange) { * The body as a stream for the request entity. A Map, a List or an object (a body unmarshalled from JSON, XML or * CSV, or built in the route) cannot become one, and the converter's message only names the conversion: say what * the body is, the step that set it, and the marshal that writes it, in the data format the Content-Type names - * (CAMEL-25309, CAMEL-25749). + * (CAMEL-25309, CAMEL-25749). When neither the httpMethod option nor the CamelHttpMethod header set the method, the + * body made the call a POST, which is often meant as a GET (a lookup for each line of a split order): say that too + * (CAMEL-25745). */ - private static InputStream mandatoryBodyStream(Exchange exchange, Message in) throws InvalidPayloadException { + private InputStream mandatoryBodyStream(Exchange exchange, Message in) throws InvalidPayloadException { try { return in.getMandatoryBody(InputStream.class); } catch (InvalidPayloadException e) { // what the body is, the step that set it, and the marshal that writes it (CAMEL-25309, CAMEL-25749) String hint = BodyFacts.hint(exchange, BodyFacts.Need.BYTES, ExchangeHelper.getContentType(exchange)); if (hint != null) { - InvalidPayloadException answer = new InvalidPayloadException( - exchange, InputStream.class, in, - BodyFacts.describe(exchange) + ", which is not an HTTP request body: " + hint); + String message = BodyFacts.describe(exchange) + ", which is not an HTTP request body"; + if (getEndpoint().getHttpMethod() == null && in.getHeader(HttpConstants.HTTP_METHOD) == null) { + // the body made the call a POST (CAMEL-25745) + message += ". The call is a POST because the message has a body and neither the httpMethod option" + + " nor the CamelHttpMethod header sets the method: for a GET, set httpMethod GET on the" + + " endpoint (a GET sends no body); for a POST, " + hint; + } else { + message += ": " + hint; + } + InvalidPayloadException answer = new InvalidPayloadException(exchange, InputStream.class, in, message); answer.initCause(e.getCause() != null ? e.getCause() : e); throw answer; } diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java index 7153bed9e106a..1e45694f9bae0 100644 --- a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java +++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMapBodyTest.java @@ -66,7 +66,22 @@ public void aMapBodySaysToMarshalIt() { assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class) .hasMessageContaining("the body is a Map (LinkedHashMap), which is not an HTTP request body") .hasMessageContaining("marshal it first with the data format to write (such as marshal: json," - + " marshal: jacksonXml or marshal: csv), or set the body to the text"); + + " marshal: jacksonXml or marshal: csv), or set the body to the text") + // the method is set, so it is not about GET + .hasMessageNotContaining("set httpMethod GET"); + } + + @Test + public void aMapBodyWithoutAMethodSaysHowToSendAGet() { + // no httpMethod option and no CamelHttpMethod header: the body made the call a POST (CAMEL-25745) + Exchange exchange = template.request(endpointUrl + "/post", + e -> e.getIn().setBody(new LinkedHashMap<>(Map.of("sku", "CAMEL-MUG", "qty", 2)))); + + assertThat(exchange.getException()).isInstanceOf(InvalidPayloadException.class) + .hasMessageContaining("the body is a Map (LinkedHashMap), which is not an HTTP request body") + .hasMessageContaining("The call is a POST because the message has a body") + .hasMessageContaining("for a GET, set httpMethod GET on the endpoint") + .hasMessageContaining("for a POST, marshal it first with the data format to write"); } @Test