diff --git a/docs/se/webclient.adoc b/docs/se/webclient.adoc index 3b448ff5d37..f02114fd004 100644 --- a/docs/se/webclient.adoc +++ b/docs/se/webclient.adoc @@ -35,10 +35,13 @@ include::{rootdir}/includes/se.adoc[] == Overview -WebClient is an HTTP client of Helidon SE. It handles the responses to the HTTP requests in a programmatic way. +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. Both `HTTP/1.1` and `HTTP/2` request and response will run in the thread of the user. Additionally, for `HTTP/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. @@ -58,11 +61,23 @@ include::{rootdir}/includes/dependencies.adoc[] ---- +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: + +[source,xml] +---- + + io.helidon.webclient + helidon-webclient-http2 + +---- + == Usage -=== Creating the WebClient +=== Instantiating the WebClient -You can create WebClient by executing `WebClient.create()` method. This will create an instance of client with default settings and without a base uri set. +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. @@ -75,40 +90,174 @@ WebClient client = WebClient.builder() .build(); ---- -=== Creating and Executing the WebClient Request +=== Creating the Request -WebClient executes requests to the target endpoints and returns specific response type. +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: -It offers variety of methods to specify the type of request you want to execute: - -* `put()` * `get()` +* `post()` +* `put()` * `method(String methodName)` -These methods set specific request type based on their name or parameter to the new instance of `WebClientRequesBuilder` and return this instance based on configurations for specific request type. +Check out link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/HttpClient.html[HttpClient.html] API to learn more about request methods. These methods will create a new instance of link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/HttpClientRequest.html[HttpClientRequest] which can then be configured to add optional settings that will customize the behavior of the request. -You can set configuration for every request type before it is sent as described in <>. +=== Customizing the Request -For the final execution, use the following methods with variations and different parameters: +Configuration can be set for every request type before it is sent. Below are some examples of the optional parameters. -* `Single submit(Object entity, Class responseType)` -* `Single request(Class responseType)` +|=== +|Parameter |Description -.Execute a simple GET request to endpoint: +|`uri("http://example.com")` |Overrides baseUri from WebClient +|`path("/path")` |Adds path to the uri +|`queryParam("query", "parameter")` |Adds query parameter to the request +|`fragment("someFragment")` |Adds fragment to the request +|`headers(headers -> headers.addAccept(MediaType.APPLICATION_JSON))` |Adds header to the request +|=== + +For more information about these optional parameters, check out link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/ClientRequestBase.html[ClientRequestBase] API, which is a parent class of link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/HttpClientRequest.html[HttpClientRequest]. + +link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/HttpClientRequest.html[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 link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/ClientRequest.html[ClientRequest] API, which is a parent class of link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/HttpClientRequest.html[HttpClientRequest]. + +=== Sending the Request + +Once the request setup is completed, the following methods can be used to send it: + +* `HttpClientResponse request()` +* ` ClientResponseTyped request(Class type)` +* ` E requestEntity(Class type)` +* `HttpClientResponse submit(Object entity)` +* ` ClientResponseTyped submit(Object entity, Class requestedType)` +* `HttpClientResponse outputStream(OutputStreamHandler outputStreamConsumer)` +* ` ClientResponseTyped outputStream(OutputStreamHandler outputStreamConsumer, Class requestedType)` + +Each of the methods will provide a way to allow response to be retrieved in a particular response type. Refer to link:{webclient-javadoc-base-url}..api/io/helidon/webclient/api/ClientRequest.html[ClientRequest API] for more details about these methods. + +.Execute a simple GET request to endpoint and receive a String response: [source,java] ---- -Single response = client.get() - .path("/endpoint") - .request(String.class); +ClientResponseTyped response = webClient.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/2` has a higher weight, so it is chosen if supported by both sides. +* A specific protocol can be explicitly selected by calling `HttpClientRequest#protocolId(String)`. +[source,java] +---- +String result = webClient.get() + .protocolId("http/1.1") + .requestEntity(String.class); +---- +* If `HTTP/2` is used, an upgrade attempt will be performed. If it fails, the client falls-back to `HTTP/1.1`. +* The parameter `prior-knowledge` can be defined using `HTTP/2` protocol configuration. Please refer to <> on how to customize `HTTP/2`. In such a case, `prior-knowledge` will be used and fail if it is unable to switch to `HTTP/2`. + +=== Adding Media Support to the WebClient + +Webclient supports the following built-in Helidon Media Support libraries: + +1. JSON Processing (JSON-P) +2. JSON Binding (JSON-B) +3. Jackson + +They can be activated by adding their corresponding libraries into the classpath. This can simply be done by adding their corresponding dependencies. + +.Add JSON-P support: +[source,xml] +---- + + io.helidon.http.media + helidon-http-media-jsonp + +---- +.Add JSON-B support: +[source,xml] +---- + + io.helidon.http.media + helidon-http-media-jsonb + +---- +.Add Jackson support: +[source,xml] +---- + + io.helidon.http.media + helidon-http-media-jackson + +---- + +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. + +[source,java] +---- +WebClient webclient = WebClient.builder() + .mediaContext(MediaContext.builder() + .addMediaSupport(CustomMediaSupport.create()) <1> + .build()) + .build() +---- +<1> Register CustomMedia support from the WebClient. + +=== DNS Resolving + +Webclient provides three DNS resolver implementations out of the box: + +* `Java DNS resolution` is the default. +* `*First* DNS resolution` uses the first IP address from a DNS lookup. To enable this option, add below dependency: + +[source,xml] +---- + + io.helidon.webclient.dns.resolver + helidon-webclient-dns-resolver-first + +---- + +* `*Round-Robin* DNS resolution` cycles through IP addresses from a DNS lookup. To enable this option, add this dependency: + +[source,xml] +---- + + io.helidon.webclient.dns.resolver + helidon-webclient-dns-resolver-round-robin + +---- + +=== Adding Service + + == Configuring the WebClient The class responsible for WebClient configuration is: include::{rootdir}/config/io_helidon_webclient_api_WebClient.adoc[leveloffset=+1,tag=config] +=== 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` +include::{rootdir}/config/io_helidon_webclient_http1_Http1ClientProtocolConfig.adoc[leveloffset=2,tag=config] + + +* `HTTP/2` + +include::{rootdir}/config/io_helidon_webclient_http2_Http2ClientProtocolConfig.adoc[leveloffset=2,tag=config] === Example of a WebClient Runtime Configuration @@ -130,121 +279,112 @@ client: read-timeout-millis: 2000 follow-redirects: true <1> max-redirects: 5 - cookies: + cookie-manager: <2> automatic-store-enabled: true default-cookies: - - name: "env" - value: "dev" - headers: - - name: "Accept" - value: ["application/json","text/plain"] <2> - services: <3> - config: - 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" - - methods: ["GET"] - type: GAUGE_IN_PROGRESS - name-format: "client.inprogress.%2$s" - description: "In progress requests to host" - tracing: - proxy: <4> - use-system-selector: false + flavor3: strawberry + flavor4: raspberry + default-headers: <3> + Accept: '"application/json","text/plain"' + services: <4> + 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: <5> + http_1_1: + max-header-size: 20000 + validate-request-headers: true + h2: + prior-knowledge: true + proxy: <6> host: "hostName" port: 80 no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"] - tls: <5> - server: - trust-all: true - disable-hostname-verification: true + tls: <7> + trust: keystore: passphrase: "password" trust-store: true resource: resource-path: "client.p12" - client: - keystore: - passphrase: "password" - resource: - resource-path: "client.p12" ---- <1> Client functional settings -<2> Default client headers and cookies -<3> Client service configuration -<4> Proxy configuration -<5> TLS configuration +<2> Cookie management +<3> Default client headers +<4> Client service configuration +<5> Protocol configuration +<6> Proxy configuration +<7> TLS configuration == Examples -=== Request Configuration - -The request settings are based on the following optional parameters, and change when a specific request is executed. - -|=== -|Parameter |Description - -|`uri("http://example.com")` |Overrides baseUri from WebClient -|`path("/path")` |Adds path to the uri -|`queryParam("query", "parameter")` |Adds query parameter to the request -|`fragment("someFragment")` |Adds fragment to the request -|`headers(headers -> headers.addAccept(MediaType.APPLICATION_JSON))` |Adds header to the request -|=== +=== Webclient with Proxy +Configure Proxy setup either programmatically or via the Helidon configuration framework. -`WebClientRequestBuilder` class also provides specific header methods that help the user to set a particular header. The methods are: +==== Configuring Proxy in your code +Proxy can be set directly from WebClient builder. +[source,java] +---- +Proxy proxy = Proxy.builder() + .type(Proxy.ProxyType.HTTP) + .host(PROXY_HOST) + .port(PROXY_PORT) + .build(); +WebClient webClient = WebClient.builder() + .proxy(proxy) + .build(); +---- -* `contentType` (MediaType contentType) -* `accept` (MediaType... mediaTypes) +Alternative is to set proxy directly from the request via `HttpClientRequest`. -For more details, see the link:{webserver-javadoc-base-url}/io/helidon/webserver/RequestHeaders.html[Request Headers] API. +[source,java] +---- +// Using System Proxy +Proxy proxy = Proxy.create(); +System.setProperty("http.proxyHost", PROXY_HOST); +System.setProperty("http.proxyPort", PROXY_PORT); +System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1|10.*.*.*|*.example.com|etc|" + TARGET_HOST); +webClient.get("/proxiedresource").proxy(proxy).request() +---- -=== Adding JSON Processing Media Support to the WebClient +==== Configuring Proxy in the config file -JSON Processing (JSON-P) media support is not present in the WebClient by default. So, in this case, you must first register it before making a request. -This example shows how to register `JsonpSupport` using the following two methods. +Proxy can also be configured in WebClient through the `application.yaml` configuration file. -[source,java] -.Register JSON-P support to the WebClient. +[source,yaml] +.WebClient Proxy configuration in `application.yaml` ---- -WebClient.builder() - .baseUri("http://localhost") - .addReader(JsonpSupport.reader()) // <1> - .addWriter(JsonpSupport.writer()) // <2> - .addMediaService(JsonpSupport.create()) // <3> - .build(); +webclient: + proxy: + host: "hostName" + port: 80 + no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"] ---- -<1> Adds JSON-P reader to all client requests. -<2> Adds JSON-P writer to all client requests. -<3> Adds JSON-P writer and reader to all client requests. +Then, in your application code, load the configuration from that file. [source,java] -.Register JSON-P support only to the specific request. - +.WebClient initialization using the `application.yaml` file located on the classpath ---- -WebClient webClient = WebClient.create(); - -WebClientRequestBuilder requestBuilder = webClient.get(); -requestBuilder.writerContext().registerWriter(JsonSupport.writer()); // <1> -requestBuilder.readerContext().registerReader(JsonSupport.reader()); // <2> - -requestBuilder.request(JsonObject.class) +Config config = Config.create(); +WebClient webClient = WebClient.create(config.get("webclient")); ---- -<1> Adds JSON-P writer only to this request. -<2> Adds JSON-P reader only to this request. + === WebClient TLS setup @@ -256,20 +396,14 @@ One way to configure TLS in WebClient is in your application code as shown below [source,java] ---- -KeyConfig keyConfig = KeyConfig.keystoreBuilder() - //Whether this keystore is also trust store - .trustStore() - //Keystore location/name - .keystore(Resource.create("client.p12")) - //Password to the keystore - .keystorePassphrase("password") - .build(); - WebClient.builder() - .tls(WebClientTls.builder() - .certificateTrustStore(keyConfig) - .clientKeyStore(keyConfig) - .build()) + .tls(Tls.builder() + .trust(trust -> trust + .keystore(store -> store + .passphrase("password") + .trustStore(true) + .keystore(Resource.create("client.p12")))) + .build()) .build(); ---- @@ -278,23 +412,16 @@ WebClient.builder() Another way to configure TLS in WebClient is through the `application.yaml` configuration file. [source,yaml] -.WebClient TLS configuration file `application.yaml` +.WebClient TLS configuration in `application.yaml` ---- webclient: tls: - #Server part defines settings for server certificate validation and truststore - server: + trust: keystore: passphrase: "password" trust-store: true resource: - resource-path: "keystore.p12" - #Client part defines access to the keystore with client private key or certificate - client: - keystore: - passphrase: "password" - resource: - resource-path: "keystore.p12" + resource-path: "client.p12" ---- Then, in your application code, load the configuration from that file. @@ -304,18 +431,145 @@ Then, in your application code, load the configuration from that file. Config config = Config.create(); WebClient webClient = WebClient.create(config.get("webclient")); ---- -Or you can only create WebClientTls instance based on the config file. + +=== 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` +[source,xml] +---- + + io.helidon.webclient + helidon-webclient-metrics + +---- +* `tracing` +[source,xml] +---- + + io.helidon.webclient + helidon-webclient-tracing + +---- +* `security` +[source,xml] +---- + + io.helidon.webclient + helidon-webclient-security + +---- + +==== Adding a service in your code + +Services can be added in WebClient as shown in the code below. [source,java] -.WebClientTls instance based on `application.yaml` file located on the classpath +---- +//Creates new metric which will count all GET requests and has format of example.metric.GET. +WebClientService clientService = WebClientMetrics.counter() + .methods(Method.GET) + .nameFormat("example.metric.%1$s.%2$s") + .build(); + +//This newly created metric now needs to be registered to WebClient. +WebClient client = WebClient.builder() + .baseUri(url) + .config(config) + .addService(clientService) + .build(); +---- + +==== Adding service in the config file + +Adding service in WebClient can also be done through the `application.yaml` configuration file. + +[source,yaml] +.WebClient Service configuration in `application.yaml` +---- +webclient: + 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. + +[source,java] +.WebClient initialization using the `application.yaml` file located on the classpath ---- Config config = Config.create(); -WebClientTls.builder() - .config(config.get("webclient.tls")) +WebClient webClient = WebClient.create(config.get("webclient")); +---- + +=== 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. + +[source,java] +---- +WebClient client = WebClient.builder() + .baseUri(url) + .config(config.get("client")) + .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. + +[source,yaml] +.Setting up `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: true +---- +Then, in your application code, load the configuration from that file. + +[source,java] +.WebClient initialization using the `application.yaml` file located on the classpath +---- +Config config = Config.create(); +WebClient webClient = WebClient.create(config.get("webclient")); +---- + + + == Reference -* link:https://helidon.io/docs/v2/apidocs/io.helidon.webclient/module-summary.html[Helidon WebClient JavaDoc] +* link:{webclient-javadoc-base-url}.api/module-summary.html[Helidon Webclient API] +* link:{webclient-javadoc-base-url}.http1/module-summary.html[Helidon WebClient HTTP/1.1 Support] +* link:{webclient-javadoc-base-url}.http2/module-summary.html[Helidon WebClient HTTP/2 Support] +* link:{webclient-javadoc-base-url}.dns.resolver.first/module-summary.html[Helidon WebClient DNS Resolver First Support] +* link:{webclient-javadoc-base-url}.dns.resolver.roundrobin/module-summary.html[Helidon WebClient DNS Resolver Round Robin Support] +* link:{webclient-javadoc-base-url}.metrics/module-summary.html[Helidon WebClient Metrics Support] +* link:{webclient-javadoc-base-url}.security/module-summary.html[Helidon WebClient Security Support] +* link:{webclient-javadoc-base-url}.tracing/module-summary.html[Helidon WebClient Tracing Support]