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 f3a04698c7b8 CAMEL-25563: camel-http - Send each value of a 
multi-value header as its own HTTP header (#27787)
f3a04698c7b8 is described below

commit f3a04698c7b83bc5a969e58c766ae89300fc96f6
Author: Urmila Unni <[email protected]>
AuthorDate: Wed Oct 14 17:16:59 2026 +0530

    CAMEL-25563: camel-http - Send each value of a multi-value header as its 
own HTTP header (#27787)
    
    Co-authored-by: Claude <[email protected]>
---
 .../apache/camel/component/http/HttpProducer.java  |  46 ++++++--
 .../http/HttpProducerMultiValueHeaderTest.java     | 127 +++++++++++++++++++++
 .../HttpProducerTwoHeadersWithSameKeyTest.java     |   8 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  15 +++
 4 files changed, 184 insertions(+), 12 deletions(-)

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 c689b99da43d..a15243f1d4eb 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
@@ -35,6 +35,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.stream.Stream;
 
 import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
@@ -211,11 +212,18 @@ public class HttpProducer extends DefaultProducer 
implements LineNumberAware {
                             continue;
                         }
 
-                        // use an iterator as there can be multiple values. 
(must not use a delimiter, and allow empty values)
-                        final Iterator<?> it = 
ObjectHelper.createIterator(headerValue, null, true);
-
-                        HttpUtil.applyHeader(strategy, exchange, it, tc, key,
-                                (multiValues, prev) -> 
applyHeader(httpRequest, key, multiValues, prev));
+                        if (isSentAsOneJoinedHeader(headerValue)) {
+                            // a primitive array (other than byte[] and 
char[]), an Iterator, a Stream, or an Iterable
+                            // that is not a Collection is sent as one header 
in the form [a, b], as before
+                            final Iterator<?> it = 
ObjectHelper.createIterator(headerValue, null, true);
+                            HttpUtil.applyHeader(strategy, exchange, it, tc, 
key,
+                                    (multiValues, prev) -> 
applyJoinedHeader(httpRequest, key, multiValues, prev));
+                        } else {
+                            // a collection or an object array is sent as one 
header per value; any other value
+                            // is converted to one String and sent as a single 
header
+                            HttpUtil.applyHeaderValue(strategy, exchange, 
headerValue, tc, key,
+                                    (multiValues, prev) -> 
applyHeader(httpRequest, key, multiValues, prev));
+                        }
                     }
                 }
             }
@@ -340,12 +348,30 @@ public class HttpProducer extends DefaultProducer 
implements LineNumberAware {
     }
 
     private static void applyHeader(HttpUriRequest httpRequest, String key, 
List<String> multiValues, String prev) {
-        // add the value(s) as a http request header
+        // add the value(s) as http request header(s), one header line per 
value
+        if (multiValues != null) {
+            for (String value : multiValues) {
+                httpRequest.addHeader(key, value);
+            }
+        } else if (prev != null) {
+            httpRequest.addHeader(key, prev);
+        }
+    }
+
+    private static boolean isSentAsOneJoinedHeader(Object value) {
+        if (value instanceof Iterator || value instanceof Stream
+                || (value instanceof Iterable && !(value instanceof 
Collection))) {
+            return true;
+        }
+        Class<?> type = value.getClass();
+        return type.isArray() && type.getComponentType().isPrimitive()
+                && type != byte[].class && type != char[].class;
+    }
+
+    private static void applyJoinedHeader(HttpUriRequest httpRequest, String 
key, List<String> multiValues, String prev) {
+        // two or more values are sent as one header in the form [a, b], one 
value as it is
         if (multiValues != null) {
-            // use the default toString of a ArrayList to create in the form 
[xxx, yyy]
-            // if multi valued, for a single value, then just output the value 
as is
-            String s = multiValues.size() > 1 ? multiValues.toString() : 
multiValues.get(0);
-            httpRequest.addHeader(key, s);
+            httpRequest.addHeader(key, multiValues.toString());
         } else if (prev != null) {
             httpRequest.addHeader(key, prev);
         }
diff --git 
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMultiValueHeaderTest.java
 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMultiValueHeaderTest.java
new file mode 100644
index 000000000000..72e9dca208cf
--- /dev/null
+++ 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerMultiValueHeaderTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.http;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class HttpProducerMultiValueHeaderTest extends BaseHttpTest {
+
+    private HttpServer localServer;
+
+    @Override
+    public void setupResources() throws Exception {
+        localServer = ServerBootstrap.bootstrap()
+                
.setCanonicalHostName("localhost").setHttpProcessor(getBasicHttpProcessor())
+                
.setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory())
+                .setSslContext(getSSLContext())
+                .register("/myapp", (request, response, context) -> {
+                    // reply with the values of the received header lines, 
separated by |
+                    String body = Arrays.stream(request.getHeaders("test"))
+                            .map(Header::getValue)
+                            .collect(Collectors.joining("|"));
+                    response.setEntity(new StringEntity(body, 
StandardCharsets.US_ASCII));
+                    response.setCode(HttpStatus.SC_OK);
+                }).create();
+        localServer.start();
+    }
+
+    @Override
+    public void cleanupResources() throws Exception {
+        if (localServer != null) {
+            localServer.stop();
+        }
+    }
+
+    @Test
+    public void testListIsOneHeaderPerValue() {
+        assertEquals("a|b|c", send(List.of("a", "b", "c")));
+    }
+
+    @Test
+    public void testArrayIsOneHeaderPerValue() {
+        assertEquals("a|b", send(new String[] { "a", "b" }));
+    }
+
+    @Test
+    public void testStringWithCommaIsOneHeader() {
+        assertEquals("a, b", send("a, b"));
+    }
+
+    @Test
+    public void testNullElementsAreSkipped() {
+        assertEquals("a|b", send(Arrays.asList("a", null, "b")));
+    }
+
+    @Test
+    public void testSingleElementListIsOneHeader() {
+        assertEquals("x", send(List.of("x")));
+    }
+
+    @Test
+    public void testIntArrayIsOneHeaderAsBefore() {
+        assertEquals("[1, 2]", send(new int[] { 1, 2 }));
+    }
+
+    @Test
+    public void testIteratorIsOneHeaderAsBefore() {
+        assertEquals("[a, b]", send(List.of("a", "b").iterator()));
+    }
+
+    @Test
+    public void testByteArrayIsSentAsText() {
+        assertEquals("abc", send("abc".getBytes(StandardCharsets.US_ASCII)));
+    }
+
+    @Test
+    public void testStreamIsOneHeaderAsBefore() {
+        assertEquals("[a, b]", send(Stream.of("a", "b")));
+    }
+
+    @Test
+    public void testCharArrayIsSentAsText() {
+        assertEquals("ab", send(new char[] { 'a', 'b' }));
+    }
+
+    @Test
+    public void testIterableIsOneHeaderAsBefore() {
+        Iterable<String> iterable = () -> List.of("a", "b").iterator();
+        assertEquals("[a, b]", send(iterable));
+    }
+
+    @Test
+    public void testSingleElementIntArrayIsOneHeader() {
+        assertEquals("7", send(new int[] { 7 }));
+    }
+
+    private String send(Object headerValue) {
+        String uri = "http://localhost:"; + localServer.getLocalPort() + 
"/myapp";
+        return template.requestBodyAndHeader(uri, null, "test", headerValue, 
String.class);
+    }
+}
diff --git 
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerTwoHeadersWithSameKeyTest.java
 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerTwoHeadersWithSameKeyTest.java
