Skip to content

Commit

Permalink
feat: Add possibility to add default headers to request.
Browse files Browse the repository at this point in the history
  • Loading branch information
nstdio committed Sep 3, 2022
1 parent 5a8cb08 commit 0dd3ffc
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
import java.util.function.Consumer;
import java.util.stream.Stream;

import static io.github.nstdio.http.ext.ExtendedHttpClient.toBuilder;
import static io.github.nstdio.http.ext.Headers.HEADER_IF_MODIFIED_SINCE;
import static io.github.nstdio.http.ext.Headers.HEADER_IF_NONE_MATCH;
import static io.github.nstdio.http.ext.HttpRequests.toBuilder;
import static io.github.nstdio.http.ext.Responses.gatewayTimeoutResponse;
import static io.github.nstdio.http.ext.Responses.isSafeRequest;
import static io.github.nstdio.http.ext.Responses.isSuccessful;
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/Chain.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package io.github.nstdio.http.ext;

import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;

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

Chain<T> withRequest(HttpRequest request) {
return of(ctx.withRequest(request), futureHandler, response);
}

RequestContext ctx() {
return this.ctx;
}
Expand All @@ -63,4 +68,8 @@ FutureHandler<T> futureHandler() {
Optional<HttpResponse<T>> response() {
return this.response;
}

HttpRequest request() {
return ctx.request();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import java.util.List;
import java.util.Optional;

import static io.github.nstdio.http.ext.ExtendedHttpClient.toBuilder;
import static io.github.nstdio.http.ext.Headers.HEADER_CONTENT_ENCODING;
import static io.github.nstdio.http.ext.Headers.HEADER_CONTENT_LENGTH;
import static io.github.nstdio.http.ext.HttpRequests.toBuilder;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;

Expand Down Expand Up @@ -64,10 +64,9 @@ private HttpRequest preProcessRequest(HttpRequest request) {
return request;
}

HttpRequest.Builder builder = toBuilder(request);
builder.setHeader("Accept-Encoding", supported);

return builder.build();
return toBuilder(request)
.setHeader("Accept-Encoding", supported)
.build();
}

private <T> DecompressingBodyHandler<T> decompressingHandler(HttpResponse.BodyHandler<T> bodyHandler) {
Expand Down
97 changes: 75 additions & 22 deletions src/main/java/io/github/nstdio/http/ext/ExtendedHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,50 @@
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.PushPromiseHandler;
import java.net.http.WebSocket;
import java.time.Clock;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Supplier;

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

public class ExtendedHttpClient extends HttpClient {
private final CompressionInterceptor compressionInterceptor;
private final CachingInterceptor cachingInterceptor;
private final HeadersAddingInterceptor headersAddingInterceptor;

private final HttpClient delegate;
private final boolean allowInsecure;

ExtendedHttpClient(HttpClient delegate, Cache cache, Clock clock) {
this(delegate, cache, false, true, clock);
this(
null,
cache instanceof NullCache ? null : new CachingInterceptor(cache, clock),
null,
delegate,
true
);
}

ExtendedHttpClient(HttpClient delegate, Cache cache, boolean transparentEncoding, boolean allowInsecure, Clock clock) {
private ExtendedHttpClient(CompressionInterceptor compressionInterceptor,
CachingInterceptor cachingInterceptor,
HeadersAddingInterceptor headersAddingInterceptor,
HttpClient delegate, boolean allowInsecure) {
this.compressionInterceptor = compressionInterceptor;
this.cachingInterceptor = cachingInterceptor;
this.headersAddingInterceptor = headersAddingInterceptor;
this.delegate = delegate;
this.cachingInterceptor = cache instanceof NullCache ? null : new CachingInterceptor(cache, clock);
this.compressionInterceptor = transparentEncoding ? new CompressionInterceptor() : null;
this.allowInsecure = allowInsecure;
}

Expand All @@ -80,20 +93,6 @@ public static ExtendedHttpClient newHttpClient() {
.build();
}

static HttpRequest.Builder toBuilder(HttpRequest r) {
var builder = HttpRequest.newBuilder();
builder
.uri(r.uri())
.method(r.method(), r.bodyPublisher().orElseGet(BodyPublishers::noBody))
.expectContinue(r.expectContinue());

r.version().ifPresent(builder::version);
r.timeout().ifPresent(builder::timeout);
r.headers().map().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));

return builder;
}

//<editor-fold desc="Delegate Methods">
@Override
public Optional<CookieHandler> cookieHandler() {
Expand Down Expand Up @@ -186,12 +185,17 @@ private void checkInsecureScheme(HttpRequest request) {

private <T> Chain<T> buildAndExecute(RequestContext ctx) {
Chain<T> chain = Chain.of(ctx);
chain = compressionInterceptor != null ? compressionInterceptor.intercept(chain) : chain;
chain = cachingInterceptor != null ? cachingInterceptor.intercept(chain) : chain;
chain = possiblyApply(compressionInterceptor, chain);
chain = possiblyApply(cachingInterceptor, chain);
chain = possiblyApply(headersAddingInterceptor, chain);

return chain;
}

private <T> Chain<T> possiblyApply(Interceptor i, Chain<T> c) {
return i != null ? i.intercept(c) : c;
}

/**
* The {@code future} DOES NOT represent ongoing computation it's always either completed or failed.
*/
Expand Down Expand Up @@ -239,6 +243,8 @@ public static class Builder implements HttpClient.Builder {
private boolean transparentEncoding;
private boolean allowInsecure = true;
private Cache cache = Cache.noop();
private Map<String, String> headers = Map.of();
private Map<String, Supplier<String>> resolvableHeaders = Map.of();

Builder(HttpClient.Builder delegate) {
this.delegate = delegate;
Expand Down Expand Up @@ -376,11 +382,58 @@ public Builder allowInsecure(boolean allowInsecure) {
return this;
}

/**
* Provided header will be included on each request.
*
* @param name The header name.
* @param value The header value.
*
* @return builder itself.
*/
public Builder defaultHeader(String name, String value) {
Objects.requireNonNull(name);
Objects.requireNonNull(value);

if (headers.isEmpty()) {
headers = new HashMap<>(1);
}
headers.put(name, value);

return this;
}

/**
* Provided header will be included on each request. Note that {@code valueSupplier} will be resolved before each
* request.
*
* @param name The header name.
* @param valueSupplier The header value supplier.
*
* @return builder itself.
*/
public Builder defaultHeader(String name, Supplier<String> valueSupplier) {
Objects.requireNonNull(name);
Objects.requireNonNull(valueSupplier);

if (resolvableHeaders.isEmpty()) {
resolvableHeaders = new HashMap<>(1);
}
resolvableHeaders.put(name, valueSupplier);

return this;
}

@Override
public ExtendedHttpClient build() {
HttpClient client = delegate.build();

return new ExtendedHttpClient(client, cache, transparentEncoding, allowInsecure, Clock.systemUTC());
return new ExtendedHttpClient(
transparentEncoding ? new CompressionInterceptor() : null,
cache instanceof NullCache ? null : new CachingInterceptor(cache, Clock.systemUTC()),
new HeadersAddingInterceptor(Map.copyOf(headers), Map.copyOf(resolvableHeaders)),
client,
allowInsecure
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 Edgar Asatryan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.github.nstdio.http.ext;

import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

class HeadersAddingInterceptor implements Interceptor {
private final Map<String, String> headers;
private final Map<String, Supplier<String>> resolvableHeaders;

HeadersAddingInterceptor(Map<String, String> headers, Map<String, Supplier<String>> resolvableHeaders) {
this.headers = headers;
this.resolvableHeaders = resolvableHeaders;
}

@Override
public <T> Chain<T> intercept(Chain<T> in) {
if (in.response().isPresent() || !hasHeaders()) {
return in;
}

return in.withRequest(apply(in.request()));
}

private HttpRequest apply(HttpRequest request) {
var headers = addHeaders(request.headers());

var builder = HttpRequests.toBuilderOmitHeaders(request);
headers.forEach((name, values) -> values.forEach(v -> builder.header(name, v)));

return builder.build();
}

private Map<String, List<String>> addHeaders(HttpHeaders h) {
var headersBuilder = new HttpHeadersBuilder(h);

headers.forEach(headersBuilder::add);
resolvableHeaders.forEach((name, valueSupplier) -> headersBuilder.add(name, valueSupplier.get()));

return headersBuilder.map();
}

private boolean hasHeaders() {
return !headers.isEmpty() || !resolvableHeaders.isEmpty();
}
}
10 changes: 8 additions & 2 deletions src/main/java/io/github/nstdio/http/ext/HttpHeadersBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

import java.net.http.HttpHeaders;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiPredicate;

import static io.github.nstdio.http.ext.Headers.ALLOW_ALL;

class HttpHeadersBuilder {
private static final BiPredicate<String, String> ALWAYS_ALLOW = (s, s2) -> true;
private final TreeMap<String, List<String>> headersMap;

HttpHeadersBuilder() {
Expand Down Expand Up @@ -92,8 +94,12 @@ HttpHeadersBuilder remove(String name) {
return this;
}

Map<String, List<String>> map() {
return Collections.unmodifiableMap(headersMap);
}

HttpHeaders build() {
return build(ALWAYS_ALLOW);
return build(ALLOW_ALL);
}

HttpHeaders build(BiPredicate<String, String> filter) {
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/io/github/nstdio/http/ext/HttpRequests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 Edgar Asatryan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.github.nstdio.http.ext;

import java.net.http.HttpRequest;

class HttpRequests {
static HttpRequest.Builder toBuilderOmitHeaders(HttpRequest r) {
var builder = HttpRequest.newBuilder();
builder
.uri(r.uri())
.method(r.method(), r.bodyPublisher().orElseGet(HttpRequest.BodyPublishers::noBody))
.expectContinue(r.expectContinue());

r.version().ifPresent(builder::version);
r.timeout().ifPresent(builder::timeout);

return builder;
}

static HttpRequest.Builder toBuilder(HttpRequest r) {
var builder = toBuilderOmitHeaders(r);
r.headers().map().forEach((name, values) -> values.forEach(value -> builder.header(name, value)));

return builder;
}
}
Loading

0 comments on commit 0dd3ffc

Please sign in to comment.