This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new c55c94104a07 CAMEL-26525: camel-azure-functions - invokeFunction uses 
the Content-Type header of the message (#29449)
c55c94104a07 is described below

commit c55c94104a07042d8d7c8f0cea326a4be09ae428
Author: Urmila Unni <[email protected]>
AuthorDate: Mon Nov 9 17:45:59 2026 +0530

    CAMEL-26525: camel-azure-functions - invokeFunction uses the Content-Type 
header of the message (#29449)
    
    * CAMEL-26525: camel-azure-functions - invokeFunction uses the Content-…
    * CAMEL-26525: test that GET does not send the Content-Type header of t…
    * CAMEL-26525: describe the new Content-Type order in the component docs
    
    Co-authored-by: Claude <[email protected]>
---
 .../camel/catalog/components/azure-functions.json  |  2 +-
 .../catalog/docs/azure-functions-component.adoc    |  6 ++-
 .../component/azure/functions/azure-functions.json |  2 +-
 .../src/main/docs/azure-functions-component.adoc   |  6 ++-
 .../azure/functions/FunctionsEndpoint.java         |  5 ++-
 .../azure/functions/FunctionsProducer.java         | 13 ++++++
 .../functions/FunctionsProducerInvokeTest.java     | 50 ++++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 10 +++++
 8 files changed, 86 insertions(+), 8 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-functions.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-functions.json
index e7b5662081d7..cb5aa1bf72ac 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-functions.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/azure-functions.json
@@ -68,7 +68,7 @@
     "CamelAzureFunctionsStorageConnectionString": { "index": 17, "kind": 
"header", "displayName": "", "group": "producer", "label": "producer", 
"required": false, "javaType": "String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Storage account connection string for the function app", "constantName": 
"org.apache.camel.component.azure.functions.FunctionsConstants#STORAGE_CONNECTION_STRING"
 }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "value", "bytes", "stream", "file", 
"null" ], "byOption": [ { "option": "operation", "value": "listFunctionApps", 
"carriers": [ "any" ] }, { "option": "operation", "value": "getFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "createFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "deleteFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "startFunctionApp", 
"carriers": [ "any"  [...]
+    "producerIn": { "carriers": [ "text", "value", "bytes", "stream", "file", 
"null" ], "byOption": [ { "option": "operation", "value": "listFunctionApps", 
"carriers": [ "any" ] }, { "option": "operation", "value": "getFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "createFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "deleteFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "startFunctionApp", 
"carriers": [ "any"  [...]
     "producerOut": { "carriers": [ "text" ], "javaType": "java.lang.String", 
"byOption": [ { "option": "operation", "value": "listFunctionApps", "carriers": 
[ "list" ], "javaType": "java.util.List<java.util.Map<String, Object>>" }, { 
"option": "operation", "value": "getFunctionApp", "carriers": [ "map" ], 
"javaType": "java.util.Map<String, Object>" }, { "option": "operation", 
"value": "createFunctionApp", "carriers": [ "map" ], "javaType": 
"java.util.Map<String, Object>" }, { "option": " [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-functions-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-functions-component.adoc
index bb3c5c1178a6..15c03b8e1c8e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-functions-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-functions-component.adoc
@@ -211,8 +211,10 @@ The `invokeFunction` operation sends the message body to 
the function: text, a n
 marshalled: marshal it to JSON first, for example with `marshal().json()`, as 
a body that does not convert to bytes
 fails the exchange. `GET` and `DELETE` send no body.
 
-A request with a body has the `Content-Type: application/json` header, unless 
the `CamelAzureFunctionsHttpHeaders`
-header, a `Map<String, String>` of HTTP headers to send, sets a `Content-Type`.
+A request with a body has the `Content-Type` that the 
`CamelAzureFunctionsHttpHeaders` header, a `Map<String, String>`
+of HTTP headers to send, sets. When that map sets none, the `Content-Type` 
header of the message is used, and when the
+message has none either, `Content-Type: application/json`. Text is always sent 
as UTF-8: the charset parameter of a
+`Content-Type` from the message, for example `text/plain; charset=ISO-8859-1`, 
is not used for the encoding.
 
 The response body of the function becomes the message body, as text, with the 
HTTP status in the
 `CamelAzureFunctionsStatusCode` header and the HTTP headers of the response in 
the `CamelAzureFunctionsResponseHeaders`
diff --git 
a/components/camel-azure/camel-azure-functions/src/generated/resources/META-INF/org/apache/camel/component/azure/functions/azure-functions.json
 
b/components/camel-azure/camel-azure-functions/src/generated/resources/META-INF/org/apache/camel/component/azure/functions/azure-functions.json
index e7b5662081d7..cb5aa1bf72ac 100644
--- 
a/components/camel-azure/camel-azure-functions/src/generated/resources/META-INF/org/apache/camel/component/azure/functions/azure-functions.json
+++ 
b/components/camel-azure/camel-azure-functions/src/generated/resources/META-INF/org/apache/camel/component/azure/functions/azure-functions.json
@@ -68,7 +68,7 @@
     "CamelAzureFunctionsStorageConnectionString": { "index": 17, "kind": 
"header", "displayName": "", "group": "producer", "label": "producer", 
"required": false, "javaType": "String", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Storage account connection string for the function app", "constantName": 
"org.apache.camel.component.azure.functions.FunctionsConstants#STORAGE_CONNECTION_STRING"
 }
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "text", "value", "bytes", "stream", "file", 
"null" ], "byOption": [ { "option": "operation", "value": "listFunctionApps", 
"carriers": [ "any" ] }, { "option": "operation", "value": "getFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "createFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "deleteFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "startFunctionApp", 
"carriers": [ "any"  [...]
+    "producerIn": { "carriers": [ "text", "value", "bytes", "stream", "file", 
"null" ], "byOption": [ { "option": "operation", "value": "listFunctionApps", 
"carriers": [ "any" ] }, { "option": "operation", "value": "getFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "createFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "deleteFunctionApp", 
"carriers": [ "any" ] }, { "option": "operation", "value": "startFunctionApp", 
"carriers": [ "any"  [...]
     "producerOut": { "carriers": [ "text" ], "javaType": "java.lang.String", 
"byOption": [ { "option": "operation", "value": "listFunctionApps", "carriers": 
[ "list" ], "javaType": "java.util.List<java.util.Map<String, Object>>" }, { 
"option": "operation", "value": "getFunctionApp", "carriers": [ "map" ], 
"javaType": "java.util.Map<String, Object>" }, { "option": "operation", 
"value": "createFunctionApp", "carriers": [ "map" ], "javaType": 
"java.util.Map<String, Object>" }, { "option": " [...]
   },
   "properties": {
diff --git 
a/components/camel-azure/camel-azure-functions/src/main/docs/azure-functions-component.adoc
 
b/components/camel-azure/camel-azure-functions/src/main/docs/azure-functions-component.adoc
index bb3c5c1178a6..15c03b8e1c8e 100644
--- 
a/components/camel-azure/camel-azure-functions/src/main/docs/azure-functions-component.adoc
+++ 
b/components/camel-azure/camel-azure-functions/src/main/docs/azure-functions-component.adoc
@@ -211,8 +211,10 @@ The `invokeFunction` operation sends the message body to 
the function: text, a n
 marshalled: marshal it to JSON first, for example with `marshal().json()`, as 
a body that does not convert to bytes
 fails the exchange. `GET` and `DELETE` send no body.
 
-A request with a body has the `Content-Type: application/json` header, unless 
the `CamelAzureFunctionsHttpHeaders`
-header, a `Map<String, String>` of HTTP headers to send, sets a `Content-Type`.
+A request with a body has the `Content-Type` that the 
`CamelAzureFunctionsHttpHeaders` header, a `Map<String, String>`
+of HTTP headers to send, sets. When that map sets none, the `Content-Type` 
header of the message is used, and when the
+message has none either, `Content-Type: application/json`. Text is always sent 
as UTF-8: the charset parameter of a
+`Content-Type` from the message, for example `text/plain; charset=ISO-8859-1`, 
is not used for the encoding.
 
 The response body of the function becomes the message body, as text, with the 
HTTP status in the
 `CamelAzureFunctionsStatusCode` header and the HTTP headers of the response in 
the `CamelAzureFunctionsResponseHeaders`
diff --git 
a/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsEndpoint.java
 
b/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsEndpoint.java
index 61eaeae45ec9..c24444a6a3ea 100644
--- 
a/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsEndpoint.java
+++ 
b/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsEndpoint.java
@@ -77,8 +77,9 @@ import org.apache.camel.util.ObjectHelper;
                                                 + " is sent as text (UTF-8), 
and bytes, a stream or a file as their content. A Map"
                                                 + " or a POJO is not"
                                                 + " marshalled: marshal it to 
JSON first, as a body that does not convert to bytes"
-                                                + " fails. The Content-Type is 
application/json unless the"
-                                                + " 
CamelAzureFunctionsHttpHeaders header sets one. GET and DELETE send no body."
+                                                + " fails. The Content-Type is 
the one the CamelAzureFunctionsHttpHeaders header"
+                                                + " sets, else the 
Content-Type header of the message, else application/json."
+                                                + " GET and DELETE send no 
body."
                                                 + " The management operations 
do not use the body: they take their input from"
                                                 + " headers, such as the app 
settings and the tags"),
            producerOut = @BodyType(value = Carrier.TEXT, javaType = 
"java.lang.String",
diff --git 
a/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsProducer.java
 
b/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsProducer.java
index 4d7fd20f246e..50447076b979 100644
--- 
a/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsProducer.java
+++ 
b/components/camel-azure/camel-azure-functions/src/main/java/org/apache/camel/component/azure/functions/FunctionsProducer.java
@@ -169,6 +169,14 @@ public class FunctionsProducer extends DefaultProducer {
             body = getRequestBody(exchange);
         }
 
+        // the Content-Type header of the message, when the headers map does 
not set one
+        String contentType = 
exchange.getMessage().getHeader(Exchange.CONTENT_TYPE, String.class);
+        if (body != null && contentType != null && !hasContentType(headers)) {
+            // copy the map, as it can be the map of the 
CamelAzureFunctionsHttpHeaders header
+            headers = headers != null ? new HashMap<>(headers) : new 
HashMap<>();
+            headers.put(Exchange.CONTENT_TYPE, contentType);
+        }
+
         LOG.debug("Invoking function: {} with method: {}", url, httpMethod);
 
         FunctionInvocationResponse response = invocationClient.invoke(url, 
httpMethod, body, headers);
@@ -184,6 +192,11 @@ public class FunctionsProducer extends DefaultProducer {
         message.setBody(response.getBody());
     }
 
+    private static boolean hasContentType(Map<String, String> headers) {
+        // HTTP header names are case-insensitive
+        return headers != null && 
headers.keySet().stream().anyMatch(Exchange.CONTENT_TYPE::equalsIgnoreCase);
+    }
+
     /**
      * Text, a number or a boolean is sent as its text (as UTF-8), and bytes, 
a stream, a file or any other body as its
      * content, converted to bytes with the type converter: a body that cannot 
be converted fails, instead of being sent
diff --git 
a/components/camel-azure/camel-azure-functions/src/test/java/org/apache/camel/component/azure/functions/FunctionsProducerInvokeTest.java
 
b/components/camel-azure/camel-azure-functions/src/test/java/org/apache/camel/component/azure/functions/FunctionsProducerInvokeTest.java
index 05ef21dbb0c7..ff03f1030fa6 100644
--- 
a/components/camel-azure/camel-azure-functions/src/test/java/org/apache/camel/component/azure/functions/FunctionsProducerInvokeTest.java
+++ 
b/components/camel-azure/camel-azure-functions/src/test/java/org/apache/camel/component/azure/functions/FunctionsProducerInvokeTest.java
@@ -190,6 +190,20 @@ class FunctionsProducerInvokeTest extends CamelTestSupport 
{
         assertNull(request.contentTypes());
     }
 
+    @Test
+    void getSendsNoContentTypeOfMessage() throws Exception {
+        // GET sends no body, so the Content-Type header of the message is not 
sent either
+        Exchange result = template.send(functionsEndpoint("&httpMethod=GET"), 
e -> {
+            e.getIn().setBody("Hello");
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
+        });
+
+        assertNull(result.getException());
+        RecordedRequest request = nextRequest();
+        assertEquals("GET", request.method());
+        assertNull(request.contentTypes());
+    }
+
     @Test
     void defaultContentTypeWithoutHttpHeaders() throws Exception {
         Exchange result = template.send(functionsEndpoint(""), e -> 
e.getIn().setBody("{\"name\":\"Camel\"}"));
@@ -221,6 +235,42 @@ class FunctionsProducerInvokeTest extends CamelTestSupport 
{
         assertEquals(List.of("text/plain"), nextRequest().contentTypes());
     }
 
+    @Test
+    void contentTypeOfMessageIsUsedWithoutHttpHeaders() throws Exception {
+        Exchange result = template.send(functionsEndpoint(""), e -> {
+            e.getIn().setBody("Hello");
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
+        });
+
+        assertNull(result.getException());
+        assertEquals(List.of("text/plain"), nextRequest().contentTypes());
+    }
+
+    @Test
+    void contentTypeOfMessageIsUsedWithHttpHeadersWithoutContentType() throws 
Exception {
+        // Map.of is immutable: the producer must copy the map to add the 
Content-Type
+        Exchange result = template.send(functionsEndpoint(""), e -> {
+            e.getIn().setBody("Hello");
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
+            e.getIn().setHeader(FunctionsConstants.HTTP_HEADERS, 
Map.of("X-Custom", "value"));
+        });
+
+        assertNull(result.getException());
+        assertEquals(List.of("text/plain"), nextRequest().contentTypes());
+    }
+
+    @Test
+    void contentTypeOfHttpHeadersWinsOverContentTypeOfMessage() throws 
Exception {
+        Exchange result = template.send(functionsEndpoint(""), e -> {
+            e.getIn().setBody("Hello");
+            e.getIn().setHeader(Exchange.CONTENT_TYPE, "text/csv");
+            e.getIn().setHeader(FunctionsConstants.HTTP_HEADERS, 
Map.of("content-type", "text/plain"));
+        });
+
+        assertNull(result.getException());
+        assertEquals(List.of("text/plain"), nextRequest().contentTypes());
+    }
+
     @Test
     void serverErrorFailsTheExchange() throws Exception {
         responseStatus = 500;
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 7d1a8214d90f..3f82626e4ff3 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -7515,3 +7515,13 @@ The `logWarnOnBadRequest` option of a `netty-http` 
consumer is documented as ena
 left at `false`, so a request that Netty could not decode got its HTTP 400 
returned without a log line. The option now
 defaults to `true`, as documented, so such a request logs `Netty request 
decoder failure due: <reason> returning HTTP
 Status 400 to client` at `WARN` level. Set `logWarnOnBadRequest=false` on the 
consumer to keep the previous silence.
+
+=== camel-azure-functions - invokeFunction uses the Content-Type header of the 
message
+
+When the `CamelAzureFunctionsHttpHeaders` header does not set a 
`Content-Type`, `invokeFunction` now sends the
+`Content-Type` header of the message, if there is one, before falling back to 
`application/json`. Before, the
+request was always sent as `application/json` in that case.
+
+A route whose message still carries a `Content-Type` header from an earlier 
step, for example from an HTTP
+consumer, now sends that Content-Type. Remove the header, or set the 
Content-Type in
+`CamelAzureFunctionsHttpHeaders`, to keep the old behavior.

Reply via email to