Skip to content

Commit 800e0a7

Browse files
committed
fixing SimpleHttpRequest by using a single combined cookie header string
1 parent c97bfac commit 800e0a7

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

http-client/src/main/java/io/micronaut/http/client/netty/NettyClientHttpRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public MutableHttpRequest<B> cookies(Set<Cookie> cookies) {
131131
String value = ClientCookieEncoder.INSTANCE.encode(cookie);
132132
this.cookies.put(cookie.getName(), value);
133133
}
134-
headers.set(HttpHeaderNames.COOKIE, String.join(";", this.cookies.values()));
134+
headers.set(HttpHeaderNames.COOKIE, String.join("; ", this.cookies.values()));
135135
} else if (!cookies.isEmpty()) {
136136
cookie(cookies.iterator().next());
137137
}

http/src/main/java/io/micronaut/http/simple/SimpleHttpRequest.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,20 @@ public MutableHttpRequest<B> cookie(Cookie cookie) {
7777

7878
private void updateCookies() {
7979
headers.remove(HttpHeaders.COOKIE);
80+
StringBuilder sb = new StringBuilder();
81+
boolean first = true;
8082
for (Cookie cookie : cookies.getAll()) {
81-
headers.add(HttpHeaders.COOKIE, ClientCookieEncoder.INSTANCE.encode(cookie));
83+
String encoded = ClientCookieEncoder.INSTANCE.encode(cookie);
84+
if (encoded != null && !encoded.isEmpty()) {
85+
if (!first) {
86+
sb.append("; ");
87+
}
88+
sb.append(encoded);
89+
first = false;
90+
}
91+
}
92+
if (sb.length() > 0) {
93+
headers.set(HttpHeaders.COOKIE, sb.toString());
8294
}
8395
}
8496

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.micronaut.repro.http
2+
3+
import io.micronaut.http.HttpHeaders
4+
import io.micronaut.http.MutableHttpRequest
5+
import io.micronaut.http.client.netty.NettyClientHttpRequestFactory
6+
import io.micronaut.http.cookie.Cookie
7+
import io.micronaut.http.simple.SimpleHttpRequestFactory
8+
import org.junit.jupiter.api.Assertions.assertEquals
9+
import org.junit.jupiter.api.Assertions.assertFalse
10+
import org.junit.jupiter.api.Assertions.assertTrue
11+
import org.junit.jupiter.api.Test
12+
13+
class CookieHeaderSingleValueReproTest {
14+
15+
private val cookies = linkedSetOf(
16+
Cookie.of("a", "1"),
17+
Cookie.of("b", "2"),
18+
Cookie.of("a", "3") // latest value for 'a' should win
19+
)
20+
21+
// Simple request factory
22+
@Test
23+
fun simpleRequest_addCookies_oneByOne_resultsInSingleCookieHeader() {
24+
val request: MutableHttpRequest<Any> = SimpleHttpRequestFactory().get<Any>("/")
25+
assertSingleCookieHeaderAfterAddingCookiesIndividually(request)
26+
}
27+
28+
@Test
29+
fun simpleRequest_addCookies_bulk_resultsInSingleCookieHeader() {
30+
val request: MutableHttpRequest<Any> = SimpleHttpRequestFactory().get<Any>("/")
31+
assertSingleCookieHeaderAfterAddingCookiesBulk(request)
32+
}
33+
34+
// Netty client request factory
35+
@Test
36+
fun nettyClientRequest_addCookies_oneByOne_resultsInSingleCookieHeader() {
37+
val request: MutableHttpRequest<Any> = NettyClientHttpRequestFactory().get<Any>("/")
38+
assertSingleCookieHeaderAfterAddingCookiesIndividually(request)
39+
}
40+
41+
@Test
42+
fun nettyClientRequest_addCookies_bulk_resultsInSingleCookieHeader() {
43+
val request: MutableHttpRequest<Any> = NettyClientHttpRequestFactory().get<Any>("/")
44+
assertSingleCookieHeaderAfterAddingCookiesBulk(request)
45+
}
46+
47+
private fun assertSingleCookieHeaderAfterAddingCookiesIndividually(request: MutableHttpRequest<*>) {
48+
cookies.forEach { request.cookie(it) }
49+
assertSingleCookieHeaderAndValues(request)
50+
}
51+
52+
private fun assertSingleCookieHeaderAfterAddingCookiesBulk(request: MutableHttpRequest<*>) {
53+
request.cookies(cookies)
54+
assertSingleCookieHeaderAndValues(request)
55+
}
56+
57+
private fun assertSingleCookieHeaderAndValues(request: MutableHttpRequest<*>) {
58+
val cookieHeaders = request.headers.getAll(HttpHeaders.COOKIE)
59+
assertEquals(1, cookieHeaders.size, "Expected a single Cookie header value")
60+
61+
val value = cookieHeaders[0]
62+
// Validate semantics (latest value wins, all expected cookies present)
63+
val parts = value.split(";".toRegex()).map { it.trim() }.filter { it.isNotEmpty() }
64+
assertTrue(parts.contains("a=3"), "Expected latest value for cookie 'a' to be present")
65+
assertTrue(parts.contains("b=2"), "Expected cookie 'b' to be present")
66+
assertFalse(parts.contains("a=1"), "Expected old value for cookie 'a' to be replaced")
67+
}
68+
}

0 commit comments

Comments
 (0)