Skip to content

Commit 3d32302

Browse files
authored
Merge pull request #169 from SentryMan/client
HttpClientRequest Now Supports Multi-Value Headers/Query Parameters
2 parents 6efb809 + 0e29dd0 commit 3d32302

File tree

7 files changed

+92
-12
lines changed

7 files changed

+92
-12
lines changed

http-client/src/main/java/io/avaje/http/client/DHttpClientRequest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.*;
1717
import java.util.concurrent.CompletableFuture;
1818
import java.util.function.Supplier;
19+
import java.util.stream.Collectors;
1920
import java.util.stream.Stream;
2021

2122
import static java.net.http.HttpResponse.BodyHandlers.discarding;
@@ -124,7 +125,25 @@ public HttpClientRequest header(String name, String value) {
124125
}
125126

126127
@Override
128+
public HttpClientRequest header(String name, Collection<String> value) {
129+
if (headers == null) {
130+
headers = new LinkedHashMap<>();
131+
}
132+
headers.computeIfAbsent(name, s -> new ArrayList<>()).addAll(value);
133+
return this;
134+
}
135+
136+
@Override
137+
@SuppressWarnings("unchecked")
127138
public HttpClientRequest header(String name, Object value) {
139+
140+
if (value instanceof Collection) {
141+
final var headerList =
142+
((Collection<Object>) value).stream().map(Object::toString).collect(Collectors.toList());
143+
144+
return header(name, headerList);
145+
}
146+
128147
return value != null ? header(name, value.toString()) : this;
129148
}
130149

