Skip to content

Commit 8a75233

Browse files
authored
Merge pull request #86 from nstdio/default-headers
feat: Add possibility to add default headers to request.
2 parents 5a8cb08 + 0dd3ffc commit 8a75233

File tree

9 files changed

+374
-35
lines changed

9 files changed

+374
-35
lines changed

src/main/java/io/github/nstdio/http/ext/CachingInterceptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
import java.util.function.Consumer;
3030
import java.util.stream.Stream;
3131

32-
import static io.github.nstdio.http.ext.ExtendedHttpClient.toBuilder;
3332
import static io.github.nstdio.http.ext.Headers.HEADER_IF_MODIFIED_SINCE;
3433
import static io.github.nstdio.http.ext.Headers.HEADER_IF_NONE_MATCH;
34+
import static io.github.nstdio.http.ext.HttpRequests.toBuilder;
3535
import static io.github.nstdio.http.ext.Responses.gatewayTimeoutResponse;
3636
import static io.github.nstdio.http.ext.Responses.isSafeRequest;
3737
import static io.github.nstdio.http.ext.Responses.isSuccessful;

src/main/java/io/github/nstdio/http/ext/Chain.java

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

1717
package io.github.nstdio.http.ext;
1818

19+
import java.net.http.HttpRequest;
1920
import java.net.http.HttpResponse;
2021
import java.util.Optional;
2122

@@ -52,6 +53,10 @@ Chain<T> withResponse(HttpResponse<T> response) {
5253
return of(ctx, futureHandler, Optional.of(response));
5354
}
5455

56+
Chain<T> withRequest(HttpRequest request) {
57+
return of(ctx.withRequest(request), futureHandler, response);
58+
}
59+
5560
RequestContext ctx() {
5661
return this.ctx;
5762
}
@@ -63,4 +68,8 @@ FutureHandler<T> futureHandler() {
6368
Optional<HttpResponse<T>> response() {
6469
return this.response;
6570
}
71+
72+
HttpRequest request() {
73+
return ctx.request();
74+
}
6675
}

src/main/java/io/github/nstdio/http/ext/CompressionInterceptor.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
import java.util.List;
2323
import java.util.Optional;
2424

25-
import static io.github.nstdio.http.ext.ExtendedHttpClient.toBuilder;
2625
import static io.github.nstdio.http.ext.Headers.HEADER_CONTENT_ENCODING;
2726
import static io.github.nstdio.http.ext.Headers.HEADER_CONTENT_LENGTH;
27+
import static io.github.nstdio.http.ext.HttpRequests.toBuilder;
2828
import static java.util.function.Predicate.not;
2929
import static java.util.stream.Collectors.joining;
3030

@@ -64,10 +64,9 @@ private HttpRequest preProcessRequest(HttpRequest request) {
6464
return request;
6565
}
6666

67-
HttpRequest.Builder builder = toBuilder(request);
68-
builder.setHeader("Accept-Encoding", supported);
69-
70-
return builder.build();
67+
return toBuilder(request)
68+
.setHeader("Accept-Encoding", supported)
69+
.build();
7170
}
7271

