From 3701d33a886071276384a2b76e588d3beda4e19c Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 18 Oct 2026 08:45:31 +0200 Subject: [PATCH 1/2] CAMEL-25745: camel-http - make it stand out that a message with a body is sent as a POST unless httpMethod is set The http page opens its Usage section with which HTTP method is used: the rule, the trap after unmarshal, split or setBody, the fix (httpMethod GET; a GET sends no body unless getWithBody is true), the order, and examples in Java, XML and YAML. camel_catalog_doc returns the start of the page, so the rule is in its answers too. How to set the http method names the option first, and its YAML example is canonical. The error for a Map or List body says, when neither the option nor the header set the method, that the call is a POST because the message has a body, and how to make it a GET. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../camel/catalog/docs/http-component.adoc | 129 ++++++++++++++++-- .../src/main/docs/http-component.adoc | 129 ++++++++++++++++-- .../camel/component/http/HttpProducer.java | 19 ++- .../http/HttpProducerMapBodyTest.java | 17 ++- 4 files changed, 262 insertions(+), 32 deletions(-) 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..8be45a1e4aa54 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] ==== @@ -353,7 +454,9 @@ YAML:: steps: - setHeader: name: CamelHttpMethod - constant: POST + expression: + constant: + expression: POST - to: uri: http://www.google.com - to: diff --git a/components/camel-http/src/main/docs/http-component.adoc b/components/camel-http/src/main/docs/http-component.adoc index 094bfccca34a2..8be45a1e4aa54 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] ==== @@ -353,7 +454,9 @@ YAML:: steps: - setHeader: name: CamelHttpMethod - constant: POST + expression: + constant: + expression: POST - to: uri: http://www.google.com - to: 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 From dd81ea0728cbd45c4493957c3636f4f623ca6cc0 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Sun, 18 Oct 2026 09:17:25 +0200 Subject: [PATCH 2/2] CAMEL-25745: camel-http - keep the compact YAML setHeader, as in the other examples on the page Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../org/apache/camel/catalog/docs/http-component.adoc | 4 +--- components/camel-http/src/main/docs/http-component.adoc | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) 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 8be45a1e4aa54..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 @@ -454,9 +454,7 @@ YAML:: steps: - setHeader: name: CamelHttpMethod - expression: - constant: - expression: POST + constant: POST - to: uri: http://www.google.com - to: diff --git a/components/camel-http/src/main/docs/http-component.adoc b/components/camel-http/src/main/docs/http-component.adoc index 8be45a1e4aa54..2b461b9b1921d 100644 --- a/components/camel-http/src/main/docs/http-component.adoc +++ b/components/camel-http/src/main/docs/http-component.adoc @@ -454,9 +454,7 @@ YAML:: steps: - setHeader: name: CamelHttpMethod - expression: - constant: - expression: POST + constant: POST - to: uri: http://www.google.com - to: