Skip to content

Commit 5a1c2b9

Browse files
committed
Introduce HttpClient and HttpClient.Builder
1 parent dfbcb62 commit 5a1c2b9

File tree

10 files changed

+718
-244
lines changed

10 files changed

+718
-244
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package io.avaje.http.client;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.avaje.inject.BeanScope;
5+
import io.avaje.jsonb.Jsonb;
6+
7+
import javax.net.ssl.SSLContext;
8+
import javax.net.ssl.SSLParameters;
9+
import java.net.Authenticator;
10+
import java.net.CookieHandler;
11+
import java.net.CookieManager;
12+
import java.net.ProxySelector;
13+
import java.time.Duration;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Optional;
17+
import java.util.concurrent.Executor;
18+
19+
import static java.util.Objects.requireNonNull;
20+
21+
abstract class DBaseBuilder {
22+
23+
java.net.http.HttpClient client;
24+
String baseUrl;
25+
boolean requestLogging = true;
26+
Duration requestTimeout = Duration.ofSeconds(20);
27+
BodyAdapter bodyAdapter;
28+
RetryHandler retryHandler;
29+
AuthTokenProvider authTokenProvider;
30+
31+
CookieHandler cookieHandler = new CookieManager();
32+
java.net.http.HttpClient.Redirect redirect = java.net.http.HttpClient.Redirect.NORMAL;
33+
java.net.http.HttpClient.Version version;
34+
Executor executor;
35+
ProxySelector proxy;
36+
SSLContext sslContext;
37+
SSLParameters sslParameters;
38+
Authenticator authenticator;
39+
int priority;
40+
41+
final List<RequestIntercept> interceptors = new ArrayList<>();
42+
final List<RequestListener> listeners = new ArrayList<>();
43+
44+
void configureFromScope(BeanScope beanScope) {
45+
if (bodyAdapter == null) {
46+
configureBodyAdapter(beanScope);
47+
}
48+
if (retryHandler == null) {
49+
configureRetryHandler(beanScope);
50+
}
51+
}
52+
53+
private void configureRetryHandler(BeanScope beanScope) {
54+
beanScope.getOptional(RetryHandler.class)
55+
.ifPresent(this::setRetryHandler);
56+
}
57+
58+
private void setRetryHandler(RetryHandler retryHandler) {
59+
this.retryHandler = retryHandler;
60+
}
61+
62+
private void configureBodyAdapter(BeanScope beanScope) {
63+
Optional<BodyAdapter> body = beanScope.getOptional(BodyAdapter.class);
64+
if (body.isPresent()) {
65+
bodyAdapter = body.get();
66+
} else if (beanScope.contains("io.avaje.jsonb.Jsonb")) {
67+
bodyAdapter = new JsonbBodyAdapter(beanScope.get(Jsonb.class));
68+
} else if (beanScope.contains("com.fasterxml.jackson.databind.ObjectMapper")) {
69+
ObjectMapper objectMapper = beanScope.get(ObjectMapper.class);
70+
bodyAdapter = new JacksonBodyAdapter(objectMapper);
71+
}
72+
}
73+
74+
private RequestListener buildListener() {
75+
if (listeners.isEmpty()) {
76+
return null;
77+
} else if (listeners.size() == 1) {
78+
return listeners.get(0);
79+
} else {
80+
return new DRequestListeners(listeners);
81+
}
82+
}
83+
84+
private RequestIntercept buildIntercept() {
85+
if (interceptors.isEmpty()) {
86+
return null;
87+
} else if (interceptors.size() == 1) {
88+
return interceptors.get(0);
89+
} else {
90+
return new DRequestInterceptors(interceptors);
91+
}
92+
}
93+
94+
private java.net.http.HttpClient defaultClient() {
95+
final java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder()
96+
.followRedirects(redirect)
97+
.connectTimeout(Duration.ofSeconds(20));
98+
if (cookieHandler != null) {
99+
builder.cookieHandler(cookieHandler);
100+
}
101+
if (version != null) {
102+
builder.version(version);
103+
}
104+
if (executor != null) {
105+
builder.executor(executor);
106+
}
107+
if (proxy != null) {
108+
builder.proxy(proxy);
109+
}
110+
if (sslContext != null) {
111+
builder.sslContext(sslContext);
112+
}
113+
if (sslParameters != null) {
114+
builder.sslParameters(sslParameters);
115+
}
116+
if (authenticator != null) {
117+
builder.authenticator(authenticator);
118+
}
119+
if (priority > 0) {
120+
builder.priority(priority);
121+
}
122+
return builder.build();
123+
}
124+
125+
/**
126+
* Create a reasonable default BodyAdapter if avaje-jsonb or Jackson are present.
127+
*/
128+
private BodyAdapter defaultBodyAdapter() {
129+
try {
130+
return detectJsonb() ? new JsonbBodyAdapter()
131+
: detectJackson() ? new JacksonBodyAdapter()
132+
: null;
133+
} catch (IllegalAccessError e) {
134+
// not in module path
135+
return null;
136+
}
137+
}
138+
139+
private boolean detectJsonb() {
140+
return detectTypeExists("io.avaje.jsonb.Jsonb");
141+
}
142+
143+
private boolean detectJackson() {
144+
return detectTypeExists("com.fasterxml.jackson.databind.ObjectMapper");
145+
}
146+
147+
private boolean detectTypeExists(String className) {
148+
try {
149+
Class.forName(className);
150+
return true;
151+
} catch (ClassNotFoundException e) {
152+
return false;
153+
}
154+
}
155+
156+
DHttpClientContext buildClient() {
157+
requireNonNull(baseUrl, "baseUrl is not specified");
158+
requireNonNull(requestTimeout, "requestTimeout is not specified");
159+
if (client == null) {
160+
client = defaultClient();
161+
}
162+
if (requestLogging) {
163+
// register the builtin request/response logging
164+
this.listeners.add(new RequestLogger());
165+
}
166+
if (bodyAdapter == null) {
167+
bodyAdapter = defaultBodyAdapter();
168+
}
169+
return new DHttpClientContext(client, baseUrl, requestTimeout, bodyAdapter, retryHandler, buildListener(), authTokenProvider, buildIntercept());
170+
}
171+
172+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package io.avaje.http.client;
2+
3+
import io.avaje.inject.BeanScope;
4+
5+
import javax.net.ssl.SSLContext;
6+
import javax.net.ssl.SSLParameters;
7+
import java.net.Authenticator;
8+
import java.net.CookieHandler;
9+
import java.net.ProxySelector;
10+
import java.time.Duration;
11+
import java.util.concurrent.Executor;
12+
13+
final class DHttpClientBuilder extends DBaseBuilder implements HttpClient.Builder, HttpClient.Builder.State {
14+
15+
DHttpClientBuilder() {
16+
}
17+
18+
@Override
19+
public HttpClient.Builder client(java.net.http.HttpClient client) {
20+
this.client = client;
21+
return this;
22+
}
23+
24+
@Override
25+
public HttpClient.Builder baseUrl(String baseUrl) {
26+
this.baseUrl = baseUrl;
27+
return this;
28+
}
29+
30+
@Override
31+
public HttpClient.Builder requestTimeout(Duration requestTimeout) {
32+
this.requestTimeout = requestTimeout;
33+
return this;
34+
}
35+
36+
@Override
37+
public HttpClient.Builder bodyAdapter(BodyAdapter adapter) {
38+
this.bodyAdapter = adapter;
39+
return this;
40+
}
41+
42+
@Override
43+
public HttpClient.Builder retryHandler(RetryHandler retryHandler) {
44+
this.retryHandler = retryHandler;
45+
return this;
46+
}
47+
48+
@Override
49+
public HttpClient.Builder requestLogging(boolean requestLogging) {
50+
this.requestLogging = requestLogging;
51+
return this;
52+
}
53+
54+
@Override
55+
public HttpClient.Builder requestListener(RequestListener requestListener) {
56+
this.listeners.add(requestListener);
57+
return this;
58+
}
59+
60+
@Override
61+
public HttpClient.Builder requestIntercept(RequestIntercept requestIntercept) {
62+
this.interceptors.add(requestIntercept);
63+
return this;
64+
}
65+
66+
@Override
67+
public HttpClient.Builder authTokenProvider(AuthTokenProvider authTokenProvider) {
68+
this.authTokenProvider = authTokenProvider;
69+
return this;
70+
}
71+
72+
@Override
73+
public HttpClient.Builder cookieHandler(CookieHandler cookieHandler) {
74+
this.cookieHandler = cookieHandler;
75+
return this;
76+
}
77+
78+
@Override
79+
public HttpClient.Builder redirect(java.net.http.HttpClient.Redirect redirect) {
80+
this.redirect = redirect;
81+
return this;
82+
}
83+
84+
@Override
85+
public HttpClient.Builder version(java.net.http.HttpClient.Version version) {
86+
this.version = version;
87+
return this;
88+
}
89+
90+
@Override
91+
public HttpClient.Builder executor(Executor executor) {
92+
this.executor = executor;
93+
return this;
94+
}
95+
96+
@Override
97+
public HttpClient.Builder proxy(ProxySelector proxySelector) {
98+
this.proxy = proxySelector;
99+
return this;
100+
}
101+
102+
@Override
103+
public HttpClient.Builder sslContext(SSLContext sslContext) {
104+
this.sslContext = sslContext;
105+
return this;
106+
}
107+
108+
@Override
109+
public HttpClient.Builder sslParameters(SSLParameters sslParameters) {
110+
this.sslParameters = sslParameters;
111+
return this;
112+
}
113+
114+
@Override
115+
public HttpClient.Builder authenticator(Authenticator authenticator) {
116+
this.authenticator = authenticator;
117+
return this;
118+
}
119+
120+
@Override
121+
public HttpClient.Builder priority(int priority) {
122+
this.priority = priority;
123+
return this;
124+
}
125+
126+
@Override
127+
public HttpClient.Builder.State state() {
128+
return this;
129+
}
130+
131+
@Override
132+
public HttpClient.Builder configureWith(BeanScope beanScope) {
133+
super.configureFromScope(beanScope);
134+
return this;
135+
}
136+
137+
@Override
138+
public HttpClient build() {
139+
return buildClient();
140+
}
141+
142+
@Override
143+
public String baseUrl() {
144+
return baseUrl;
145+
}
146+
147+
@Override
148+
public BodyAdapter bodyAdapter() {
149+
return bodyAdapter;
150+
}
151+
152+
@Override
153+
public java.net.http.HttpClient client() {
154+
return client;
155+
}
156+
157+
@Override
158+
public boolean requestLogging() {
159+
return requestLogging;
160+
}
161+
162+
@Override
163+
public Duration requestTimeout() {
164+
return requestTimeout;
165+
}
166+
167+
@Override
168+
public RetryHandler retryHandler() {
169+
return retryHandler;
170+
}
171+
172+
}

client/src/main/java/io/avaje/http/client/DHttpClientContext.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import java.util.concurrent.atomic.LongAccumulator;
1717
import java.util.concurrent.atomic.LongAdder;
1818

19-
final class DHttpClientContext implements HttpClientContext {
19+
final class DHttpClientContext implements HttpClientContext, SpiHttpClient {
2020

2121
/**
2222
* HTTP Authorization header.

0 commit comments

Comments
 (0)