7372
private <T> DecompressingBodyHandler<T> decompressingHandler(HttpResponse.BodyHandler<T> bodyHandler) {

src/main/java/io/github/nstdio/http/ext/ExtendedHttpClient.java

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,50 @@
2525
import java.net.URI;
2626
import java.net.http.HttpClient;
2727
import java.net.http.HttpRequest;
28-
import java.net.http.HttpRequest.BodyPublishers;
2928
import java.net.http.HttpResponse;
3029
import java.net.http.HttpResponse.BodyHandler;
3130
import java.net.http.HttpResponse.PushPromiseHandler;
3231
import java.net.http.WebSocket;
3332
import java.time.Clock;
3433
import java.time.Duration;
34+
import java.util.HashMap;
35+
import java.util.Map;
3536
import java.util.Objects;
3637
import java.util.Optional;
3738
import java.util.concurrent.CompletableFuture;
3839
import java.util.concurrent.CompletionException;
3940
import java.util.concurrent.Executor;
4041
import java.util.function.Function;
42+
import java.util.function.Supplier;
4143

4244
import static java.util.concurrent.CompletableFuture.completedFuture;
4345

4446
public class ExtendedHttpClient extends HttpClient {
4547
private final CompressionInterceptor compressionInterceptor;
4648
private final CachingInterceptor cachingInterceptor;
49+
private final HeadersAddingInterceptor headersAddingInterceptor;
4750

4851
private final HttpClient delegate;
4952
private final boolean allowInsecure;
5053

5154
ExtendedHttpClient(HttpClient delegate, Cache cache, Clock clock) {
52-
this(delegate, cache, false, true, clock);
55+
this(
56+
null,
57+
cache instanceof NullCache ? null : new CachingInterceptor(cache, clock),
58+
null,
59+
delegate,
60+
true
61+
);
5362
}
5463

55-
ExtendedHttpClient(HttpClient delegate, Cache cache, boolean transparentEncoding, boolean allowInsecure, Clock clock) {
64+
private ExtendedHttpClient(CompressionInterceptor compressionInterceptor,
65+
CachingInterceptor cachingInterceptor,
66+
HeadersAddingInterceptor headersAddingInterceptor,
67+
HttpClient delegate, boolean allowInsecure) {
68+
this.compressionInterceptor = compressionInterceptor;
69+
this.cachingInterceptor = cachingInterceptor;
70+
this.headersAddingInterceptor = headersAddingInterceptor;
5671
this.delegate = delegate;
57-
this.cachingInterceptor = cache instanceof NullCache ? null : new CachingInterceptor(cache, clock);
58-
this.compressionInterceptor = transparentEncoding ? new CompressionInterceptor() : null;
5972
this.allowInsecure = allowInsecure;
6073
}
6174

@@ -80,20 +93,6 @@ public static ExtendedHttpClient newHttpClient() {
8093
.build();
8194
}
8295

83-
static HttpRequest.Builder toBuilder(HttpRequest r) {
84-
var builder = HttpRequest.newBuilder();
85-
builder
86-
.uri(r.uri())
87-
.method(r.method(), r.bodyPublisher().orElseGet(BodyPublishers::noBody))
88-
.expectContinue(r.expectContinue());
89-
90-
r.version().ifPresent(builder::version);
91-
r.timeout().ifPresent(builder::timeout);
92-
r.headers().map().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));
93-
94-
return builder;
95-
}
96-
9796
//<editor-fold desc="Delegate Methods">
9897
@Override
9998
public Optional<CookieHandler> cookieHandler() {
@@ -186,12 +185,17 @@ private void checkInsecureScheme(HttpRequest request) {
186185

187186
private <T> Chain<T> buildAndExecute(RequestContext ctx) {
188187
Chain<T> chain = Chain.of(ctx);
189-
chain = compressionInterceptor != null ? compressionInterceptor.intercept(chain) : chain;
190-
chain = cachingInterceptor != null ? cachingInterceptor.intercept(chain) : chain;
188+
chain = possiblyApply(compressionInterceptor, chain);
189+
chain = possiblyApply(cachingInterceptor, chain);
190+
chain = possiblyApply(headersAddingInterceptor, chain);
191191

192192
return chain;
193193
}
194194

195+
private <T> Chain<T> possiblyApply(Interceptor i, Chain<T> c) {
196+
return i != null ? i.intercept(c) : c;
197+
}
198+
195199
/**
196200
* The {@code future} DOES NOT represent ongoing computation it's always either completed or failed.
197201
*/
@@ -239,6 +243,8 @@ public static class Builder implements HttpClient.Builder {
239243
private boolean transparentEncoding;
240244
private boolean allowInsecure = true;
241245
private Cache cache = Cache.noop();
246+
private Map<String, String> headers = Map.of();
247+
private Map<String, Supplier<String>> resolvableHeaders = Map.of();
242248

243249
Builder(HttpClient.Builder delegate) {
244250
this.delegate = delegate;
@@ -376,11 +382,58 @@ public Builder allowInsecure(boolean allowInsecure) {
376382
return this;
377383
}
378384

385+
/**
386+
* Provided header will be included on each request.
387+
*
388+
* @param name The header name.
389+
* @param value The header value.
390+
*
391+
* @return builder itself.
392+
*/
393+
public Builder defaultHeader(String name, String value) {
394+
Objects.requireNonNull(name);
395+
Objects.requireNonNull(value);
396+
397+
if (headers.isEmpty()) {
398+
headers = new HashMap<>(1);
399+
}
400+
headers.put(name, value);
401+
402+
return this;
403+
}
404+
405+
/**
406+
* Provided header will be included on each request. Note that {@code valueSupplier} will be resolved before each
407+
* request.
408+
*
409+
* @param name The header name.
410+
* @param valueSupplier The header value supplier.
411+
*
412+
* @return builder itself.
413+
*/
414+
public Builder defaultHeader(String name, Supplier<String> valueSupplier) {
415+
Objects.requireNonNull(name);
416+
Objects.requireNonNull(valueSupplier);
417+
418+
if (resolvableHeaders.isEmpty()) {
419+
resolvableHeaders = new HashMap<>(1);
420+
}
421+
resolvableHeaders.put(name, valueSupplier);
422+
423+
return this;
424+
}
425+
379426
@Override
380427
public ExtendedHttpClient build() {
381428
HttpClient client = delegate.build();
382429

383-
return new ExtendedHttpClient(client, cache, transparentEncoding, allowInsecure, Clock.systemUTC());
430+
return new ExtendedHttpClient(
431+
transparentEncoding ? new CompressionInterceptor() : null,
432+
cache instanceof NullCache ? null : new CachingInterceptor(cache, Clock.systemUTC()),
433+
new HeadersAddingInterceptor(Map.copyOf(headers), Map.copyOf(resolvableHeaders)),
434+
client,
435+
allowInsecure
436+
);
384437
}
385438
}
386439
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (C) 2022 Edgar Asatryan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.github.nstdio.http.ext;
18+
19+
import java.net.http.HttpHeaders;
20+
import java.net.http.HttpRequest;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.function.Supplier;
24+
25+
class HeadersAddingInterceptor implements Interceptor {
26+
private final Map<String, String> headers;
27+
private final Map<String, Supplier<String>> resolvableHeaders;
28+
29+
HeadersAddingInterceptor(Map<String, String> headers, Map<String, Supplier<String>> resolvableHeaders) {
30+
this.headers = headers;
31+
this.resolvableHeaders = resolvableHeaders;
32+
}
33+
34+
@Override
35+
public <T> Chain<T> intercept(Chain<T> in) {
36+
if (in.response().isPresent() || !hasHeaders()) {
37+
return in;
38+
}
39+
40+
return in.withRequest(apply(in.request()));
41+
}
42+
43+
private HttpRequest apply(HttpRequest request) {
44+
var headers = addHeaders(request.headers());
45+
46+
var builder = HttpRequests.toBuilderOmitHeaders(request);
47+
headers.forEach((name, values) -> values.forEach(v -> builder.header(name, v)));
48+
49+
return builder.build();
50+
}
51+
52+
private Map<String, List<String>> addHeaders(HttpHeaders h) {
53+
var headersBuilder = new HttpHeadersBuilder(h);
54+
55+
headers.forEach(headersBuilder::add);
56+
resolvableHeaders.forEach((name, valueSupplier) -> headersBuilder.add(name, valueSupplier.get()));
57+
58+
return headersBuilder.map();
59+
}
60+
61+
private boolean hasHeaders() {
62+
return !headers.isEmpty() || !resolvableHeaders.isEmpty();
63+
}
64+
}

src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
import java.net.http.HttpHeaders;
1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Map;
2223
import java.util.TreeMap;
2324
import java.util.function.BiPredicate;
2425

26+
import static io.github.nstdio.http.ext.Headers.ALLOW_ALL;
27+
2528
class HttpHeadersBuilder {
26-
private static final BiPredicate<String, String> ALWAYS_ALLOW = (s, s2) -> true;
2729
private final TreeMap<String, List<String>> headersMap;
2830

2931
HttpHeadersBuilder() {
@@ -92,8 +94,12 @@ HttpHeadersBuilder remove(String name) {
9294
return this;
9395
}
9496

97+
Map<String, List<String>> map() {
98+
return Collections.unmodifiableMap(headersMap);
99+
}
100+
95101
HttpHeaders build() {
96-
return build(ALWAYS_ALLOW);
102+
return build(ALLOW_ALL);
97103
}
98104

99105
HttpHeaders build(BiPredicate<String, String> filter) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright (C) 2022 Edgar Asatryan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.github.nstdio.http.ext;
18+
19+
import java.net.http.HttpRequest;
20+
21+
class HttpRequests {
22+
static HttpRequest.Builder toBuilderOmitHeaders(HttpRequest r) {
23+
var builder = HttpRequest.newBuilder();
24+
builder
25+
.uri(r.uri())
26+
.method(r.method(), r.bodyPublisher().orElseGet(HttpRequest.BodyPublishers::noBody))
27+
.expectContinue(r.expectContinue());
28+
29+
r.version().ifPresent(builder::version);
30+
r.timeout().ifPresent(builder::timeout);
31+
32+
return builder;
33+
}
34+
35+
static HttpRequest.Builder toBuilder(HttpRequest r) {
36+
var builder = toBuilderOmitHeaders(r);
37+
r.headers().map().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));
38+
39+
return builder;
40+
}
41+
}

0 commit comments

Comments
 (0)