http-client/src/main/java/io/avaje/http/client/HttpClientRequest.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.net.http.HttpResponse;
66
import java.nio.file.Path;
77
import java.time.Duration;
8+
import java.util.Collection;
89
import java.util.List;
910
import java.util.Map;
1011
import java.util.function.Supplier;
@@ -109,9 +110,10 @@ public interface HttpClientRequest {
109110
HttpClientRequest header(String name, String value);
110111

111112
/**
112-
* Add the header to the request implicitly converting the value to a String.
113+
* Add the header to the request implicitly converting the value to a String. If the value is a
114+
* collection then it's values are appended with the same key
113115
*
114-
* @param name The header name
116+
* @param name The header name
115117
* @param value The header value
116118
* @return The request being built
117119
*/
@@ -125,6 +127,15 @@ public interface HttpClientRequest {
125127
*/
126128
HttpClientRequest header(Map<String, ?> headers);
127129

130+
/**
131+
* Add the headers to the request via Collection.
132+
*
133+
* @param name The header name
134+
* @param value The header values
135+
* @return The request being built
136+
*/
137+
HttpClientRequest header(String name, Collection<String> value);
138+
128139
/**
129140
* Return the header values that have been set for the given header name.
130141
*
@@ -218,7 +229,7 @@ public interface HttpClientRequest {
218229
HttpClientRequest queryParam(String name, String value);
219230

220231
/**
221-
* Add a query parameter
232+
* Add a query parameter, if value is a collection then it's values are appended with the same key
222233
*
223234
* @param name The name of the query parameter
224235
* @param value The value of the query parameter which can be null
@@ -234,6 +245,17 @@ public interface HttpClientRequest {
234245
*/
235246
HttpClientRequest queryParam(Map<String, ?> params);
236247

248+
/**
249+
* Add a query parameter with multiple values
250+
*
251+
* @param name The name of the query parameter
252+
* @param value The values of the query parameter which can be null
253+
* @return The request being built
254+
*/
255+
default HttpClientRequest queryParam(String name, Collection<String> values) {
256+
return queryParam(name, (Object) values);
257+
}
258+
237259
/**
238260
* Add a form parameter.
239261
*

http-client/src/main/java/io/avaje/http/client/UrlBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.net.URLEncoder;
44
import java.nio.charset.StandardCharsets;
5+
import java.util.Collection;
56
import java.util.Map;
67

78
/**
@@ -84,6 +85,14 @@ public UrlBuilder queryParam(String name, String value) {
8485
* The name and value parameters are url encoded.
8586
*/
8687
public UrlBuilder queryParam(String name, Object value) {
88+
89+
if (value instanceof Collection) {
90+
for (var e : (Collection) value) {
91+
queryParam(name, e);
92+
}
93+
return this;
94+
}
95+
8796
if (value != null) {
8897
addQueryParam(name, value.toString());
8998
}

http-client/src/test/java/io/avaje/http/client/DHttpClientRequestTest.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package io.avaje.http.client;
22

3-
import org.junit.jupiter.api.Test;
3+
import static org.assertj.core.api.Assertions.assertThat;
44

55
import java.time.Duration;
6+
import java.util.List;
67

7-
import static org.assertj.core.api.Assertions.assertThat;
8+
import org.junit.jupiter.api.Test;
89

910
class DHttpClientRequestTest {
1011

@@ -21,6 +22,35 @@ void suppressLogging_listenerEvent_expect_suppressedPayloadContent() {
2122
assertThat(event.responseBody()).isEqualTo("<suppressed response body>");
2223
}
2324

25+
@Test
26+
void assertHeader() {
27+
final var request = new DHttpClientRequest(context, Duration.ZERO);
28+
29+
final var headers =
30+
request
31+
.header("Accept", (Object) List.of("application/json", "application/json2"))
32+
.header("Accept");
33+
34+
assertThat(headers).asList().contains("application/json", "application/json2");
35+
}
36+
37+
@Test
38+
void assertQuery() {
39+
final var client = HttpClient.builder().baseUrl("https://ap7i.github.com").build();
40+
41+
final var uri =
42+
client
43+
.request()
44+
.queryParam("param", List.of("param1", "param2"))
45+
.HEAD()
46+
.asDiscarding()
47+
.request()
48+
.uri()
49+
.toString();
50+
51+
assertThat(uri).isEqualTo("https://ap7i.github.com?param=param1&param=param2");
52+
}
53+
2454
@Test
2555
void skipAuthToken_listenerEvent_expect_suppressedPayloadContent() {
2656
final DHttpClientRequest request = new DHttpClientRequest(context, Duration.ZERO);

http-client/src/test/java/io/avaje/http/client/HelloControllerTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ void async_list_as() throws ExecutionException, InterruptedException {
801801
void get_withPathParamAndQueryParam_returningBean() {
802802

803803
final HelloDto dto = clientContext.request()
804-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
804+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (Object) null)
805805
.GET()
806806
.bean(HelloDto.class);
807807

@@ -813,7 +813,7 @@ void get_withPathParamAndQueryParam_returningBean() {
813813
@Test
814814
void callBean() {
815815
final HelloDto dto = clientContext.request()
816-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
816+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (Object) null)
817817
.GET()
818818
.call().bean(HelloDto.class).execute();
819819

@@ -825,7 +825,7 @@ void callBean() {
825825
@Test
826826
void callBeanAsync() throws ExecutionException, InterruptedException {
827827
final CompletableFuture<HelloDto> future = clientContext.request()
828-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
828+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (String) null)
829829
.GET()
830830
.call().bean(HelloDto.class).async();
831831

@@ -842,7 +842,7 @@ void async_whenComplete_returningBean() throws ExecutionException, InterruptedEx
842842
final AtomicReference<HelloDto> ref = new AtomicReference<>();
843843

844844
final CompletableFuture<HelloDto> future = clientContext.request()
845-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
845+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (String) null)
846846
.GET()
847847
.async().bean(HelloDto.class);
848848

@@ -872,7 +872,7 @@ void async_whenComplete_returningBeanWithHeaders() throws ExecutionException, In
872872
final AtomicReference<HttpResponse<HelloDto>> ref = new AtomicReference<>();
873873

874874
final CompletableFuture<HttpResponse<HelloDto>> future = clientContext.request()
875-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
875+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (String) null)
876876
.GET()
877877
.async().as(HelloDto.class);
878878

tests/test-javalin-jsonb/src/test/java/org/example/myapp/HelloControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ void getWithPathParamAndQueryParam() {
9696
assertThat(bean.otherParam).isEqualTo("other");
9797

9898
final HelloDto dto = client.request()
99-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
99+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (String) null)
100100
.GET().bean(HelloDto.class);
101101

102102
assertThat(dto.id).isEqualTo(43L);

tests/test-javalin/src/test/java/org/example/myapp/HelloControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ void getWithPathParamAndQueryParam() {
9898
assertThat(bean.otherParam).isEqualTo("other");
9999

100100
final HelloDto dto = client.request()
101-
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", null)
101+
.path("hello/43/2020-03-05").queryParam("otherParam", "other").queryParam("foo", (String) null)
102102
.GET().bean(HelloDto.class);
103103

104104
assertThat(dto.id).isEqualTo(43L);

0 commit comments

Comments
 (0)