Skip to content

Commit b55a3d1

Browse files
authored
WebClient services for Níma client (HTTP/1 only for now). (helidon-io#6752)
* WebClient services for Níma client (HTTP/1 only for now). * WebClientTracing implementation.
1 parent 58666cd commit b55a3d1

40 files changed

Lines changed: 2051 additions & 534 deletions

File tree

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,11 @@
12881288
<artifactId>helidon-nima-webclient</artifactId>
12891289
<version>${helidon.version}</version>
12901290
</dependency>
1291+
<dependency>
1292+
<groupId>io.helidon.nima.webclient</groupId>
1293+
<artifactId>helidon-nima-webclient-tracing</artifactId>
1294+
<version>${helidon.version}</version>
1295+
</dependency>
12911296
<dependency>
12921297
<groupId>io.helidon.nima.http2</groupId>
12931298
<artifactId>helidon-nima-http2</artifactId>

common/common/src/main/java/io/helidon/common/HelidonServiceLoader.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2019, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.util.Set;
2525
import java.util.function.Consumer;
2626
import java.util.stream.Collectors;
27+
import java.util.stream.Stream;
2728

2829
import static java.lang.System.Logger.Level.TRACE;
2930

@@ -131,6 +132,15 @@ public List<T> asList() {
131132
return new LinkedList<>(this.services);
132133
}
133134

135+
/**
136+
* Provides a stream of service implementations, in weighted order.
137+
*
138+
* @return stream os service implementations
139+
*/
140+
public Stream<T> stream() {
141+
return asList().stream();
142+
}
143+
134144
/**
135145
* Fluent api builder for {@link io.helidon.common.HelidonServiceLoader}.
136146
*

common/http/src/main/java/io/helidon/common/http/Headers.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.OptionalLong;
2424
import java.util.function.Supplier;
2525
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
import java.util.stream.StreamSupport;
2628

2729
import io.helidon.common.media.type.MediaType;
2830

@@ -227,4 +229,13 @@ default Map<String, List<String>> toMap() {
227229

228230
return headers;
229231
}
232+
233+
/**
234+
* A sequential stream with these headers as the source.
235+
*
236+
* @return stream of header values
237+
*/
238+
default Stream<Http.HeaderValue> stream() {
239+
return StreamSupport.stream(spliterator(), false);
240+
}
230241
}

