Vert.x Web Client(Web客户端)是一个异步的 HTTP 和 HTTP/2 客户端。
Web Client使得发送 HTTP 请求以及从 Web 服务器接收 HTTP 响应变得更加便捷,同时提供了额外的高级功能,例如:
JSON体的编码和解码
请求和响应泵
请求参数的处理
统一的错误处理
提交表单
制作Web Client的目的并非为了替换Vert.x Core中的 HttpClient
,
而是基于该客户端,扩展并保留其便利的设置和特性,例如请求连接池(Pooling),HTTP/2的支持,
流水线/管线的支持等。当您需要对 HTTP 请求和响应做细微粒度控制时,您应当使用
HttpClient
。
另外Web Client并未提供 WebSocket API,此时您应当使用 HttpClient
。
如需使用Vert.x Web Client,请先加入以下依赖:
Maven (在 pom.xml
文件中):
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>3.6.2</version>
</dependency>
Gradle (在 build.gradle
文件中):
dependencies {
compile 'io.vertx:vertx-web-client:3.6.2'
}
Vert.x Web Client使用Vert.x Core的API,如您对此还不熟悉,请先熟悉
HttpClient
的一些基本概念。
您可使用缺省设置创建一个 WebClient
:
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.create(vertx)
您亦可使用配置选项来创建客户端:
require 'vertx-web-client/web_client'
options = {
'userAgent' => "My-App/1.2.3"
}
options['keepAlive'] = false
client = VertxWebClient::WebClient.create(vertx, options)
Web Client配置选项继承自 HttpClient
配置选项,使用时可根据实际情况选择。
如已在程序中创建 HttpClient
,可用以下方式复用:
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.wrap(httpClient)
一般情况下,HTTP GET,OPTIONS以及HEAD请求没有请求体,可用以下方式发送无请求体的HTTP Requests(HTTP请求):
require 'vertx-web-client/web_client'
client = VertxWebClient::WebClient.create(vertx)
# Send a GET request
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
if (ar_err == nil)
# Obtain response
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
# Send a HEAD request
client.head(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
if (ar_err == nil)
# Obtain response
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
您可用以下链式方式向请求URI添加查询参数
client.get(8080, "myserver.mycompany.com", "/some-uri").add_query_param("param", "param_value").send() { |ar_err,ar|
if (ar_err == nil)
# Obtain response
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
在请求URI中的参数将会被预填充
request = client.get(8080, "myserver.mycompany.com", "/some-uri?param1=param1_value¶m2=param2_value")
# Add param3
request.add_query_param("param3", "param3_value")
# Overwrite param2
request.set_query_param("param2", "another_param2_value")
设置请求URI将会自动清除已有的查询参数
request = client.get(8080, "myserver.mycompany.com", "/some-uri")
# Add param1
request.add_query_param("param1", "param1_value")
# Overwrite param1 and add param2
request.uri("/some-uri?param1=param1_value¶m2=param2_value")
如需要发送请求体,可使用相同的API并在最后加上 sendXXX
方法发送相应的请求体。
例如用 sendBuffer
方法发送一个缓冲体:
# 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").send_buffer(buffer) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
有时候我们并不希望将所有数据一次性全部读入内存,因为文件太大或希望同时处理多个请求,希望每个请求仅使用最小的内存。出于此目的,Web Client可用
sendStream
方法发送流式数据 ReadStream<Buffer>
(例如 AsyncFile
便是一个 ReadStream<Buffer>
):
# When the stream len is unknown sendStream sends the file to the server using chunked transfer encoding
client.post(8080, "myserver.mycompany.com", "/some-uri").send_stream(stream) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
Web Client会为您设置好传输泵以平滑传输流。如果流长度未知则使用分块传输(chunked transfer)。
如已知流的大小,可在HTTP协议头中设置 content-length
属性
fs.open("content.txt", {
}) { |fileRes_err,fileRes|
if (fileRes_err == nil)
fileStream = fileRes
fileLen = "1024"
# Send the file to the server using POST
client.post(8080, "myserver.mycompany.com", "/some-uri").put_header("content-length", fileLen).send_stream(fileStream) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
end
}
此时POST方法不会使用分块传输。
有时您需要在请求体中使用JSON格式,可使用 sendJsonObject
方法发送 JsonObject
:
client.post(8080, "myserver.mycompany.com", "/some-uri").send_json_object({
'firstName' => "Dale",
'lastName' => "Cooper"
}) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
在Java,Groovy以及Kotlin语言中,您亦可使用 sendJson
方法发送POJO(Plain Old Java Object),该方法会自动调用 Json.encode
方法将 POJO 映射为 JSON:
client.post(8080, "myserver.mycompany.com", "/some-uri").send_json(Java::ExamplesWebClientExamples::User.new("Dale", "Cooper")) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
Note
|
Json.encode 方法使用Jackson的 mapper将 POJO 映射成 JSON。
|
您可使用 sendForm
方法发送HTTP表单。
require 'vertx/multi_map'
form = Vertx::MultiMap.case_insensitive_multi_map()
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").send_form(form) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
缺省情况下,提交表单的请求头中的 content-type
属性值为 application/x-www-form-urlencoded
,您亦可将其设置为 multipart/form-data
:
require 'vertx/multi_map'
form = Vertx::MultiMap.case_insensitive_multi_map()
form.set("firstName", "Dale")
form.set("lastName", "Cooper")
# Submit the form as a multipart form body
client.post(8080, "myserver.mycompany.com", "/some-uri").put_header("content-type", "multipart/form-data").send_form(form) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
If you want to upload files and send attributes, you can create a MultipartForm
and
use sendMultipartForm
.
require 'vertx-web-common/multipart_form'
form = VertxWebCommon::MultipartForm.create().attribute("imageDescription", "a very nice image").binary_file_upload("imageFile", "image.jpg", "/path/to/image", "image/jpeg")
# Submit the form as a multipart form body
client.post(8080, "myserver.mycompany.com", "/some-uri").send_multipart_form(form) { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
您可使用以下方式填充请求头:
request = client.get(8080, "myserver.mycompany.com", "/some-uri")
headers = request.headers()
headers.set("content-type", "application/json")
headers.set("other-header", "foo")
此处 Headers 是一个 MultiMap
对象,提供了增加、设置以及删除头属性操作的入口。HTTP头的某些特定属性允许设置多个值。
您亦可通过 putHeader
方法写入头属性:
request = client.get(8080, "myserver.mycompany.com", "/some-uri")
request.put_header("content-type", "application/json")
request.put_header("other-header", "foo")
send
方法可被重复多次调用,这使得配置以及重用
HttpRequest
对象变得更加便捷:
get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send() { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
# Same request again
get.send() { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
请注意, HttpRequest
对象是可变的。
所以在修改缓存中的对象之前,您应当使用 copy
方法先复制一份拷贝:
get = client.get(8080, "myserver.mycompany.com", "/some-uri")
get.send() { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
# The "get" request instance remains unmodified
get.copy().put_header("a-header", "with-some-value").send() { |ar_err,ar|
if (ar_err == nil)
# Ok
end
}
您可通过 timeout
方法设置超时时间。
client.get(8080, "myserver.mycompany.com", "/some-uri").timeout(5000).send() { |ar_err,ar|
if (ar_err == nil)
# Ok
else
# Might be a timeout when cause is java.util.concurrent.TimeoutException
end
}
若请求在设定时间内没返回任何数据,则一个超时异常将会传递给响应处理代码。
Web Client请求发送之后,返回的结果将会被包装在异步结果 HttpResponse
中。
当响应被成功接收到之后,相应的回调函数将会被触发。
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
if (ar_err == nil)
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
Warning
|
缺省状况下,响应会被完全缓冲读入内存,请用 BodyCodec.pipe 方法将响应写入流。
|
缺省状况下,响应以缓冲形式提供,并不提供任何形式的解码。
可用 BodyCodec
将响应定制成以下类型:
普通字符串
JSON对象
将JSON映射成POJO
响应体编解码器对二进制数据流解码,以节省您在响应处理中的代码。
使用 BodyCodec.jsonObject
将结果解码为JSON对象:
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.json_object()).send() { |ar_err,ar|
if (ar_err == nil)
response = ar
body = response.body()
puts "Received response with status code#{response.status_code()} with body #{body}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
在Java,Groovy以及Kotlin语言中,JSON对象可被解码映射成POJO:
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.json(Java::ExamplesWebClientExamples::User::class)).send() { |ar_err,ar|
if (ar_err == nil)
response = ar
user = response.body()
puts "Received response with status code#{response.status_code()} with body #{user.get_first_name()} #{user.get_last_name()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
当响应结果较大时,请使用 BodyCodec.pipe
方法。响应体编解码器将响应结果压入 WriteStream
并在最后发出成功或失败的信号。
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.pipe(writeStream)).send() { |ar_err,ar|
if (ar_err == nil)
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
最后,如您对响应结果不感兴趣,可用 BodyCodec.none
废弃响应体。
require 'vertx-web-common/body_codec'
client.get(8080, "myserver.mycompany.com", "/some-uri").as(VertxWebCommon::BodyCodec.none()).send() { |ar_err,ar|
if (ar_err == nil)
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
若无法预知响应内容类型,您依旧可以在获取结果之后,用 bodyAsXXX()
方法将其转换成特定的类型
client.get(8080, "myserver.mycompany.com", "/some-uri").send() { |ar_err,ar|
if (ar_err == nil)
response = ar
# Decode the body as a json object
body = response.body_as_json_object()
puts "Received response with status code#{response.status_code()} with body #{body}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
Warning
|
这种方式仅对响应结果为缓冲体有效。 |
缺省状况下,客户端将会依照30x状态码自动重定向,您可使用 WebClientOptions
予以配置:
require 'vertx-web-client/web_client'
# Change the default behavior to not follow redirects
client = VertxWebClient::WebClient.create(vertx, {
'followRedirects' => false
})
客户端将会执行最多达`16`次重定向,该参数亦可在 WebClientOptions
配置:
require 'vertx-web-client/web_client'
# Follow at most 5 redirections
client = VertxWebClient::WebClient.create(vertx, {
'maxRedirects' => 5
})
Vert.x Web Client可用与 HttpClient
相同方式配置HTTPS协议。
您可对每个请求单独设置:
client.get(443, "myserver.mycompany.com", "/some-uri").ssl(true).send() { |ar_err,ar|
if (ar_err == nil)
# Obtain response
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}
或使用绝对路径:
client.get_abs("https://myserver.mycompany.com:4043/some-uri").send() { |ar_err,ar|
if (ar_err == nil)
# Obtain response
response = ar
puts "Received response with status code#{response.status_code()}"
else
puts "Something went wrong #{ar_err.get_message()}"
end
}