index 7f47d0d13b67..3793b976ea61 100644
--- 
a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerTwoHeadersWithSameKeyTest.java
+++ 
b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpProducerTwoHeadersWithSameKeyTest.java
@@ -49,7 +49,9 @@ public class HttpProducerTwoHeadersWithSameKeyTest extends 
BaseHttpTest {
                     Header[] from = request.getHeaders("from");
                     assertEquals("me", from[0].getValue());
                     Header[] to = request.getHeaders("to");
-                    assertEquals("[foo, bar]", to[0].getValue());
+                    assertEquals(2, to.length);
+                    assertEquals("foo", to[0].getValue());
+                    assertEquals("bar", to[1].getValue());
 
                     response.setHeader("bar", "yes");
                     response.addHeader("foo", "123");
@@ -61,7 +63,9 @@ public class HttpProducerTwoHeadersWithSameKeyTest extends 
BaseHttpTest {
                     Header[] from = request.getHeaders("from");
                     assertEquals("me", from[0].getValue());
                     Header[] to = request.getHeaders("to");
-                    assertEquals("[foo, bar]", to[0].getValue());
+                    assertEquals(2, to.length);
+                    assertEquals("foo", to[0].getValue());
+                    assertEquals("bar", to[1].getValue());
 
                     response.setHeader("bar", "yes");
                     response.addHeader("foo", "123");
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 a67107184f73..afd11a244abb 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
@@ -243,6 +243,21 @@ of a `JsonArray`.
 A route that needs the old single header of the producer can set the header to 
a String, for example with
 `String.valueOf(list)`.
 
+=== camel-http - a header with several values is sent as several headers
+
+A message header whose value is a `java.util.Collection` or an object array 
(as the HTTP consumers map a request
+header that occurs more than once) was sent as one HTTP header with the values 
in the form `[a, b]`. The producer now
+sends one HTTP header per value, as camel-vertx-http and camel-knative-http 
do. Null values are skipped, and a
+collection with one value is sent as one header with that value, as before.
+
+Every other value is sent as one header. A String is sent as before, also when 
it contains commas. A `byte[]` or a
+`char[]` is now converted to a String with the type converter, for example 
`abc` (it was sent as `[97, 98, 99]`), and
+a `Map` is sent as its `toString()`, such as `{k1=v1, k2=v2}` (it was `[k1=v1, 
k2=v2]`). The other primitive arrays,
+an `Iterator`, a `java.util.stream.Stream` and an `Iterable` that is not a 
collection are sent in the form `[a, b]` as before. A route that needs
+the old single header can set the header to a String, for example with 
`String.valueOf(list)`.
+
+This also applies to camel-graphql, which sends its requests with camel-http.
+
 === camel-dapr - the saveBulk and executeTransaction state operations are sent
 
 The `saveBulk` and `executeTransaction` operations of `dapr:state` built their 
request but never sent it to Dapr, so

Reply via email to