common/http/src/main/java/io/helidon/common/http/Http.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,9 @@ public static HeaderValue createCached(HeaderName name, int value) {
14971497
* @see #create(io.helidon.common.http.Http.HeaderName, boolean, boolean, String...)
14981498
*/
14991499
public static HeaderValue create(HeaderName name, LazyString value) {
1500+
Objects.requireNonNull(name);
1501+
Objects.requireNonNull(value);
1502+
15001503
return new HeaderValueLazy(name, false, false, value);
15011504
}
15021505

@@ -1509,6 +1512,8 @@ public static HeaderValue create(HeaderName name, LazyString value) {
15091512
* @see #create(io.helidon.common.http.Http.HeaderName, boolean, boolean, String...)
15101513
*/
15111514
public static HeaderValue create(HeaderName name, int value) {
1515+
Objects.requireNonNull(name);
1516+
15121517
return new HeaderValueSingle(name, false, false, String.valueOf(value));
15131518
}
15141519

@@ -1521,6 +1526,8 @@ public static HeaderValue create(HeaderName name, int value) {
15211526
* @see #create(io.helidon.common.http.Http.HeaderName, boolean, boolean, String...)
15221527
*/
15231528
public static HeaderValue create(HeaderName name, long value) {
1529+
Objects.requireNonNull(name);
1530+
15241531
return new HeaderValueSingle(name, false, false, String.valueOf(value));
15251532
}
15261533

@@ -1533,6 +1540,9 @@ public static HeaderValue create(HeaderName name, long value) {
15331540
* @see #create(io.helidon.common.http.Http.HeaderName, boolean, boolean, String...)
15341541
*/
15351542
public static HeaderValue create(HeaderName name, String value) {
1543+
Objects.requireNonNull(name, "HeaderName must not be null");
1544+
Objects.requireNonNull(value, "HeaderValue must not be null");
1545+
15361546
return new HeaderValueSingle(name,
15371547
false,
15381548
false,

examples/nima/tracing/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@
4646
<groupId>io.helidon.nima.webserver</groupId>
4747
<artifactId>helidon-nima-webserver-tracing</artifactId>
4848
</dependency>
49+
<dependency>
50+
<groupId>io.helidon.nima.webclient</groupId>
51+
<artifactId>helidon-nima-webclient</artifactId>
52+
</dependency>
53+
<dependency>
54+
<groupId>io.helidon.nima.webclient</groupId>
55+
<artifactId>helidon-nima-webclient-tracing</artifactId>
56+
</dependency>
4957
<dependency>
5058
<groupId>io.helidon.tracing</groupId>
5159
<artifactId>helidon-tracing-jaeger</artifactId>

examples/nima/tracing/src/main/java/io/helidon/examples/nima/tracing/TracingMain.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@
1818

1919
import io.helidon.logging.common.LogConfig;
2020
import io.helidon.nima.http2.webserver.Http2Route;
21+
import io.helidon.nima.webclient.WebClient;
22+
import io.helidon.nima.webclient.http1.Http1Client;
23+
import io.helidon.nima.webclient.tracing.WebClientTracing;
2124
import io.helidon.nima.webserver.WebServer;
2225
import io.helidon.nima.webserver.http.Handler;
2326
import io.helidon.nima.webserver.http.ServerRequest;
@@ -54,11 +57,30 @@ public static void main(String[] args) {
5457
.addFeature(TracingFeature.create(tracer))
5558
.route(Http1Route.route(GET, "/versionspecific", new TracedHandler(tracer, "HTTP/1.1 route")))
5659
.route(Http2Route.route(GET, "/versionspecific", new TracedHandler(tracer, "HTTP/2 route")))
57-
)
60+
.get("/client", new ClientHandler(tracer)))
5861
.build()
5962
.start();
6063
}
6164

65+
private static class ClientHandler implements Handler {
66+
private final Http1Client client;
67+
68+
private ClientHandler(Tracer tracer) {
69+
this.client = WebClient.builder()
70+
.baseUri("http://localhost:8080/versionspecific")
71+
.useSystemServiceLoader(false)
72+
.addService(WebClientTracing.create(tracer))
73+
.build();
74+
}
75+
76+
@Override
77+
public void handle(ServerRequest req, ServerResponse res) {
78+
res.send(client.get()
79+
.request()
80+
.as(String.class));
81+
}
82+
}
83+
6284
private static class TracedHandler implements Handler {
6385
private final Tracer tracer;
6486
private final String message;

nima/http2/webclient/src/main/java/io/helidon/nima/http2/webclient/ClientRequestImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.helidon.common.http.Http.HeaderValue;
3636
import io.helidon.common.http.WritableHeaders;
3737
import io.helidon.common.socket.SocketOptions;
38+
import io.helidon.common.uri.UriFragment;
3839
import io.helidon.common.uri.UriQueryWriteable;
3940
import io.helidon.nima.common.tls.Tls;
4041
import io.helidon.nima.http2.Http2Headers;
@@ -64,6 +65,7 @@ class ClientRequestImpl implements Http2ClientRequest {
6465
private ClientConnection explicitConnection;
6566
private Duration flowControlTimeout = Duration.ofMillis(100);
6667
private Duration timeout = Duration.ofSeconds(10);
68+
private UriFragment fragment = UriFragment.empty();
6769

6870
ClientRequestImpl(Http2ClientImpl client,
6971
ExecutorService executor,
@@ -224,6 +226,12 @@ public Http2ClientRequest flowControlTimeout(Duration timeout) {
224226
return this;
225227
}
226228

229+
@Override
230+
public Http2ClientRequest fragment(String fragment) {
231+
this.fragment = UriFragment.create(fragment);
232+
return this;
233+
}
234+
227235
UriHelper uriHelper() {
228236
return uri;
229237
}
@@ -250,7 +258,7 @@ private Http2Headers prepareHeaders(WritableHeaders<?> headers) {
250258
http2Headers.method(this.method);
251259
http2Headers.authority(this.uri.authority());
252260
http2Headers.scheme(this.uri.scheme());
253-
http2Headers.path(this.uri.pathWithQuery(query));
261+
http2Headers.path(this.uri.pathWithQueryAndFragment(query, fragment));
254262

255263
headers.remove(Header.HOST, LogHeaderConsumer.INSTANCE);
256264
headers.remove(Header.TRANSFER_ENCODING, LogHeaderConsumer.INSTANCE);

nima/http2/webclient/src/main/java/io/helidon/nima/http2/webclient/Http2Client.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package io.helidon.nima.http2.webclient;
1818

1919
import io.helidon.nima.http2.WindowSize;
20+
import io.helidon.nima.webclient.DefaultDnsResolverProvider;
21+
import io.helidon.nima.webclient.DnsAddressLookup;
2022
import io.helidon.nima.webclient.HttpClient;
2123
import io.helidon.nima.webclient.WebClient;
2224

@@ -46,6 +48,9 @@ class Http2ClientBuilder extends WebClient.Builder<Http2ClientBuilder, Http2Clie
4648
private int prefetch = 33554432;
4749

4850
private Http2ClientBuilder() {
51+
// until we use the same parent for HTTP/1 and HTTP/2, we need to have these defined as defaults
52+
super.dnsResolver(new DefaultDnsResolverProvider().createDnsResolver());
53+
super.dnsAddressLookup(DnsAddressLookup.defaultLookup());
4954
}
5055

5156
/**

nima/tests/integration/webclient/webclient/src/test/java/io/helidon/nima/webclient/http1/ClientRequestImplTest.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ class ClientRequestImplTest {
5858
Http.Header.create("X-Req-Expect100"), "true");
5959
private static final Http.HeaderName REQ_CONTENT_LENGTH_HEADER_NAME = Http.Header.create("X-Req-ContentLength");
6060
private static final long NO_CONTENT_LENGTH = -1L;
61-
private static String baseURI;
62-
private static Http1Client injectedHttp1client;
61+
62+
private final String baseURI;
63+
private final Http1Client injectedHttp1client;
6364

6465
ClientRequestImplTest(WebServer webServer, Http1Client client) {
65-
this.baseURI = "http://localhost:" + webServer.port();
66-
this.injectedHttp1client = client;
66+
baseURI = "http://localhost:" + webServer.port();
67+
injectedHttp1client = client;
6768
}
6869

6970
@SetUpRoute
@@ -208,7 +209,11 @@ void testConnectionQueueDequeue() {
208209
for (int i = 0; i < 5; ++i) {
209210
Http1ClientRequest request = injectedHttp1client.put("/test");
210211
// connection will be dequeued if queue is not empty
211-
connectionNow = ((ClientRequestImpl) request).getConnection(true);
212+
ClientRequestImpl requestImpl = (ClientRequestImpl) request;
213+
connectionNow = ConnectionCache.connection(requestImpl.clientConfig(),
214+
null,
215+
requestImpl.uri(),
216+
requestImpl.headers());
212217
request.connection(connectionNow);
213218
Http1ClientResponse response = request.request();
214219
// connection will be queued up
@@ -222,14 +227,18 @@ void testConnectionQueueDequeue() {
222227

223228
@Test
224229
void testConnectionQueueSizeLimit() {
225-
int connectionQueueSize = ((Http1ClientImpl) injectedHttp1client).connectionQueueSize();
230+
int connectionQueueSize = ((Http1ClientImpl) injectedHttp1client).clientConfig().connectionQueueSize();
226231

227232
List<ClientConnection> connectionList = new ArrayList<ClientConnection>();
228233
List<Http1ClientResponse> responseList = new ArrayList<Http1ClientResponse>();
229234
// create connections beyond the queue size limit
230235
for (int i = 0; i < connectionQueueSize + 1; ++i) {
231236
Http1ClientRequest request = injectedHttp1client.put("/test");
232-
connectionList.add(((ClientRequestImpl) request).getConnection(true));
237+
ClientRequestImpl requestImpl = (ClientRequestImpl) request;
238+
connectionList.add(ConnectionCache.connection(requestImpl.clientConfig(),
239+
null,
240+
requestImpl.uri(),
241+
requestImpl.headers()));
233242
request.connection(connectionList.get(i));
234243
responseList.add(request.request());
235244
}
@@ -244,7 +253,11 @@ void testConnectionQueueSizeLimit() {
244253
Http1ClientResponse response = null;
245254
for (int i = 0; i < connectionQueueSize + 1; ++i) {
246255
Http1ClientRequest request = injectedHttp1client.put("/test");
247-
connection = ((ClientRequestImpl) request).getConnection(true);
256+
ClientRequestImpl requestImpl = (ClientRequestImpl) request;
257+
connection = ConnectionCache.connection(requestImpl.clientConfig(),
258+
null,
259+
requestImpl.uri(),
260+
requestImpl.headers());
248261
request.connection(connection);
249262
response = request.request();
250263
if (i < connectionQueueSize) {
@@ -259,7 +272,11 @@ void testConnectionQueueSizeLimit() {
259272
// The queue is currently empty so check if we can add the last created connection into it.
260273
response.close();
261274
Http1ClientRequest request = injectedHttp1client.put("/test");
262-
ClientConnection connectionNow = ((ClientRequestImpl) request).getConnection(true);
275+
ClientRequestImpl requestImpl = (ClientRequestImpl) request;
276+
ClientConnection connectionNow = ConnectionCache.connection(requestImpl.clientConfig(),
277+
null,
278+
requestImpl.uri(),
279+
requestImpl.headers());
263280
request.connection(connectionNow);
264281
Http1ClientResponse responseNow = request.request();
265282
// Verify that the connection was dequeued
@@ -339,7 +356,7 @@ private static void customHandler(ServerRequest req, ServerResponse res, boolean
339356
}
340357
}
341358

342-
private static Http1ClientRequest getHttp1ClientRequest(Http.Method method, String uriPath) {
359+
private Http1ClientRequest getHttp1ClientRequest(Http.Method method, String uriPath) {
343360
return injectedHttp1client.method(method).uri(uriPath);
344361
}
345362

nima/webclient/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
<modules>
3939
<module>webclient</module>
40+
<module>tracing</module>
4041
</modules>
4142

4243
</project>

0 commit comments

Comments
 (0)