Contents
Overview
WebClient is an HTTP client for Helidon SE. It can be used to send requests and retrieve corresponding responses in a programmatic way.
Helidon WebClient provides the following features:
Blocking approach
The Webclient uses the blocking approach to synchronously process a request and its correspond response. BothHTTP/1.1andHTTP/2request and response will run in the thread of the user. Additionally, forHTTP/2, virtual thread is employed to manage the connection.Builder-like setup and execution
Creates every client and request as a builder pattern. This improves readability and code maintenance.Redirect chain
Follows the redirect chain and perform requests on the correct endpoint by itself.Tracing and security propagation
Automatically propagates the configured tracing and security settings of the Helidon WebServer to the WebClient and uses them during request and response.
Maven Coordinates
To enable WebClient add the following dependency to your project’s pom.xml (see Managing Dependencies).
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
</dependency>The helidon-webclient dependency has built-in support for HTTP/1.1.
If support for HTTP/2 is a requirement, below dependency needs to be added:
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient-http2</artifactId>
</dependency>Usage
Instantiating the WebClient
You can create an instance of a WebClient by executing WebClient.create() which will have default settings and without a base uri set.
To change the default settings and register additional services, you can use simple builder that allows you to customize the client behavior.
WebClient client = WebClient.builder()
.baseUri("http://localhost")
.build();Creating the Request
WebClient offers a set of request methods that are used to specify the type of action to be performed on a given resource. Below are some examples of request methods:
get()post()put()method(String methodName)
Check out HttpClient API to learn more about request methods. These methods will create a new instance of HttpClientRequest which can then be configured to add optional settings that will customize the behavior of the request.
Customizing the Request
Configuration can be set for every request type before it is sent.
client.get()
.uri("http://example.com")
.path("/path")
.queryParam("query", "parameter")
.fragment("someFragment")
.headers(headers -> headers.accept(MediaTypes.APPLICATION_JSON)); - Overrides
baseUrifrom WebClient - Adds path to the uri
- Adds query parameter to the request
- Adds fragment to the request
- Adds header to the request
For more information about these optional parameters, check out ClientRequestBase API, which is a parent class of HttpClientRequest.
HttpClientRequest class also provides specific header methods that help the user to set a particular header. Some examples of these are:
contentType(MediaType contentType)accept(MediaType… mediaTypes)
For more information about these methods, check out ClientRequest API, which is a parent class of HttpClientRequest.
Sending the Request
Once the request setup is completed, the following methods can be used to send it:
HttpClientResponse request()<E> ClientResponseTyped<E> request(Class<E> type)<E> E requestEntity(Class<E> type)HttpClientResponse submit(Object entity)<T> ClientResponseTyped<T> submit(Object entity, Class<T> requestedType)HttpClientResponse outputStream(OutputStreamHandler outputStreamConsumer)<T> ClientResponseTyped<T> outputStream(OutputStreamHandler outputStreamConsumer, Class<T> requestedType)
Each of the methods will provide a way to allow response to be retrieved in a particular response type. Refer to ClientRequest API for more details about these methods.
ClientResponseTyped<String> response = client.get()
.path("/endpoint")
.request(String.class);
String entityString = response.entity();Protocol Used
WebClient currently supports HTTP/1.1 and HTTP/2 protocols. Below are the rules on which specific protocol will be used:
Using plain socket triggers WebClient to process a request using
HTTP/1.1.When using TLS, the client will use ALPN (protocol negotiation) to use appropriate HTTP version (either 1.1, or 2).
HTTP/2has a higher weight, so it is chosen if supported by both sides.A specific protocol can be explicitly selected by calling
HttpClientRequest#protocolId(String).
String result = client.get()
.protocolId("http/1.1")
.requestEntity(String.class);If
HTTP/2is used, an upgrade attempt will be performed. If it fails, the client falls-back toHTTP/1.1.The parameter
prior-knowledgecan be defined usingHTTP/2protocol configuration. Please refer to on how to customizeHTTP/2. In such a case,prior-knowledgewill be used and fail if it is unable to switch toHTTP/2.
Adding Media Support
Webclient supports the following built-in Helidon Media Support libraries:
- JSON Processing (JSON-P)
- JSON Binding (JSON-B)
- Jackson
They can be activated by adding their corresponding libraries into the classpath. This can simply be done by adding their corresponding dependencies.
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonp</artifactId>
</dependency><dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonb</artifactId>
</dependency><dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jackson</artifactId>
</dependency>Users can also create their own Custom Media Support library and make them work by following either of the approaches:
Create a Provider of the Custom Media Support and expose it via Service Loader followed by adding the Media Support library to the classpath.
Explicitly register the Custom Media Support from WebClient.
WebClient.builder()
.mediaContext(it -> it
.addMediaSupport(CustomMediaSupport.create()))
.build();- Register CustomMedia support from the WebClient.
DNS Resolving
Webclient provides three DNS resolver implementations out of the box:
Java DNS resolutionis the default.First DNS resolutionuses the first IP address from a DNS lookup. To enable this option, add below dependency:
<dependency>
<groupId>io.helidon.webclient.dns.resolver</groupId>
<artifactId>helidon-webclient-dns-resolver-first</artifactId>
</dependency>Round-Robin DNS resolutioncycles through IP addresses from a DNS lookup. To enable this option, add this dependency:
<dependency>
<groupId>io.helidon.webclient.dns.resolver</groupId>
<artifactId>helidon-webclient-dns-resolver-round-robin</artifactId>
</dependency>Configuring the WebClient
The class responsible for WebClient configuration is:
Type: io.helidon.webclient.api.WebClient
This is a standalone configuration type, prefix from configuration root: clients
Configuration options
| key | type | default value | description |
|---|---|---|---|
base-uri | string | Base uri used by the client in all requests. @return base uri of the client requests | |
connect-timeout | Duration | Connect timeout. @return connect timeout @see io.helidon.common.socket.SocketOptions#connectTimeout() | |
connection-cache-size | int | 256 | Maximal size of the connection cache. For most HTTP protocols, we may cache connections to various endpoints for keep alive (or stream reuse in case of HTTP/2). This option limits the size. Setting this number lower than the "usual" number of target services will cause connections to be closed and reopened frequently. |
content-encoding | Configure the listener specific io.helidon.http.encoding.ContentEncodingContext. This method discards all previously registered ContentEncodingContext. If no content encoding context is registered, default encoding context is used. @return content encoding context | ||
cookie-manager | WebClient cookie manager. @return cookie manager to use | ||
default-headers | Map<string, string> | Default headers to be used in every request from configuration. @return default headers | |
follow-redirects | boolean | true | Whether to follow redirects. @return whether to follow redirects |
keep-alive | boolean | true | Determines if connection keep alive is enabled (NOT socket keep alive, but HTTP connection keep alive, to re-use the same connection for multiple requests). @return keep alive for this connection @see io.helidon.common.socket.SocketOptions#socketKeepAlive() |
max-in-memory-entity | int | 131072 | If the entity is expected to be smaller that this number of bytes, it would be buffered in memory to optimize performance. If bigger, streaming will be used. Note that for some entity types we cannot use streaming, as they are already fully in memory (String, byte[]), for such cases, this option is ignored. Default is 128Kb. @return maximal number of bytes to buffer in memory for supported writers |
max-redirects | int | 10 | Max number of followed redirects. This is ignored if #followRedirects() option is @return max number of followed redirects |
media-context | create() | Configure the listener specific io.helidon.http.media.MediaContext. This method discards all previously registered MediaContext. If no media context is registered, default media context is used. @return media context | |
media-type-parser-mode | ParserMode (STRICT, RELAXED) | STRICT | Configure media type parsing mode for HTTP @return media type parsing mode |
properties | Map<string, string> | Properties configured for this client. These properties are propagated through client request, to be used by services (and possibly for other purposes). @return map of client properties | |
protocol-configs | io.helidon.webclient.spi.ProtocolConfig[] (service provider interface) | Configuration of client protocols. @return client protocol configurations | |
proxy | Proxy configuration to be used for requests. @return proxy to use, defaults to Proxy#noProxy() | ||
read-continue-timeout | Duration | PT1S | Socket 100-Continue read timeout. Default is 1 second. This read timeout is used when 100-Continue is sent by the client, before it sends an entity. @return read 100-Continue timeout duration |
read-timeout | Duration | Read timeout. @return read timeout @see io.helidon.common.socket.SocketOptions#readTimeout() | |
relative-uris | boolean | false | Can be set to @return relative URIs flag |
send-expect-continue | boolean | true | Whether Expect-100-Continue header is sent to verify server availability before sending an entity. Defaults to `true`. @return whether Expect:100-Continue header should be sent on streamed transfers |
services | io.helidon.webclient.spi.WebClientService[] (service provider interface) | WebClient services. @return services to use with this web client | |
share-connection-cache | boolean | true | Whether to share connection cache between all the WebClient instances in JVM. @return true if connection cache is shared |
socket-options | Socket options for connections opened by this client. If there is a value explicitly configured on this type and on the socket options, the one configured on this type’s builder will win:
@return socket options | ||
tls | TLS configuration for any TLS request from this client. TLS can also be configured per request. TLS is used when the protocol is set to @return TLS configuration to use |
Protocol Specific Configuration
Protocol specific configuration can be set using the protocol-configs parameter. Webclient currently supports HTTP/1.1. and HTTP/2. Below are the options for each of the protocol type:
HTTP/1.1
Type: io.helidon.webclient.http1.Http1ClientProtocolConfig
Configuration options
| key | type | default value | description |
|---|---|---|---|
default-keep-alive | boolean | true | Whether to use keep alive by default. @return `true` for keeping connections alive and re-using them for multiple requests (default), `false` to create a new connection for each request |
max-header-size | int | 16384 | Configure the maximum allowed header size of the response. @return maximum header size |
max-status-line-length | int | 256 | Configure the maximum allowed length of the status line from the response. @return maximum status line length |
name | string | http_1_1 | |
validate-request-headers | boolean | false | Sets whether the request header format is validated or not. Defaults to `false` as user has control on the header creation. @return whether request header validation should be enabled |
validate-response-headers | boolean | true | Sets whether the response header format is validated or not. Defaults to `true`. @return whether response header validation should be enabled |
HTTP/2
Configuration options
| key | type | default value | description |
|---|---|---|---|
flow-control-block-timeout | Duration | PT0.1S | Timeout for blocking between windows size check iterations. @return timeout |
initial-window-size | int | 65535 | Configure INITIAL_WINDOW_SIZE setting for new HTTP/2 connections. Sends to the server the size of the largest frame payload client is willing to receive. Defaults to @return units of octets |
max-frame-size | int | 16384 | Configure initial MAX_FRAME_SIZE setting for new HTTP/2 connections. Maximum size of data frames in bytes the client is prepared to accept from the server. Default value is 2^14(16_384). @return data frame size in bytes between 2^14(16_384) and 2^24-1(16_777_215) |
max-header-list-size | long | -1 | Configure initial MAX_HEADER_LIST_SIZE setting for new HTTP/2 connections. Sends to the server the maximum header field section size client is prepared to accept. Defaults to @return units of octets |
name | string | h2 | |
ping | boolean | false | Check healthiness of cached connections with HTTP/2.0 ping frame. Defaults to @return use ping if true |
ping-timeout | Duration | PT0.5S | Timeout for ping probe used for checking healthiness of cached connections. Defaults to @return timeout |
prior-knowledge | boolean | false | Prior knowledge of HTTP/2 capabilities of the server. If server we are connecting to does not support HTTP/2 and prior knowledge is set to @return whether to use prior knowledge of HTTP/2 |
Example of a WebClient Runtime Configuration
Config config = Config.create();
WebClient client = WebClient.builder()
.baseUri("http://localhost")
.config(config.get("client"))
.build();Example of a WebClient YAML Configuration
client:
connect-timeout-millis: 2000
read-timeout-millis: 2000
follow-redirects: true
max-redirects: 5
cookie-manager:
automatic-store-enabled: true
default-cookies:
flavor3: strawberry
flavor4: raspberry
default-headers:
Accept: '"application/json", "text/plain"'
services:
metrics:
- methods: ["PUT", "POST", "DELETE"]
type: METER
name-format: "client.meter.overall"
- type: TIMER
# meter per method
name-format: "client.meter.%1$s"
- methods: ["GET"]
type: COUNTER
errors: false
name-format: "client.counter.%1$s.success"
description: "Counter of successful GET requests"
- methods: ["PUT", "POST", "DELETE"]
type: COUNTER
success: false
name-format: "wc.counter.%1$s.error"
description: "Counter of failed PUT, POST and DELETE requests"
tracing:
protocol-configs:
http_1_1:
max-header-size: 20000
validate-request-headers: true
h2:
prior-knowledge: true
proxy:
host: "hostName"
port: 80
no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"]
tls:
trust:
keystore:
passphrase: "password"
trust-store: true
resource:
resource-path: "client.p12"- Client functional settings
- Cookie management
- Default client headers
- Client service configuration
- Protocol configuration
- Proxy configuration
- TLS configuration
Examples
Webclient with Proxy
Configure Proxy setup either programmatically or via the Helidon configuration framework.
Configuring Proxy in your code
Proxy can be set directly from WebClient builder.
Proxy proxy = Proxy.builder()
.type(Proxy.ProxyType.HTTP)
.host(PROXY_HOST)
.port(PROXY_PORT)
.build();
WebClient.builder()
.proxy(proxy)
.build();Alternative is to set proxy directly from the request via HttpClientRequest.
Proxy proxy = Proxy.create();
HttpClientResponse response = client.get("/proxiedresource")
.proxy(proxy)
.request();- Proxy instance configured using system settings (environment variables and system properties)
- Configure the proxy per client request
Configuring Proxy in the config file
Proxy can also be configured in WebClient through the application.yaml configuration file.
application.yamlclient:
proxy:
host: "hostName"
port: 80
no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"]Then, in your application code, load the configuration from that file.
application.yaml file located on the classpathConfig config = Config.create();
WebClient.builder()
.config(config.get("client"))
.build();application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.create()- Passing the client configuration node
WebClient TLS Setup
Configure TLS either programmatically or by the Helidon configuration framework.
Configuring TLS in your code
One way to configure TLS in WebClient is in your application code as shown below.
WebClient.builder()
.tls(it -> it.trust(t -> t
.keystore(k -> k.passphrase("password")
.trustStore(true)
.keystore(r -> r.resourcePath("client.p12")))))
.build();Configuring TLS in the config file
Another way to configure TLS in WebClient is through the application.yaml configuration file.
application.yamlclient:
tls:
trust:
keystore:
passphrase: "password"
trust-store: true
resource:
resource-path: "client.p12"The passphrase value on the config file can be encrypted if stronger security is required. For more information on how secrets can be encrypted using a master password and store them in a configuration file, please see Configuration Secrets.
In the application code, load the settings from the configuration file.
application.yaml file located on the classpathConfig config = Config.create();
WebClient.builder()
.config(config.get("client"))
.build();application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.create()- Passing the client configuration node
Adding Service to WebClient
WebClient currently supports 3 built-in services namely metrics, tracing and security.
Enabling the service
In order for a service to function, their dependency needs to be added in the application’s pom.xml. Below are examples on how to enable the built-in services:
metrics
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient-metrics</artifactId>
</dependency>tracing
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient-tracing</artifactId>
</dependency>security
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient-security</artifactId>
</dependency>Adding a service in your code
Services can be added in WebClient as shown in the code below.
WebClientService clientService = WebClientMetrics.counter()
.methods(Method.GET)
.nameFormat("example.metric.%1$s.%2$s")
.build();
WebClient.builder()
.addService(clientService)
.build();- Creates new metric which will count all GET requests and has format of
example.metric.GET.<host-name> - Register the service in the client instance
Adding service in the config file
Adding service in WebClient can also be done through the application.yaml configuration file.
application.yamlwebclient:
services:
metrics:
- type: METER
name-format: "client.meter.overall"
- type: TIMER
# meter per method
name-format: "client.meter.%1$s"
- methods: ["PUT", "POST", "DELETE"]
type: COUNTER
success: false
name-format: "wc.counter.%1$s.error"
description: "Counter of failed PUT, POST and DELETE requests"
tracing:Then, in your application code, load the configuration from that file.
application.yaml file located on the classpathConfig config = Config.create();
WebClient.builder()
.config(config.get("client"))
.build();application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.create()- Passing the client configuration node
Setting Protocol configuration
Individual protocols can be customized using the protocol-config parameter.
Setting up protocol configuration in your code
Below is an example of customizing HTTP/1.1 protocol in the application code.
WebClient.builder()
.addProtocolConfig(Http1ClientProtocolConfig.builder()
.defaultKeepAlive(false)
.validateRequestHeaders(true)
.validateResponseHeaders(false)
.build())
.build();Setting up protocol configuration in the config file
Protocol configuration can also be set in the application.yaml configuration file.
HTTP/1.1 and HTTP/2 protocol using application.yaml file.webclient:
protocol-configs:
http_1_1:
max-header-size: 20000
validate-request-headers: true
h2:
prior-knowledge: trueThen, in your application code, load the configuration from that file.
application.yaml file located on the classpathConfig config = Config.create();
WebClient.builder()
.config(config.get("client"))
.build();application.yamlis a default configuration source loaded when YAML support is on classpath, so we can just useConfig.create()- Passing the client configuration node