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 HttpClient
, indeed it is based on
this client and inherits its configuration and great features like pooling, HTTP/2 support, pipelining support, etc…
The 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 HttpClient
should
be used.
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
):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>3.4.0.Beta1</version>
</dependency>
-
Gradle (in your
build.gradle
file):
dependencies {
compile 'io.vertx:vertx-web-client:3.4.0.Beta1'
}
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
HttpClient
using Vert.x core, if you’re not already.
Creating a web client
You create an WebClient
instance with default options as follows
import io.vertx.ext.web.client.WebClient
var client = WebClient.create(vertx)
If you want to configure options for the client, you create it as follows
import io.vertx.core.http.HttpClientOptions
import io.vertx.ext.web.client.WebClient
import io.vertx.kotlin.core.http.*
var options = HttpClientOptions(
keepAlive = false)
var client = WebClient.wrap(vertx.createHttpClient(options))
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
import io.vertx.ext.web.client.WebClient
var client = WebClient.create(vertx)
// Send a GET request
client.get(8080, "myserver.mycompany.com", "/some-uri").send({ ar ->
if (ar.succeeded()) {
// Obtain response
var response = ar.result()
println("Received response with status code${response.statusCode()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
// Send a HEAD request
client.head(8080, "myserver.mycompany.com", "/some-uri").send({ ar ->
if (ar.succeeded()) {
// Obtain response
var response = ar.result()
println("Received response with status code${response.statusCode()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
You can add query parameters to the request URI in a fluent fashion
Code not translatable
Any request URI parameter will pre-populate the request
var request = client.get(8080, "myserver.mycompany.com", "/some-uri?param1=param1_value¶m2=param2_value")
// Add param3
request.addQueryParam("param3", "param3_value")
// Overwrite param2
request.setQueryParam("param2", "another_param2_value")
Setting a request URI discards existing query parameters
var request = client.get(8080, "myserver.mycompany.com", "/some-uri")
// Add param1
request.addQueryParam("param1", "param1_value")
// Overwrite param1 and add param2
request.uri("/some-uri?param1=param1_value¶m2=param2_value")
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 sendBuffer
to send a buffer body
// Send a buffer to the server using POST, the content-length header will be set for you
client.post(8080, "myserver.mycompany.com", "/some-uri").sendBuffer(buffer, { ar ->
if (ar.succeeded()) {
// Ok
}
})
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<Buffer>
(e.g a
AsyncFile
is a ReadStream<Buffer>`) with the sendStream
method
Code not translatable
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
import io.vertx.core.file.OpenOptions
import io.vertx.kotlin.core.file.*
fs.open("content.txt", OpenOptions(), { fileRes ->
if (fileRes.succeeded()) {
var fileStream = fileRes.result()
var fileLen = "1024"
// Send the file to the server using POST
client.post(8080, "myserver.mycompany.com", "/some-uri").putHeader("content-length", fileLen).sendStream(fileStream, { ar ->
if (ar.succeeded()) {
// Ok
}
})
}
})
The POST will not be chunked.
Json bodies
Often you’ll want to send Json body requests, to send a JsonObject
use the sendJsonObject
import io.vertx.kotlin.core.json.*
client.post(8080, "myserver.mycompany.com", "/some-uri").sendJsonObject(json {
obj(
"firstName" to "Dale",
"lastName" to "Cooper"
)
}, { ar ->
if (ar.succeeded()) {
// Ok
}
})
In Java, Groovy or Kotlin, you can use the sendJson
method that maps
a POJO (Plain Old Java Object) to a Json object using Json.encode
method
client.post(8080, "myserver.mycompany.com", "/some-uri").sendJson(examples.WebClientExamples.User("Dale", "Cooper"), { ar ->
if (ar.succeeded()) {
// Ok
}
})
Note
|
the Json.encode uses the Jackson mapper to encode the object
to Json.
|
Form submissions
You can send http form submissions bodies with the sendForm
variant.
import io.vertx.core.MultiMap
var form = MultiMap.caseInsensitiveMultiMap()
form.set("firstName", "Dale")
form.set("lastName", "Cooper")
// Submit the form as a form URL encoded body
client.post(8080, "myserver.mycompany.com", "/some-uri").sendForm(form, { ar ->
if (ar.succeeded()) {
// Ok
}
})
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
import io.vertx.core.MultiMap
var form = MultiMap.caseInsensitiveMultiMap()
form.set("firstName", "Dale")
form.set("lastName", "Cooper")
// Submit the form as a multipart form body
client.post(8080, "myserver.mycompany.com", "/some-uri").putHeader("content-type", "multipart/form-data").sendForm(form, { ar ->
if (ar.succeeded()) {
// Ok
}
})
Note
|
at the moment multipart files are not supported, it will likely be supported in a later revision of the API. |
Writing request headers
You can write headers to a request using the headers multi-map as follows:
var request = client.get(8080, "myserver.mycompany.com", "/some-uri")
var headers = request.headers()
headers.set("content-type", "application/json")
headers.set("other-header", "foo")
The headers are an instance of 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
var request = client.get(8080, "myserver.mycompany.com", "/some-uri")
request.putHeader("content-type", "application/json")
request.putHeader("other-header", "foo")
Reusing requests
The send
method can be called multiple times
safely, making it very easy to configure and reuse HttpRequest
objects
var get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send({ ar ->
if (ar.succeeded()) {
// Ok
}
})
// Same request again
get.send({ ar ->
if (ar.succeeded()) {
// Ok
}
})
When you need to mutate a request, the copy
returns a copy of the
request
var get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send({ ar ->
if (ar.succeeded()) {
// Ok
}
})
// Same request again
get.putHeader("an-header", "with-some-value").send({ ar ->
if (ar.succeeded()) {
// Ok
}
})
Timeouts
You can set a timeout for a specific http request using timeout
.
client.get(8080, "myserver.mycompany.com", "/some-uri").timeout(5000).send({ ar ->
if (ar.succeeded()) {
// Ok
} else {
// Might be a timeout when cause is java.util.concurrent.TimeoutException
}
})
If the request does not return any data within the timeout period an exception will be passed to the response handler.
Handling http responses
When the web client sends a request you always deal with a single async result HttpResponse
.
On a success result the callback happens after the response has been received
client.get(8080, "myserver.mycompany.com", "/some-uri").send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
println("Received response with status code${response.statusCode()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
Warning
|
responses are fully buffered, use BodyCodec.pipe
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 BodyCodec
:
-
Plain String
-
Json object
-
Json mapped POJO
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 BodyCodec.jsonObject
To decode a Json object:
import io.vertx.ext.web.codec.BodyCodec
client.get(8080, "myserver.mycompany.com", "/some-uri").as(BodyCodec.jsonObject()).send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
var body = response.body()
println("Received response with status code${response.statusCode()} with body ${body}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
In Java, Groovy or Kotlin, custom Json mapped POJO can be decoded
import io.vertx.ext.web.codec.BodyCodec
client.get(8080, "myserver.mycompany.com", "/some-uri").as(BodyCodec.json(examples.WebClientExamples.User.`class`)).send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
var user = response.body()
println("Received response with status code${response.statusCode()} with body ${user.getFirstName()} ${user.getLastName()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
When large response are expected, use the BodyCodec.pipe
.
This body codec pumps the response body buffers to a WriteStream
and signals the success or the failure of the operation in the async result response
import io.vertx.ext.web.codec.BodyCodec
client.get(8080, "myserver.mycompany.com", "/some-uri").as(BodyCodec.pipe(writeStream)).send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
println("Received response with status code${response.statusCode()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
Finally if you are not interested at all by the response content, the BodyCodec.none
simply discards the entire response body
import io.vertx.ext.web.codec.BodyCodec
client.get(8080, "myserver.mycompany.com", "/some-uri").as(BodyCodec.none()).send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
println("Received response with status code${response.statusCode()}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
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
client.get(8080, "myserver.mycompany.com", "/some-uri").send({ ar ->
if (ar.succeeded()) {
var response = ar.result()
// Decode the body as a json object
var body = response.bodyAsJsonObject()
println("Received response with status code${response.statusCode()} with body ${body}")
} else {
println("Something went wrong ${ar.cause().getMessage()}")
}
})
Warning
|
this is only valid for the response decoded as a buffer. |
RxJava API
The RxJava HttpRequest
provides an rx-ified version of the original API,
the rxSend
method returns a Single<HttpResponse<Buffer>>
that
makes the HTTP request upon subscription, as consequence, the Single
can be subscribed many times.
Code not translatable
The obtained Single
can be composed and chained naturally with the RxJava API
Code not translatable
The same APIs is available
Code not translatable
The sendStream
shall
be preferred for sending bodies Observable<Buffer>
Code not translatable
Upon subscription, the body
will be subscribed and its content used for the request.