From 37243f44e861aa4b07b2e9402711ee3a858eee7e Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 5 Nov 2024 15:55:37 +0000 Subject: [PATCH] Refactor cookie support for Apache HttpClient Closes gh-33822 --- .../HttpComponentsClientHttpRequest.java | 36 ++++++++----- .../HttpComponentsClientHttpResponse.java | 54 ++++++++++++++----- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java index 6e7f4ac3dcf..c878792c0f2 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java @@ -19,11 +19,9 @@ package org.springframework.http.client.reactive; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; -import java.util.Collection; +import java.util.List; import java.util.function.Function; -import org.apache.hc.client5.http.cookie.CookieStore; -import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpRequest; @@ -37,11 +35,13 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.support.HttpComponentsHeadersAdapter; import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; /** * {@link ClientHttpRequest} implementation for the Apache HttpComponents HttpClient 5.x. @@ -143,18 +143,26 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { if (getCookies().isEmpty()) { return; } + if (!CollectionUtils.isEmpty(getCookies())) { + this.httpRequest.setHeader(HttpHeaders.COOKIE, serializeCookies()); + } + } - CookieStore cookieStore = this.context.getCookieStore(); - - getCookies().values() - .stream() - .flatMap(Collection::stream) - .forEach(cookie -> { - BasicClientCookie clientCookie = new BasicClientCookie(cookie.getName(), cookie.getValue()); - clientCookie.setDomain(getURI().getHost()); - clientCookie.setPath(getURI().getPath()); - cookieStore.addCookie(clientCookie); - }); + private String serializeCookies() { + boolean first = true; + StringBuilder sb = new StringBuilder(); + for (List cookies : getCookies().values()) { + for (HttpCookie cookie : cookies) { + if (!first) { + sb.append("; "); + } + else { + first = false; + } + sb.append(cookie.getName()).append("=").append(cookie.getValue()); + } + } + return sb.toString(); } /** diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java index 52e28133393..e79468b428e 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpResponse.java @@ -21,9 +21,15 @@ import java.time.Duration; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Iterator; +import java.util.List; import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.cookie.CookieOrigin; +import org.apache.hc.client5.http.cookie.CookieSpec; +import org.apache.hc.client5.http.cookie.MalformedCookieException; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.Message; import org.reactivestreams.Publisher; @@ -53,23 +59,47 @@ class HttpComponentsClientHttpResponse extends AbstractClientHttpResponse { super(HttpStatusCode.valueOf(message.getHead().getCode()), HttpHeaders.readOnlyHttpHeaders(new HttpComponentsHeadersAdapter(message.getHead())), - adaptCookies(context), + adaptCookies(message.getHead(), context), Flux.from(message.getBody()).map(dataBufferFactory::wrap) ); } - private static MultiValueMap adaptCookies(HttpClientContext context) { + private static MultiValueMap adaptCookies( + HttpResponse response, HttpClientContext context) { + LinkedMultiValueMap result = new LinkedMultiValueMap<>(); - context.getCookieStore().getCookies().forEach(cookie -> - result.add(cookie.getName(), - ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue()) - .domain(cookie.getDomain()) - .path(cookie.getPath()) - .maxAge(getMaxAgeSeconds(cookie)) - .secure(cookie.isSecure()) - .httpOnly(cookie.containsAttribute("httponly")) - .sameSite(cookie.getAttribute("samesite")) - .build())); + + CookieSpec cookieSpec = context.getCookieSpec(); + CookieOrigin cookieOrigin = context.getCookieOrigin(); + + Iterator
itr = response.headerIterator(HttpHeaders.SET_COOKIE); + while (itr.hasNext()) { + Header header = itr.next(); + try { + List cookies = cookieSpec.parse(header, cookieOrigin); + for (Cookie cookie : cookies) { + try { + cookieSpec.validate(cookie, cookieOrigin); + result.add(cookie.getName(), + ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue()) + .domain(cookie.getDomain()) + .path(cookie.getPath()) + .maxAge(getMaxAgeSeconds(cookie)) + .secure(cookie.isSecure()) + .httpOnly(cookie.containsAttribute("httponly")) + .sameSite(cookie.getAttribute("samesite")) + .build()); + } + catch (final MalformedCookieException ex) { + // ignore invalid cookie + } + } + } + catch (final MalformedCookieException ex) { + // ignore invalid cookie + } + } + return result; }