x-web-client.4.5.10.source-code.index.adoc Maven / Gradle / Ivy
The newest version!
= Vert.x Web Client
:toc: left
:lang: $lang
:$lang: $lang
Vert.x Web Client is an asynchronous HTTP and HTTP/2 client.
The Web Client makes easy to do HTTP request/response interactions with a web server, and provides advanced
features like:
* Json body encoding / decoding
* request/response pumping
* request parameters
* unified error handling
* form submissions
The Web Client does not deprecate the Vert.x Core {@link io.vertx.core.http.HttpClient}, indeed it is based on
this client and inherits its configuration and great features like pooling, HTTP/2 support, pipelining support, etc...
The {@link io.vertx.core.http.HttpClient} should be used when fine grained control over the HTTP
requests/responses is necessary.
The Web Client does not provide a WebSocket API, the Vert.x Core {@link io.vertx.core.http.HttpClient} should
be used. It also does not handle cookies at the moment.
== Using the Web Client
To use Vert.x Web Client, add the following dependency to the _dependencies_ section of your build descriptor:
* Maven (in your `pom.xml`):
[source,xml,subs="+attributes"]
----
io.vertx
vertx-web-client
${maven.version}
----
* Gradle (in your `build.gradle` file):
[source,groovy,subs="+attributes"]
----
dependencies {
compile 'io.vertx:vertx-web-client:${maven.version}'
}
----
== Re-cap on Vert.x core HTTP client
Vert.x Web Client uses the API from Vert.x core, so it's well worth getting familiar with the basic concepts of using
{@link io.vertx.core.http.HttpClient} using Vert.x core, if you're not already.
== Creating a Web Client
You create an {@link io.vertx.ext.web.client.WebClient} instance with default options as follows
[source,$lang]
----
{@link examples.WebClientExamples#create}
----
If you want to configure options for the client, you create it as follows
[source,$lang]
----
{@link examples.WebClientExamples#createFromOptions}
----
Web Client options inherit Http Client options so you can set any one of them.
If your already have an HTTP Client in your application you can also reuse it
[source,$lang]
----
{@link examples.WebClientExamples#wrap(io.vertx.core.http.HttpClient)}
----
IMPORTANT: In most cases, a Web Client should be created once on application startup and then reused.
Otherwise you lose a lot of benefits such as connection pooling and may leak resources if instances are not closed properly.
== Making requests
=== Simple requests with no body
Often, you’ll want to make HTTP requests with no request body. This is usually the case with HTTP GET, OPTIONS
and HEAD requests
[source,$lang]
----
{@link examples.WebClientExamples#simpleGetAndHead}
----
You can add query parameters to the request URI in a fluent fashion
[source,$lang]
----
{@link examples.WebClientExamples#simpleGetWithParams(io.vertx.ext.web.client.WebClient)}
----
Any request URI parameter will pre-populate the request
[source,$lang]
----
{@link examples.WebClientExamples#simpleGetWithInitialParams(io.vertx.ext.web.client.WebClient)}
----
Setting a request URI discards existing query parameters
[source,$lang]
----
{@link examples.WebClientExamples#simpleGetOverwritePreviousParams(io.vertx.ext.web.client.WebClient)}
----
=== Writing request bodies
When you need to make a request with a body, you use the same API and call then `sendXXX` methods
that expects a body to send.
Use {@link io.vertx.ext.web.client.HttpRequest#sendBuffer} to send a buffer body
[source,$lang]
----
{@link examples.WebClientExamples#sendBuffer(io.vertx.ext.web.client.WebClient, io.vertx.core.buffer.Buffer)}
----
Sending a single buffer is useful but often you don't want to load fully the content in memory because
it may be too large or you want to handle many concurrent requests and want to use just the minimum
for each request. For this purpose the Web Client can send `ReadStream` (e.g a
{@link io.vertx.core.file.AsyncFile} is a ReadStream`) with the {@link io.vertx.ext.web.client.HttpRequest#sendStream} method
[source,$lang]
----
{@link examples.WebClientExamples#sendStreamChunked(io.vertx.ext.web.client.WebClient, io.vertx.core.streams.ReadStream)}
----
The Web Client takes care of setting up the transfer pump for you. Since the length of the stream is not know
the request will use chunked transfer encoding .
When you know the size of the stream, you shall specify before using the `content-length` header
[source,$lang]
----
{@link examples.WebClientExamples#sendStream(io.vertx.ext.web.client.WebClient, io.vertx.core.file.FileSystem)}
----
The POST will not be chunked.
==== Json bodies
Often you’ll want to send Json body requests, to send a {@link io.vertx.core.json.JsonObject}
use the {@link io.vertx.ext.web.client.HttpRequest#sendJsonObject(io.vertx.core.json.JsonObject, io.vertx.core.Handler)}
[source,$lang]
----
{@link examples.WebClientExamples#sendJsonObject(io.vertx.ext.web.client.WebClient)}
----
In Java, Groovy or Kotlin, you can use the {@link io.vertx.ext.web.client.HttpRequest#sendJson} method that maps
a POJO (Plain Old Java Object) to a Json object using {@link io.vertx.core.json.Json#encode(java.lang.Object)}
method
[source,$lang]
----
{@link examples.WebClientExamples#sendJsonPOJO(io.vertx.ext.web.client.WebClient)}
----
NOTE: the {@link io.vertx.core.json.Json#encode(java.lang.Object)} uses the Jackson mapper to encode the object
to Json.
==== Form submissions
You can send http form submissions bodies with the {@link io.vertx.ext.web.client.HttpRequest#sendForm(io.vertx.core.MultiMap, io.vertx.core.Handler)}
variant.
[source,$lang]
----
{@link examples.WebClientExamples#sendForm(io.vertx.ext.web.client.WebClient)}
----
By default the form is submitted with the `application/x-www-form-urlencoded` content type header. You can set
the `content-type` header to `multipart/form-data` instead
[source,$lang]
----
{@link examples.WebClientExamples#sendMultipart(io.vertx.ext.web.client.WebClient)}
----
If you want to upload files and send attributes, you can create a {@link io.vertx.ext.web.multipart.MultipartForm} and
use {@link io.vertx.ext.web.client.HttpRequest#sendMultipartForm(io.vertx.ext.web.multipart.MultipartForm, io.vertx.core.Handler)}.
[source,$lang]
----
{@link examples.WebClientExamples#sendMultipartWithFileUpload(io.vertx.ext.web.client.WebClient)}
----
=== Writing request headers
You can write headers to a request using the headers multi-map as follows:
[source,$lang]
----
{@link examples.WebClientExamples#sendHeaders1(io.vertx.ext.web.client.WebClient)}
----
The headers are an instance of {@link io.vertx.core.MultiMap} which provides operations for adding,
setting and removing entries. Http headers allow more than one value for a specific key.
You can also write headers using putHeader
[source,$lang]
----
{@link examples.WebClientExamples#sendHeaders2(io.vertx.ext.web.client.WebClient)}
----
=== Configure the request to add authentication.
Authentication can be performed manually by setting the correct headers, or, using our predefined methods
(We strongly suggest having HTTPS enabled, especially for authenticated requests):
In basic HTTP authentication, a request contains a header field of the form `Authorization: Basic `,
where credentials is the base64 encoding of id and password joined by a colon.
You can configure the request to add basic access authentication as follows:
[source,$lang]
----
{@link examples.WebClientExamples#addBasicAccessAuthentication(io.vertx.ext.web.client.WebClient)}
----
In OAuth 2.0, a request contains a header field of the form `Authorization: Bearer `,
where bearerToken is the bearer token issued by an authorization server to access protected resources.
You can configure the request to add bearer token authentication as follows:
[source,$lang]
----
{@link examples.WebClientExamples#addBearerTokenAuthentication(io.vertx.ext.web.client.WebClient)}
----
=== Reusing requests
The {@link io.vertx.ext.web.client.HttpRequest#send(io.vertx.core.Handler)} method can be called multiple times
safely, making it very easy to configure and reuse {@link io.vertx.ext.web.client.HttpRequest} objects
[source,$lang]
----
{@link examples.WebClientExamples#multiGet(io.vertx.ext.web.client.WebClient)}
----
Beware though that {@link io.vertx.ext.web.client.HttpRequest} instances are mutable.
Therefore you should call the {@link io.vertx.ext.web.client.HttpRequest#copy()} method before modifying a cached instance.
[source,$lang]
----
{@link examples.WebClientExamples#multiGetCopy(io.vertx.ext.web.client.WebClient)}
----
=== Timeouts
You can set a connect timeout for a specific http request using {@link io.vertx.ext.web.client.HttpRequest#connectTimeout(long)}.
[source,$lang]
----
{@link examples.WebClientExamples#connectTimeout(io.vertx.ext.web.client.WebClient)}
----
If the client cannot obtain a connection to the server within the timeout period an exception will be passed to the response
handler.
You can set an idle timeout for a specific http request using {@link io.vertx.ext.web.client.HttpRequest#idleTimeout(long)}.
[source,$lang]
----
{@link examples.WebClientExamples#idleTimeout(io.vertx.ext.web.client.WebClient)}
----
If the request does not return any data within the timeout period an exception will be passed to the response
handler.
You can set both timeouts using {@link io.vertx.ext.web.client.HttpRequest#timeout(long)}
[source,$lang]
----
{@link examples.WebClientExamples#timeout(io.vertx.ext.web.client.WebClient)}
----
== Handling http responses
When the Web Client sends a request you always deal with a single async result {@link io.vertx.ext.web.client.HttpResponse}.
On a success result the callback happens after the response has been received
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponse(io.vertx.ext.web.client.WebClient)}
----
[CAUTION]
====
By default, a Vert.x Web Client request ends with an error only if something wrong happens at the network level.
In other words, a `404 Not Found` response, or a response with the wrong content type, are *not* considered as failures.
Use <> if you want the Web Client to perform sanity checks automatically.
====
WARNING: Responses are fully buffered, use {@link io.vertx.ext.web.codec.BodyCodec#pipe(io.vertx.core.streams.WriteStream)}
to pipe the response to a write stream
=== Decoding responses
By default the Web Client provides an http response body as a `Buffer` and does not apply
any decoding.
Custom response body decoding can be achieved using {@link io.vertx.ext.web.codec.BodyCodec}:
* Plain String
* Json object
* Json mapped POJO
* {@link io.vertx.core.streams.WriteStream}
A body codec can decode an arbitrary binary data stream into a specific object instance, saving you the decoding
step in your response handlers.
Use {@link io.vertx.ext.web.codec.BodyCodec#jsonObject()} To decode a Json object:
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAsJsonObject(io.vertx.ext.web.client.WebClient)}
----
In Java, Groovy or Kotlin, custom Json mapped POJO can be decoded
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAsJsonPOJO(io.vertx.ext.web.client.WebClient)}
----
When large response are expected, use the {@link io.vertx.ext.web.codec.BodyCodec#pipe(io.vertx.core.streams.WriteStream)}.
This body codec pumps the response body buffers to a {@link io.vertx.core.streams.WriteStream}
and signals the success or the failure of the operation in the async result response
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAsWriteStream(io.vertx.ext.web.client.WebClient, io.vertx.core.streams.WriteStream)}
----
It becomes frequent to see API returning a stream of JSON objects. For example, the Twitter API can provides a feed of
tweets. To handle this use case you can use {@link io.vertx.ext.web.codec.BodyCodec#jsonStream(io.vertx.core.parsetools.JsonParser)}.
You pass a JSON parser that emits the read JSON streams from the HTTP response:
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAsJsonStream(io.vertx.ext.web.client.WebClient)}
----
Finally if you are not interested at all by the response content, the {@link io.vertx.ext.web.codec.BodyCodec#none()}
simply discards the entire response body
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAndDiscard(io.vertx.ext.web.client.WebClient)}
----
When you don't know in advance the content type of the http response, you can still use the `bodyAsXXX()` methods
that decode the response to a specific type
[source,$lang]
----
{@link examples.WebClientExamples#receiveResponseAsBufferDecodeAsJsonObject(io.vertx.ext.web.client.WebClient)}
----
WARNING: this is only valid for the response decoded as a buffer.
[[http-response-expectations]]
=== Response expectations
By default, a Vert.x Web Client request ends with an error only if something wrong happens at the network level.
In other words, you must perform sanity checks manually after the response is received:
[source,$lang]
----
{@link examples.WebClientExamples#manualSanityChecks(io.vertx.ext.web.client.WebClient)}
----
You can trade flexibility for clarity and conciseness using _response expectations_.
{@link io.vertx.core.http.HttpResponseExpectation Response expectations} can fail a request when the response does
not match a criteria.
The Web Client can reuse the Vert.x HTTP Client predefined expectations:
[source,$lang]
----
{@link examples.WebClientExamples#usingPredefinedPredicates(io.vertx.ext.web.client.WebClient)}
----
You can also create custom expectations when existing expectations don't fit your needs:
[source,$lang]
----
{@link examples.WebClientExamples#usingPredicates(io.vertx.ext.web.client.WebClient)}
----
==== Predefined expectations
As a convenience, the Vert.x HTTP Client ships a few expectations for common uses cases that also applies to the
Web Client.
For status codes, e.g. {@link io.vertx.core.http.HttpResponseExpectation#SC_SUCCESS} to verify that the
response has a `2xx` code, you can also create a custom one:
[source,$lang]
----
{@link examples.WebClientExamples#usingSpecificStatus(io.vertx.ext.web.client.WebClient)}
----
For content types, e.g. {@link io.vertx.core.http.HttpResponseExpectation#JSON} to verify that the
response body contains JSON data, you can also create a custom one:
[source,$lang]
----
{@link examples.WebClientExamples#usingSpecificContentType(io.vertx.ext.web.client.WebClient)}
----
Please refer to the {@link io.vertx.core.http.HttpResponseExpectation} documentation for a full list of predefined predicates.
ifeval::["$lang" == "java"]
==== Creating custom failures
By default, response expectations (including the predefined ones) use a default error converter which discards
the body and conveys a simple message. You can customize the exception class by mapping the failure:
[source,$lang]
----
{@link examples.WebClientExamples#predicateCustomError()}
----
Many web APIs provide details in error responses.
For example, the https://developer.marvel.com/docs[Marvel API] uses this JSON object format:
[source,javascript]
----
{
"code": "InvalidCredentials",
"message": "The passed API key is invalid."
}
----
To avoid losing this information, it is possible to transform response body:
[source,$lang]
----
{@link examples.WebClientExamples#predicateCustomErrorWithBody()}
----
WARNING: creating exception in Java can have a performance cost when it captures a stack trace, so you might want
to create exceptions that do not capture the stack trace. By default, exceptions are reported using
an exception that does not capture the stack trace.
endif::[]
=== Handling 30x redirections
By default the client follows redirections, you can configure the default behavior in the {@link io.vertx.ext.web.client.WebClientOptions}:
[source,$lang]
----
{@link examples.WebClientExamples#testClientDisableFollowRedirects(io.vertx.core.Vertx)}
----
The client will follow at most `16` requests redirections, it can be changed in the same options:
[source,$lang]
----
{@link examples.WebClientExamples#testClientChangeMaxRedirects(io.vertx.core.Vertx)}
----
NOTE: For security reason, client won't follow redirects for request with methods different from GET or HEAD
== HTTP response caching
Vert.x web offers an HTTP response caching facility; to use it, you create a {@link io.vertx.ext.web.client.CachingWebClient}.
=== Creating a caching web client
[source,$lang]
----
{@link examples.CachingWebClientExamples#create(io.vertx.core.Vertx)}
----
=== Configuring what is cached
By default, a caching web client will only cache a response from a `GET` method that has a status code of `200`, `301`, or `404`.
Additionally, responses that contain a `Vary` header will not be cached by default.
This can be configured by passing {@link io.vertx.ext.web.client.CachingWebClientOptions} during client creation.
[source,$lang]
----
{@link examples.CachingWebClientExamples#createWithOptions(io.vertx.core.Vertx)}
----
Responses that contain the `private` directive in the `Cache-Control` header will not be cached unless the client is also
a {@link io.vertx.ext.web.client.WebClientSession}. See <<_handling_private_responses>>.
=== Providing an external store
When storing responses, the default caching client will use a local `Map`.You may provide your own store implementation to store responses.
To do so, implement {@link io.vertx.ext.web.client.spi.CacheStore}, and then you can provide it when creating your client.
[source,$lang]
----
{@link examples.CachingWebClientExamples#createWithCustomStore(io.vertx.core.Vertx)}
----
=== Handling private responses
To enable private response caching, the {@link io.vertx.ext.web.client.CachingWebClient} can be combined with the
{@link io.vertx.ext.web.client.WebClientSession}. When this is done, public responses, those with the `public` directive
in the `Cache-Control` header, will be cached in the {@link io.vertx.ext.web.client.spi.CacheStore} the client was
created with. Private responses, those with the `private` directive in the `Cache-Control` header, will be cached in
with the session to ensure the cached response is not leaked to other users (sessions).
To create a client that can cache private responses, pass a {@link io.vertx.ext.web.client.CachingWebClient} to
a {@link io.vertx.ext.web.client.WebClientSession}.
[source,$lang]
----
{@link examples.CachingWebClientExamples#createWithSession(io.vertx.core.Vertx)}
----
== URI templates
URI templates provide an alternative to HTTP request string URIs based on the https://datatracker.ietf.org/doc/html/rfc6570[URI Template RFC 6570].
You can read the Vert.x URI template link:{../../vertx-uri-template/java/}[documentation] to learn more about it.
You can create a `HttpRequest` with a `UriTemplate` URI instead of a Java string URI
first parse the template string to a `UriTemplate`
[source,$lang]
----
UriTemplate REQUEST_URI = UriTemplate.of("/some-uri?{param}");
----
then use it to create a request
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplate1}
----
set the template parameter
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplate2}
----
and finally send the request as usual
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplate3}
----
or fluently
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplateFluent}
----
=== URI templates expansion
Before sending the request, Vert.x WebClient expands the template to a string with the request template parameters.
String expansion takes care of encoding the parameters for you,
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplateEncoding}
----
The default expansion syntax is known as _simple string expansion_, there are other expansion syntax available
- _path segment expansion_ (`{/varname}`)
- _form-style query expansion_ (`{?varname}`)
- etc...
You can refer to the Vert.x URI Template documentation (add link when available) for a complete overview of the various expansion styles.
As mandated by the RFC, template expansion will replace missing template parameters by empty strings. You can change
this behavior to fail instead:
[source,$lang]
----
{@link examples.WebClientExamples#testConfigureTemplateExpansion}
----
=== Template parameter values
Template parameters accept `String`, `List` and `Map` values.
The expansion of each kind depends on the expansion style (denoted by the `?` prefix) , here is an example of the _query_ parameter that is exploded
(denoted by the `*` suffix) and expanded using form-style query expansion:
[source,$lang]
----
{@link examples.WebClientExamples#testUriTemplateMapExpansion}
----
Form-style query expansion expands the variable `{?query*}` as `?color=red&width=30&height=50` per definition.
== Using HTTPS
Vert.x Web Client can be configured to use HTTPS in exactly the same way as the Vert.x {@link io.vertx.core.http.HttpClient}.
You can specify the behavior per request
[source,$lang]
----
{@link examples.WebClientExamples#testOverrideRequestSSL(io.vertx.ext.web.client.WebClient)}
----
Or using create methods with absolute URI argument
[source,$lang]
----
{@link examples.WebClientExamples#testAbsRequestSSL(io.vertx.ext.web.client.WebClient)}
----
== Sessions management
Vert.x web offers a web session management facility; to use it, you create a
{@link io.vertx.ext.web.client.WebClientSession} for every user (session) and use it instead of the
{@link io.vertx.ext.web.client.WebClient}.
=== Creating a WebClientSession
You create a {@link io.vertx.ext.web.client.WebClientSession} instance as follows
[source,$lang]
----
{@link examples.WebClientSessionExamples#create}
----
=== Making requests
Once created, a {@link io.vertx.ext.web.client.WebClientSession} can be used instead of a
{@link io.vertx.ext.web.client.WebClient} to do HTTP(s) requests and automatically manage any cookies received from the server(s)
you are calling.
=== Setting session level headers
You can set any session level headers to be added to every request as follows:
[source,$lang]
----
{@link examples.WebClientSessionExamples#setHeaders}
----
The headers will then be added to every request; notice that these headers will be sent to all hosts; if you need
to send different headers to different hosts, you have to add them manually to every single request and not to the
{@link io.vertx.ext.web.client.WebClientSession}.
== OAuth2 security
Vert.x web offers a web session management facility; to use it, you create a
{@link io.vertx.ext.web.client.OAuth2WebClient} for every user (session) and use it instead of the
{@link io.vertx.ext.web.client.WebClient}.
=== Creating an Oauth2 Client
You create a {@link io.vertx.ext.web.client.OAuth2WebClient} instance as follows
[source,$lang]
----
{@link examples.WebClientOauth2Examples#create}
----
Client's can also take advantage of OpenId Service discovery to fully configure the client, for example to connect to
a real keycloak server one can just do:
[source,$lang]
----
{@link examples.WebClientOauth2Examples#discovery}
----
=== Making requests
Once created, a {@link io.vertx.ext.web.client.OAuth2WebClient} can be used instead of a
{@link io.vertx.ext.web.client.WebClient} to do HTTP(s) requests and automatically manage any cookies received from the server(s)
you are calling.
=== Avoid expired tokens
You can set token expiration leeway to every request as follows:
[source,$lang]
----
{@link examples.WebClientOauth2Examples#leeway}
----
If a request is to be performed the current active user object is checked for expiration with the extra given leeway.
This will allow the client to perform a token refresh if needed, instead of aborting the operation with an error.
Request may still fail due to expired tokens since the expiration calculation will still be performed at the server
side. To reduce the work on the user side, the client can be configured to perform a **single** retry on requests that
return status code **401** (Forbidden). When the option flag: `refreshTokenOnForbidden` is set to `true`, then the
client will perform a new token request retry the original request before passing the response to the user
handler/promise.
[source,$lang]
----
{@link examples.WebClientOauth2Examples#renewTokenOnForbidden}
----
ifeval::["$lang" == "java"]
include::override/rxjava3.adoc[]
endif::[]
== Domain sockets
Since 3.7.1 the Web Client supports domain sockets, e.g you can interact with the https://docs.docker.com/engine/reference/commandline/dockerd/[local Docker daemon].
To achieve this, the {@link io.vertx.core.Vertx} instance must be created using a native transport, you can read
the Vert.x core documentation that explains it clearly.
[source,$lang]
----
{@link examples.WebClientExamples#testSocketAddress}
----