You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ab...@apache.org on 2026/10/08 09:16:27 UTC

(httpcomponents-client) branch master updated: SSE: Handle request header names case-insensitively

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

arturobernalg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new 5a3138de5 SSE: Handle request header names case-insensitively
5a3138de5 is described below

commit 5a3138de57230d72463827393554ad9e6682c0ef
Author: Arturo Bernal <ab...@apache.org>
AuthorDate: Tue Oct 6 14:10:35 2026 +0200

    SSE: Handle request header names case-insensitively
---
 .../client5/http/sse/impl/DefaultEventSource.java  | 10 ++++--
 .../http/sse/impl/DefaultEventSourceTest.java      | 41 ++++++++++++++++++++++
 2 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/httpclient5-sse/src/main/java/org/apache/hc/client5/http/sse/impl/DefaultEventSource.java b/httpclient5-sse/src/main/java/org/apache/hc/client5/http/sse/impl/DefaultEventSource.java
index 6d59dff0e..4a1f8b049 100644
--- a/httpclient5-sse/src/main/java/org/apache/hc/client5/http/sse/impl/DefaultEventSource.java
+++ b/httpclient5-sse/src/main/java/org/apache/hc/client5/http/sse/impl/DefaultEventSource.java
@@ -32,8 +32,9 @@
 import java.net.URI;
 import java.util.Map;
 import java.util.Objects;
+import java.util.TreeMap;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
@@ -178,7 +179,8 @@ public DefaultEventSource(final CloseableHttpAsyncClient client,
                        final SseParser parser) {
         this.client = Objects.requireNonNull(client, "client");
         this.uri = Objects.requireNonNull(uri, "uri");
-        this.headers = new ConcurrentHashMap<>(Objects.requireNonNull(headers, "headers"));
+        this.headers = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER);
+        this.headers.putAll(Objects.requireNonNull(headers, "headers"));
         this.listener = listener != null ? listener : (id, type, data) -> { /* no-op */ };
 
         if (scheduler != null) {
@@ -273,7 +275,9 @@ public void removeHeader(final String name) {
      */
     @Override
     public Map<String, String> getHeaders() {
-        return new ConcurrentHashMap<>(headers);
+        final Map<String, String> copy = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        copy.putAll(headers);
+        return copy;
     }
 
     /**
diff --git a/httpclient5-sse/src/test/java/org/apache/hc/client5/http/sse/impl/DefaultEventSourceTest.java b/httpclient5-sse/src/test/java/org/apache/hc/client5/http/sse/impl/DefaultEventSourceTest.java
index 43d2d1fd5..18827f589 100644
--- a/httpclient5-sse/src/test/java/org/apache/hc/client5/http/sse/impl/DefaultEventSourceTest.java
+++ b/httpclient5-sse/src/test/java/org/apache/hc/client5/http/sse/impl/DefaultEventSourceTest.java
@@ -32,7 +32,9 @@
 
 import java.net.URI;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.AbstractExecutorService;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CompletableFuture;
@@ -323,4 +325,43 @@ void sendsNonEmptyLastEventId() {
                 "42",
                 client.lastRequest.getFirstHeader("Last-Event-ID").getValue());
     }
+
+    @Test
+    void headerNamesAreCaseInsensitive() {
+        final DefaultEventSource eventSource = new DefaultEventSource(
+                new CapturingClient(),
+                URI.create("http://localhost/sse"),
+                Collections.emptyMap(),
+                null);
+
+        eventSource.setHeader("Authorization", "one");
+        eventSource.setHeader("authorization", "two");
+
+        final Map<String, String> headers = eventSource.getHeaders();
+
+        assertEquals(1, headers.size());
+        assertEquals("two", headers.get("AUTHORIZATION"));
+
+        eventSource.removeHeader("AUTHORIZATION");
+
+        assertTrue(eventSource.getHeaders().isEmpty());
+    }
+
+    @Test
+    void initialHeaderNamesAreCaseInsensitive() {
+        final Map<String, String> initialHeaders = new LinkedHashMap<>();
+        initialHeaders.put("X-Test", "one");
+        initialHeaders.put("x-test", "two");
+
+        final DefaultEventSource eventSource = new DefaultEventSource(
+                new CapturingClient(),
+                URI.create("http://localhost/sse"),
+                initialHeaders,
+                null);
+
+        final Map<String, String> headers = eventSource.getHeaders();
+
+        assertEquals(1, headers.size());
+        assertEquals("two", headers.get("X-TEST"));
+    }
 }