Skip to content

Commit 5fc722f

Browse files
committed
✨ Added support for cookies into SpringRestClientRestClient
1 parent 9dcab64 commit 5fc722f

2 files changed

Lines changed: 208 additions & 30 deletions

File tree

spring/fluentforms-spring-boot-autoconfigure/src/main/java/com/_4point/aem/fluentforms/spring/rest_services/client/SpringRestClientRestClient.java

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import java.io.ByteArrayInputStream;
44
import java.io.IOException;
55
import java.io.InputStream;
6+
import java.net.HttpCookie;
67
import java.net.URI;
8+
import java.util.Collections;
79
import java.util.List;
810
import java.util.Map;
911
import java.util.Optional;
@@ -90,6 +92,7 @@ private abstract class SpringClientRequestBuilder {
9092
protected Function<UriBuilder, UriBuilder> uriBuilder = Function.identity();
9193
protected Consumer<org.springframework.http.HttpHeaders> headerBuilder = correlationIdFn != null ? h->h.put(RestClient.CORRELATION_ID_HTTP_HDR, List.of(correlationIdFn.get()))
9294
: __->{}; // if correlationIdFn is available, use it as the base headerBuilder function.
95+
protected Consumer<MultiValueMap<String, String>> cookiesConsumer = __->{};
9396

9497
public void addQueryParam(String name, String value) {
9598
this.uriBuilder = uriBuilder.andThen(u->u.queryParam(name, value));
@@ -98,15 +101,24 @@ public void addQueryParam(String name, String value) {
98101
public void addHeaderValue(String name, String value) {
99102
this.headerBuilder = headerBuilder.andThen(h->h.put(name, List.of(value)));
100103
}
104+
105+
public void addCookieValues(Cookies cookies) {
106+
if (cookies == null || cookies.isEmpty() || !(cookies instanceof SpringClientResponse.SpringRestClientResponseCookies srcCookies)) {
107+
return;
108+
}
109+
this.cookiesConsumer = cookiesConsumer.andThen(mvm->srcCookies.cookies.stream().forEach(c->mvm.add(c.getName(), c.getValue())));
110+
}
101111
}
102112

103113
private abstract static class SpringClientRequest {
104114
private final Function<UriBuilder, UriBuilder> uriBuilder;
105115
private final Consumer<org.springframework.http.HttpHeaders> headerBuilder;
116+
private final Consumer<MultiValueMap<String, String>> cookiesConsumer;
106117

107-
protected SpringClientRequest(Function<UriBuilder, UriBuilder> uriBuilder, Consumer<org.springframework.http.HttpHeaders> headerBuilder) {
118+
protected SpringClientRequest(Function<UriBuilder, UriBuilder> uriBuilder, Consumer<org.springframework.http.HttpHeaders> headerBuilder, Consumer<MultiValueMap<String, String>> cookiesConsumer) {
108119
this.uriBuilder = uriBuilder;
109120
this.headerBuilder = headerBuilder;
121+
this.cookiesConsumer = cookiesConsumer;
110122
}
111123

112124
protected Function<UriBuilder, URI> uriFunction() {
@@ -116,6 +128,7 @@ protected Function<UriBuilder, URI> uriFunction() {
116128
protected Optional<Response> processRequest(RequestHeadersSpec<?> request, ContentType acceptContentType) throws RestClientException {
117129
ResponseEntity<byte[]> result = request.accept(toMediaType(acceptContentType))
118130
.headers(headerBuilder)
131+
.cookies(cookiesConsumer)
119132
.retrieve()
120133
.toEntity(byte[].class);
121134

@@ -172,19 +185,29 @@ public HttpHeaders headers() {
172185

173186
@Override
174187
public Cookies getCookies() {
175-
return new SpringRestClientResponseCookies(headers.getFirst(org.springframework.http.HttpHeaders.SET_COOKIE));
188+
@Nullable List<String> list = headers.get(org.springframework.http.HttpHeaders.SET_COOKIE);
189+
return SpringRestClientResponseCookies.from(list);
176190
}
177191

178192
private static class SpringRestClientResponseCookies implements Cookies {
179-
private final @Nullable String cookieHeaderValue;
193+
private final List<HttpCookie> cookies;
180194

181-
private SpringRestClientResponseCookies(@Nullable String cookieHeaderValue) {
182-
this.cookieHeaderValue = cookieHeaderValue;
195+
private SpringRestClientResponseCookies(List<HttpCookie> cookies) {
196+
this.cookies = Collections.unmodifiableList(cookies);
183197
}
184198

199+
private static SpringRestClientResponseCookies from(@Nullable List<String> cookieHeaderValues) {
200+
List<HttpCookie> cookiesList = cookieHeaderValues == null ? List.of()
201+
: cookieHeaderValues.stream()
202+
.map(HttpCookie::parse)
203+
.flatMap(List::stream)
204+
.toList();
205+
return new SpringRestClientResponseCookies(cookiesList);
206+
}
207+
185208
@Override
186209
public boolean isEmpty() {
187-
return cookieHeaderValue == null || cookieHeaderValue.isEmpty();
210+
return cookies.isEmpty();
188211
}
189212

190213
@Override
@@ -206,8 +229,12 @@ private static MediaType toMediaType(ContentType contentType) {
206229
public final class SpringRestClientMultipartPayload extends SpringClientRequest implements MultipartPayload {
207230
private final MultiValueMap<String, HttpEntity<?>> multipartBody;
208231

209-
private SpringRestClientMultipartPayload(Function<UriBuilder, UriBuilder> uriBuilder, Consumer<org.springframework.http.HttpHeaders> headerBuilder, MultiValueMap<String, HttpEntity<?>> multipartBody) {
210-
super(uriBuilder, headerBuilder);
232+
private SpringRestClientMultipartPayload(Function<UriBuilder, UriBuilder> uriBuilder,
233+
Consumer<org.springframework.http.HttpHeaders> headerBuilder,
234+
Consumer<MultiValueMap<String, String>> cookiesConsumer,
235+
MultiValueMap<String, HttpEntity<?>> multipartBody
236+
) {
237+
super(uriBuilder, headerBuilder, cookiesConsumer);
211238
this.multipartBody = multipartBody;
212239
}
213240

@@ -260,7 +287,7 @@ public Builder add(String fieldName, InputStream fieldData, ContentType contentT
260287

261288
@Override
262289
public MultipartPayload build() {
263-
return new SpringRestClientMultipartPayload(uriBuilder, headerBuilder, parts);
290+
return new SpringRestClientMultipartPayload(uriBuilder, headerBuilder, cookiesConsumer, parts);
264291
}
265292

266293
@Override
@@ -277,14 +304,18 @@ public Builder addHeader(String name, String value) {
277304

278305
@Override
279306
public Builder addCookies(Cookies cookies) {
280-
throw new UnsupportedOperationException("Adding cookies to multipart payloads is not currently supported.");
307+
addCookieValues(cookies);
308+
return this;
281309
}
282310
}
283311

284312
private class SpringClientGetRequest extends SpringClientRequest implements RestClient.GetRequest {
285313

286-
private SpringClientGetRequest(Function<UriBuilder, UriBuilder> uriBuilder, Consumer<org.springframework.http.HttpHeaders> headerBuilder) {
287-
super(uriBuilder, headerBuilder);
314+
private SpringClientGetRequest(Function<UriBuilder, UriBuilder> uriBuilder,
315+
Consumer<org.springframework.http.HttpHeaders> headerBuilder,
316+
Consumer<MultiValueMap<String, String>> cookiesConsumer
317+
) {
318+
super(uriBuilder, headerBuilder, cookiesConsumer);
288319
}
289320

290321
@Override
@@ -301,9 +332,6 @@ public Optional<Response> getFromServer(ContentType acceptContentType) throws Re
301332
}
302333

303334
private class SpringClientGetRequestBuilder extends SpringClientRequestBuilder implements RestClient.GetRequest.Builder {
304-
// private Function<UriBuilder, UriBuilder> uriBuilder = Function.identity();
305-
// private Consumer<org.springframework.http.HttpHeaders> headerBuilder = correlationIdFn != null ? h->h.put(RestClient.CORRELATION_ID_HTTP_HDR, List.of(correlationIdFn.get()))
306-
// : __->{}; // if correlationIdFn is available, use it as the base headerBuilder function.
307335

308336
private SpringClientGetRequestBuilder() {
309337
}
@@ -314,7 +342,7 @@ private SpringClientGetRequestBuilder(String additionalPath) {
314342

315343
@Override
316344
public GetRequest build() {
317-
return new SpringClientGetRequest(uriBuilder, headerBuilder);
345+
return new SpringClientGetRequest(uriBuilder, headerBuilder, cookiesConsumer);
318346
}
319347

320348
@Override
@@ -331,7 +359,8 @@ public SpringClientGetRequestBuilder addHeader(String name, String value) {
331359

332360
@Override
333361
public GetRequest.Builder addCookies(Cookies cookies) {
334-
throw new UnsupportedOperationException("Adding cookies to GET requests is not currently supported.");
362+
addCookieValues(cookies);
363+
return this;
335364
}
336365
}
337366

spring/fluentforms-spring-boot-autoconfigure/src/test/java/com/_4point/aem/fluentforms/spring/rest_services/client/AbstractRestClientTest.java

Lines changed: 162 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -195,19 +195,13 @@ void testPostToServer_DocumentResponseWithMultipleHeaders() throws Exception {
195195
@Test
196196
void testPostToServer_DocumentResponseWithNoCookies() throws Exception {
197197
// Given
198-
final String COOKIES_KEY = "Set-Cookie";
199198
stubFor(post(ENDPOINT).willReturn(okForContentType(ContentType.APPLICATION_PDF.contentType(), MOCK_PDF_BYTES)
200199
));
201200

202201
// When
203202
Response response = postToServerBuilder().performPostToServer(FIELD1_NAME, FIELD1_DATA).orElseThrow();
204203

205204
// Then
206-
assertEquals(ContentType.APPLICATION_PDF, response.contentType());
207-
assertEquals(MOCK_PDF_BYTES, new String(response.data().readAllBytes()));
208-
assertTrue(response.retrieveHeader(COOKIES_KEY).isEmpty()); // Should not be present
209-
assertTrue(response.retrieveHeader(COOKIES_KEY.toUpperCase()).isEmpty()); // Should not be present
210-
211205
Cookies cookies = response.getCookies();
212206
assertFalse(cookies.isPresent());
213207
assertTrue(cookies.isEmpty());
@@ -232,11 +226,6 @@ void testPostToServer_DocumentResponseWithCookie() throws Exception {
232226
Response response = postToServerBuilder().performPostToServer(FIELD1_NAME, FIELD1_DATA).orElseThrow();
233227

234228
// Then
235-
assertEquals(ContentType.APPLICATION_PDF, response.contentType());
236-
assertEquals(MOCK_PDF_BYTES, new String(response.data().readAllBytes()));
237-
assertEquals(COOKIES_VALUE, response.retrieveHeader(COOKIES_KEY).orElseThrow()); // Should retrieve the first header value, not the 2nd or 3rd one
238-
assertEquals(COOKIES_VALUE, response.retrieveHeader(COOKIES_KEY.toUpperCase()).orElseThrow()); // Should retrieve the first header value, not the 3rd one
239-
240229
Cookies cookies = response.getCookies();
241230
assertTrue(cookies.isPresent());
242231
assertFalse(cookies.isEmpty());
@@ -296,6 +285,57 @@ void testPostToServer_DocumentResponseFromInputStream() throws Exception {
296285
);
297286
}
298287

288+
@DisplayName("PostToServer with 1 part and return 2 set-cookies in the response, send PostToServer with cookies from first response")
289+
@Test
290+
void testPostToServer_DocumentResponseWithCookie_PostWithCookies() throws Exception {
291+
// Given
292+
final String COOKIES_KEY = "Set-Cookie";
293+
final String COOKIE1_VALUE = "cookie1=value1; HttpOnly";
294+
final String COOKIE2_VALUE = "cookie2=value2; HttpOnly";
295+
final String POST_HEADER_KEY = "post";
296+
final String FIRST_POST_VALUE = "firstPost";
297+
final String SECOND_POST_VALUE = "secondPost";
298+
stubFor(post(ENDPOINT)
299+
.withHeader(POST_HEADER_KEY, equalTo(FIRST_POST_VALUE))
300+
.willReturn(okForContentType(ContentType.APPLICATION_PDF.contentType(), MOCK_PDF_BYTES)
301+
.withHeader(COOKIES_KEY, COOKIE1_VALUE)
302+
.withHeader(COOKIES_KEY, COOKIE2_VALUE)
303+
));
304+
stubFor(post(ENDPOINT)
305+
.withHeader(POST_HEADER_KEY, equalTo(SECOND_POST_VALUE))
306+
.willReturn(okForContentType(ContentType.APPLICATION_PDF.contentType(), MOCK_PDF_BYTES)));
307+
308+
// When
309+
Response response1 = postToServerBuilder()
310+
.headers(POST_HEADER_KEY, FIRST_POST_VALUE)
311+
.performPostToServer(FIELD1_NAME, FIELD1_DATA).orElseThrow();
312+
313+
Response response2 = postToServerBuilder()
314+
.headers(POST_HEADER_KEY, SECOND_POST_VALUE)
315+
.cookies(response1.getCookies())
316+
.performPostToServer(FIELD1_NAME, FIELD1_DATA).orElseThrow();
317+
318+
// Then
319+
assertEquals(ContentType.APPLICATION_PDF, response1.contentType());
320+
assertEquals(MOCK_PDF_BYTES, new String(response1.data().readAllBytes()));
321+
assertEquals(ContentType.APPLICATION_PDF, response2.contentType());
322+
assertEquals(MOCK_PDF_BYTES, new String(response2.data().readAllBytes()));
323+
324+
verify(postRequestedFor(urlEqualTo(ENDPOINT))
325+
.withAllRequestBodyParts(aMultipart(FIELD1_NAME).withBody(equalTo(FIELD1_DATA)))
326+
.withHeader(RestClient.CORRELATION_ID_HTTP_HDR, equalTo(CORRELATION_ID_TEXT))
327+
.withHeader(POST_HEADER_KEY, equalTo(FIRST_POST_VALUE))
328+
);
329+
330+
verify(postRequestedFor(urlEqualTo(ENDPOINT))
331+
.withAllRequestBodyParts(aMultipart(FIELD1_NAME).withBody(equalTo(FIELD1_DATA)))
332+
.withHeader(RestClient.CORRELATION_ID_HTTP_HDR, equalTo(CORRELATION_ID_TEXT))
333+
.withHeader(POST_HEADER_KEY, equalTo(SECOND_POST_VALUE))
334+
.withCookie("cookie1", equalTo("value1"))
335+
.withCookie("cookie2", equalTo("value2"))
336+
);
337+
}
338+
299339
@DisplayName("When AEM returns 500 Internal Server error with no body, postToServer should throw RestClientException.")
300340
@Test
301341
void testPostToServer_AemReturns500NoBody() throws Exception {
@@ -443,6 +483,7 @@ private PostToServerBuilder postToServerBuilder() {
443483
private class PostToServerBuilder {
444484
private String[] queryParams = new String[0];
445485
private String[] headers = new String[0];
486+
private Cookies cookies = null;;
446487

447488
private PostToServerBuilder queryParams(String...strings) {
448489
if (strings.length % 2 != 0) {
@@ -459,7 +500,12 @@ private PostToServerBuilder headers(String...strings) {
459500
headers = strings;
460501
return this;
461502
}
462-
503+
504+
private PostToServerBuilder cookies(Cookies cookies) {
505+
this.cookies = cookies;
506+
return this;
507+
}
508+
463509
private Optional<Response> performPostToServer(String...strings) throws RestClientException, Exception {
464510
if (strings.length % 2 != 0) {
465511
throw new IllegalArgumentException("Odd number of Strings passed in, must be even. (" + strings.length + ").");
@@ -499,7 +545,7 @@ private Optional<Response> performPostToServer(String fieldName, InputStream dat
499545
}
500546
}
501547

502-
MultipartPayload.Builder addHeadersAndQueryParams(MultipartPayload.Builder builder) {
548+
private MultipartPayload.Builder addHeadersAndQueryParams(MultipartPayload.Builder builder) {
503549
for(int i = 0; i < queryParams.length; i+=2) { // Add query Params
504550
builder.queryParam(queryParams[i], queryParams[i+1]);
505551
}
@@ -508,6 +554,10 @@ MultipartPayload.Builder addHeadersAndQueryParams(MultipartPayload.Builder build
508554
builder.addHeader(headers[i], headers[i+1]);
509555
}
510556

557+
if (cookies != null) {
558+
builder.addCookies(cookies);
559+
}
560+
511561
return builder;
512562
}
513563
}
@@ -578,6 +628,105 @@ void testGetFromServer_DocumentResponseWithHeader() throws Exception {
578628
);
579629
}
580630

631+
@DisplayName("GetFromServer with 2 query parameters and return no cookies in response")
632+
@Test
633+
void testGetFromServer_DocumentResponseNoCookies() throws Exception {
634+
// Given
635+
stubFor(get(urlPathEqualTo(ENDPOINT))
636+
.withQueryParams(Map.of(FIELD1_NAME, equalTo(FIELD1_DATA), FIELD2_NAME, equalTo(FIELD2_DATA)))
637+
.willReturn(okForContentType(ContentType.TEXT_HTML.contentType(), MOCK_PDF_BYTES)));
638+
639+
// When
640+
Response response = performGetFromServer(FIELD1_NAME, FIELD1_DATA, FIELD2_NAME, FIELD2_DATA).orElseThrow();
641+
642+
// Then
643+
Cookies cookies = response.getCookies();
644+
assertFalse(cookies.isPresent());
645+
assertTrue(cookies.isEmpty());
646+
647+
verify(getRequestedFor(urlPathEqualTo(ENDPOINT))
648+
.withQueryParam(FIELD1_NAME, equalTo(FIELD1_DATA))
649+
.withQueryParam(FIELD2_NAME, equalTo(FIELD2_DATA))
650+
);
651+
}
652+
653+
@DisplayName("GetFromServer with no query parameters and return cookies in response")
654+
@Test
655+
void testGetFromServer_DocumentResponseWithCookies() throws Exception {
656+
// Given
657+
final String COOKIES_KEY = "Set-Cookie";
658+
final String COOKIES_VALUE = "cookie1=value1; cookie2=value2; HttpOnly";
659+
stubFor(get(ENDPOINT).willReturn(okForContentType(ContentType.TEXT_HTML.contentType(), MOCK_PDF_BYTES)
660+
.withHeader(COOKIES_KEY, COOKIES_VALUE)
661+
));
662+
663+
// When
664+
Response response = performGetFromServer().orElseThrow();
665+
666+
// Then
667+
Cookies cookies = response.getCookies();
668+
assertTrue(cookies.isPresent());
669+
assertFalse(cookies.isEmpty());
670+
671+
672+
verify(getRequestedFor(urlEqualTo(ENDPOINT))
673+
.withoutQueryParam(FIELD1_NAME)
674+
);
675+
}
676+
677+
@DisplayName("GetFromServer with no query parameters and return cookies in response")
678+
@Test
679+
void testGetFromServer_DocumentResponseWithCookies_GetWithCookies() throws Exception {
680+
// Given
681+
final String COOKIES_KEY = "Set-Cookie";
682+
final String COOKIE1_VALUE = "cookie1=value1; HttpOnly";
683+
final String COOKIE2_VALUE = "cookie2=value2; HttpOnly";
684+
final String GET_HEADER_KEY = "get";
685+
final String FIRST_GET_VALUE = "firstGet";
686+
final String SECOND_GET_VALUE = "secondGet";
687+
stubFor(get(ENDPOINT)
688+
.withHeader(GET_HEADER_KEY, equalTo(FIRST_GET_VALUE))
689+
.willReturn(okForContentType(ContentType.TEXT_HTML.contentType(), MOCK_PDF_BYTES)
690+
.withHeader(COOKIES_KEY, COOKIE1_VALUE)
691+
.withHeader(COOKIES_KEY, COOKIE2_VALUE)
692+
));
693+
stubFor(get(ENDPOINT)
694+
.withHeader(GET_HEADER_KEY, equalTo(SECOND_GET_VALUE))
695+
.willReturn(okForContentType(ContentType.TEXT_HTML.contentType(), MOCK_PDF_BYTES)));
696+
697+
// When
698+
Response response1 = underTest.getRequestBuilder()
699+
.addHeader(GET_HEADER_KEY, FIRST_GET_VALUE)
700+
.build()
701+
.getFromServer(ContentType.TEXT_HTML)
702+
.orElseThrow();
703+
704+
Response response2 = underTest.getRequestBuilder()
705+
.addHeader(GET_HEADER_KEY, SECOND_GET_VALUE)
706+
.addCookies(response1.getCookies())
707+
.build()
708+
.getFromServer(ContentType.TEXT_HTML)
709+
.orElseThrow();
710+
711+
// Then
712+
assertEquals(ContentType.TEXT_HTML, response1.contentType());
713+
assertEquals(MOCK_PDF_BYTES, new String(response1.data().readAllBytes()));
714+
assertEquals(ContentType.TEXT_HTML, response2.contentType());
715+
assertEquals(MOCK_PDF_BYTES, new String(response2.data().readAllBytes()));
716+
717+
718+
verify(getRequestedFor(urlEqualTo(ENDPOINT))
719+
.withoutQueryParam(FIELD1_NAME)
720+
.withHeader(GET_HEADER_KEY, equalTo(FIRST_GET_VALUE))
721+
);
722+
verify(getRequestedFor(urlEqualTo(ENDPOINT))
723+
.withoutQueryParam(FIELD1_NAME)
724+
.withHeader(GET_HEADER_KEY, equalTo(SECOND_GET_VALUE))
725+
.withCookie("cookie1", equalTo("value1"))
726+
.withCookie("cookie2", equalTo("value2"))
727+
);
728+
}
729+
581730
@DisplayName("GetFromServer with additional path parameter")
582731
@ParameterizedTest
583732
@ValueSource(strings = {"foo", "/foo"})

0 commit comments

Comments
 (0)