服务器推送
服务器推送是HTTP/2的新特性,此特性针对单个客户端请求,可以并发的发送多个响应。在服务器处理一个请求时,服务器可以推gudp一个请求/响应到客户端:
HttpServerResponse response =request.response();
// Push main.js to the client
response.push(HttpMethod.GET,"/main.js", ar -> {
if(ar.succeeded()) {
//The server is ready to push the response
HttpServerResponse pushedResponse = ar.result();
//Send main.js response
pushedResponse.
putHeader("content-type", "application/json").
end("alert(\"Push response hello\")");
} else{
System.out.println("Could not push client resource " +ar.cause());
}
});
// Send the requested resource
response.sendFile("
在服务器准务地推送响应时,推送响应处理器将会被调用,处理器可以发送响应。如客户端取消了推送,推送响应处理器会收到一个失败。因为main.js已经在缓存中所以不在需要。在初始化响应结束前,push必须被调用,然而推送的响应可在随后写入。
HTTP压缩
Vert.x支持HTTP压缩。这意味着在响应体发回到客户端之前,Vert.x可以自动地压缩响应体。如果不支持HTTP压缩,被发回的响应将不会压缩响应体。这就是说支持HTTP压缩的客户端将发回压缩的HTTP响应,不支持压缩的客户端将发会非压缩的响应。用setCompressionSupported方法启用compression.默认压缩是禁用的。在启用HTTP压缩后,服务器将检查客户端是否包含Accept-Encoding头以支持HTTP压缩。通常是使用deflate和gzip算法。Vert.x两种都支持。如果这样的头被发现,服务器将自采一种支持的压缩方式压缩响应头,然后发回客户端。注意,压缩可能会减少网络吞吐量,但是消耗服务器CPU。
创建HTTP客户端
可使用默认选项创建HTTP客户端:
HttpClient client = vertx.createHttpClient();
如果想给客户端配置选项,工以这样做:
HttpClientOptions options = newHttpClientOptions().setKeepAlive(false);
HttpClient client =vertx.createHttpClient(options);
Vert.x支持TLSh2上的HTTP/2,t TCP上的h2c.
默 认的HTTP客 户端执行http/1.1请求,要执行HTTP/2请求,setProtocolVersion必须设置为HTTP——2。
对于 h2 请求,TLS必须启用ALPN:
HttpClientOptions options = newHttpClientOptions().
setProtocolVersion(HttpVersion.HTTP_2).
setSsl(true).
setUseAlpn(true).
setTrustAll(true);
对于h2c请求,TLS必须禁用,客户将执行http/1.1请求并升级到HTTP/2:
HttpClientOptions options = newHttpClientOptions().setProtocolVersion(HttpVersion.HTTP_2);
HttpClient client =vertx.createHttpClient(options);
H2c连接可直接建立,例如连接开始于前一个确认。在setHttp2ClearTextUpgrade选项设置为false时:在连接建立后,客户将发送HTTP/2连接前导信息并且期望从服务器得到相同的信息。
HTTP服务器可能不无支持HTTP/2,当带有版本信息的响应到达后将会检查实际的版本。
在客户端连接到HTTP/2服务器时,客户端将初始参数(initial settings)发送到服务器。设置对怎样使用连接进行定义,针对客户端的默认初始设置在HTTP/2标准进行了定义。
记录网络客户端行为
为了调试的目的,可以记录网络活动日志。
HttpClientOptions options = newHttpClientOptions().setLogActivity(true);
HttpClient client =vertx.createHttpClient(options);
参见记录网络活动日志小节获取详细说明。
发送请求
HTTP客户端非常灵活,有多种方式可以发送请求。
通常可以通过HTTP客户端发送多个请求到相同的主机相同的端口。
为了避免每次以重复主机与端口发送请求,你可以设置默认的主机和端口。
HttpClientOptions options = newHttpClientOptions().setDefaultHost("wibble.com");
// Can also set default port if you want...
HttpClient client = vertx.createHttpClient(options);
client.getNow("/some-uri", response-> {
System.out.println("Received response with status code " +response.statusCode());
});
相反,如果发现用相同的客户端向不同的主机和端口发送了大量请求,可以在发送请求时指定主机和端品解决。
HttpClient client = vertx.createHttpClient();
// Specify both port and host name
client.getNow(8080,"myserver.mycompany.com", "/some-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
});
// This time use the default port 80 butspecify the host name
client.getNow("foo.othercompany.com","/other-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
});
所有指定主机和端口的方法都支持使用客户端以不同的方式发送请求。
没有请求体的简单请求
通常,需要发送没有请求体的HTTP请求。这通常是使用HTTP GET,OPTIONS和HEAD方法的请求。最简单的方式是使用Vert.x HTTP客户端中的带Now前缀的方法。如getNow.这此方法创建请求并在单一的方法调用中进行发送。这此方法还可以接收一个处理器参数,处理器在HTTP响应返回客户端时被调用。
HttpClient client = vertx.createHttpClient();
// Send a GET request
client.getNow("/some-uri", response-> {
System.out.println("Received response with status code " +response.statusCode());
});
// Send a GET request
client.headNow("/other-uri", response-> {
System.out.println("Received response with status code " +response.statusCode());
});
编写通用请求
在其他时候,在发送时并不知道请求方式,直到运行时才知道。针对这种情况,Vert.x提供了通用目的的请求方法,如request方法,此方法在运行时可以指定HTTP方法:
HttpClient client = vertx.createHttpClient();
client.request(HttpMethod.GET,"some-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
}).end();
client.request(HttpMethod.POST, "foo-uri",response -> {
System.out.println("Received response with status code " +response.statusCode());
}).end("some-data");
编写请求体
有时,需要编写带请求体的请求,或许需要在发送请求时需要添加请求头。为此可以调用指定的请求方法,如post或者通用目的的请求方法request. 这此方法不立即发送请求,而是返回一个HttpClientRequest的实例,此实例可用于写入请求体和请求头。下面是一个采用post方法发关带有请求体的HTTP请求。
HttpClient client = vertx.createHttpClient();
HttpClientRequest request =client.post("some-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
});
// Now do stuff with the request
request.putHeader("content-length","1000");
request.putHeader("content-type","text/plain");
request.write(body);
// Make sure the request is ended when you'redone with it
request.end();
// Or fluently:
client.post("some-uri", response-> {
System.out.println("Received response with status code " +response.statusCode());
}).putHeader("content-length","1000").putHeader("content-type","text/plain").write(body).end();
// Or event more simply:
client.post("some-uri", response-> {
System.out.println("Received response with status code " +response.statusCode());
}).putHeader("content-type","text/plain").end(body);
存在使用UTF-8编码及指定特定编码写字符串和缓冲器(Buffer)的方法。
request.write("some data");
// Write string encoded in specific encoding
request.write("some other data","UTF-16");
// Write a buffer
Buffer buffer = Buffer.buffer();
buffer.appendInt(123).appendLong(245l);
request.write(buffer);
如果仅想写一个字符串或缓冲器给HTTP请求,可以调用end方法,end方法先写入后结束。
request.end("some simple data");
// Write buffer and end the request (send it)in a single call
Buffer buffer =Buffer.buffer().appendDouble(12.34d).appendLong(432l);
request.end(buffer);
在你向请求写数据时,第一次调用write方法时会导致请求头发送到服务器。实际的发送是异步的,实际写可能会在调用方法返回后某个时间发生。非块模式且带有HTTP请求体的请求需要提供Content-Length头。因此,如果在发送非块模式的带请求体的HTTP请求时没有指定Content-length头,发送时会很慢。如果是调用end方法发送一个字符串和一个缓冲区,Vert.x会在发送请求体之前,自动计算长度关设置Content-length头。如果使用HTTP块模式发送带请求体的请求,不必提供Content-Length头,所以不必提前计算大小。
写请求头
使用MultiMap实例向请求写入请求头的如下:
MultiMap headers = request.headers();
headers.set("content-type","application/json").set("other-header", "foo");
头集合是MultiMap实例,MultiMap提供了添加,设置和移除实体的操作方法。Http头容许一个键有多个值。可以在request调用putHeader方法写入请求头。
request.putHeader("content-type","application/json").putHeader("other-header","foo");
向请求写入请求头必须在请求体之前写入。
非标准HTTP方法
“OTHER” HTTP方法被用作非标准方法,在此方法被使用时,setRawMethod方法必须被用于设置原如方法发送到服务器。
结束HTTP请求
一旦使用HTTP请求完成,必须使用一个end操作结束请求。结束请求会导致写入一些请求头,如果这此头没有被写入,此请求将会标记为完成。有多种方式结束请求。调用没有参数的end方法可以简单结束请求:
request.end();
也可以调用带有一个字符串或一个缓冲器的end方法。这相当于在调用无参数的end方法前调用带有字符串和缓冲器的write方法。
request.end("some-data");
// End it with a buffer
Buffer buffer =Buffer.buffer().appendFloat(12.3f).appendInt(321);
request.end(buffer);
块模式的HTTP请求
Vert.x支持HTTP块传输编码请求.。这就可以将HTTP请求体写进块中,在向服务器写大的请求体这很常用,因为之前并不知道大小。使用setChunked方法将请求写入请求块中。在块模式中,每次write方法的调用都会新建一个块并写入服务器,在块模式中不需要预先设置Content-Length头。
request.setChunked(true);
// Write some chunks
for (int i = 0; i < 10; i++) {
request.write("this-is-chunk-" + i);
}
request.end();
请求超时
用setTimeout方法为指定的http请求设置超时时间。如果在超时时间里请求不返回任何数据,一个异常将会被发送到异常处理器(在提供异常处理器的情况下),然后请求关闭。
处理异常
可以在HttpClientRequest实例上添加异常处理器对一个请求的异常作出响应:
HttpClientRequest request =client.post("some-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
});
request.exceptionHandler(e -> {
System.out.println("Received exception: " + e.getMessage());
e.printStackTrace();
});
此处理器不会处理非2XX响应,非2XX需要在HttpClientResponse中进行处理:
HttpClientRequest request =client.post("some-uri", response -> {
if(response.statusCode() == 200) {
System.out.println("Everything fine");
return;
}
if(response.statusCode() == 500) {
System.out.println("Unexpected behavior on the server side");
return;
}
});
request.end();
重要:XXXNow方法不能接收异常处理器。
给客户端请求设置处理器
不是在调用创建客户端请求对象时提供响应处理器,而是在新建请求时或者在此之后用handle方法设置到请求对象。例如:
HttpClientRequest request =client.post("some-uri");
request.handler(response -> {
System.out.println("Received response with status code " +response.statusCode());
});
使用请求作为流
HttpClientRequest实例也是一个WriteStream,意味着可泵接任何ReadStream实例。例如,可以将一个磁盘文件泵接到http请求体:
request.setChunked(true);
Pump pump = Pump.pump(file, request);
file.endHandler(v -> request.end());
pump.start();
编写HTTP/2帧
HTTP/2是一个对HTTP请求/响应模式多帧的帧化协议。协议规定可以发送和接收其他种类的帧。为了发送这些帧,可以使用request的write方法,下面是一个例子:
int frameType = 40;
int frameStatus = 10;
Buffer payload = Buffer.buffer("somedata");
// Sending a frame to the server
request.writeCustomFrame(frameType,frameStatus, payload);
流重置
HTTP/1.x不允许清理重置请求或者响应流,例如在一个客户端上传一个服务器已经有的资源时,服务器需要接收完整的响应。HTTP/2支持在请求/响应过程的任何时间进行重置:
request.reset();
默认NO_ERROR(0)错误吗将会发送,也可用其他编码代替:
request.reset(8);
HTTP/2标准定义可用的错误列表。使用请求处理器和响应处理器,在流重置时,请求处理器将得到通知。
request.exceptionHandler(err -> {
if (errinstanceof StreamResetException) {
StreamResetException reset = (StreamResetException) err;
System.out.println("Stream reset " + reset.getCode());
}
});
处理http响应
通过在请求方法中指定响应处理器或者直接在HttpClientRequest对象上设置处理器后,就可以在处理器中获取一个HttpClientResponse实例了。可用statusCode或statusMessage方法查询响应的状态码和状态信息。
client.getNow("some-uri", response-> {
// thestatus code - e.g. 200 or 404
System.out.println("Status code is " + response.statusCode());
// thestatus message e.g. "OK" or "Not Found".
System.out.println("Status message is " +response.statusMessage());
});
使用响应作为流
HttpClientResponse实例也是ReadStream,这表示可以泵接到WriteStream实例。
响应头和响应尾
Http响应可以包含头,用header方法获取响应头。Headers方法返回的是一个MultiMap对象,它包含了一个响应头的多个值。
String contentType =response.headers().get("content-type");
String contentLength =response.headers().get("content-lengh");
块Http响应可以包含尾,响应尾是在写回最后一个响应体块时发送的。使用trailers获取响应尾,响应尾是一个MultiMap对象。
读取响应体
在从网络上读取响应头时将调用响应处理器。如果响应有响应体,此响应体可能会在头读过之后一段时间内到达。在调用响应处理器之前是不必等待响应体到达,因为响应可能很大,也许需要很长时间或者大的响应消息需要超过运行程序的内存。
作为响应体的一部分,会传递具有部分响应体数据的Buffer到响应处理器方法,并调用响应处理器:
client.getNow("some-uri", response-> {
response.handler(buffer -> {
System.out.println("Received a part of the response body: " +buffer);
});
});
如果知道响应不大,且想合并到内存中然后再处理,我们可以自己进行合并:
client.getNow("some-uri", response -> {
//Create an empty buffer
BuffertotalBuffer = Buffer.buffer();
response.handler(buffer -> {
System.out.println("Received a part of the response body: " +buffer.length());
totalBuffer.appendBuffer(buffer);
});
response.endHandler(v -> {
//Now all the body has been read
System.out.println("Total response body length is " +totalBuffer.length());
});
});
或者使便捷方法 bodyHandler,它将在读取整个响应后被调用:
client.getNow("some-uri", response-> {
response.bodyHandler(totalBuffer -> {
//Now all the body has been read
System.out.println("Total response body length is " +totalBuffer.length());
});
});
响应结束处理器
响应结束处理器,在整个响应体已经读取或者在响应头已经读取,并且没有响应体的响应处理器被调用之后立既被执行。
从响应中读取Cookies
用cookies方法从响应中获取cookies列表。另外一种方法是仅需要自己解析响应中的Set-Cookies头。
100-持续处理
根据HTTP/1.1规范,客户端可以设置Expect:100-Continue头并且在发送乘余的请求体之前发送请求此请求头。服务器然后用临时的响应状态Status:100(Continue)进行回复,此回复向客户端表示成功发送乘余的请求体。基于在大量数据发送前,服务可以批准并且接收或拒绝请求的观点。如请求没有被接收,发送大量的数据是带宽的浪费,并且因为读取废数据,阻碍了服务器数据读取。
Vert.x允许在客户端请求对象上设置continueHandler。这个处理器将在服务器向客户端发送Status:100(Continue) 表示成功响应,可以发送剩下数据时被调用。这与’sendHead’一起使用发送请求头。例子如下:
HttpClientRequest request =client.put("some-uri", response -> {
System.out.println("Received response with status code " +response.statusCode());
});
request.putHeader("Expect","100-Continue");
request.continueHandler(v -> {
// OKto send rest of body
request.write("Some data");
request.write("Some more data");
request.end();
});
在服务器侧,一个Vert.x服务器可以配置成在收到Expecct:100-Continue头时,自动发回100 Continue中间响应。这调用setHandle100ContinueAutomatically进行设置。如喜欢手工决定是否发回持续响应,setHandle100ContinueAutomatically将设成false(默认值),然后可以检查头并调用writeContinue让客户端继续发送请求体。
httpServer.requestHandler(request -> {
if(request.getHeader("Expect").equalsIgnoreCase("100-Continue")){
//Send a 100 continue response
request.response().writeContinue();
//The client should send the body when it receives the 100 response
request.bodyHandler(body -> {
//Do something with body
});
request.endHandler(v -> {
request.response().end();
});
}
});
可能通过发回失败状态码拒绝请求:此种情况下,请求要么被忽视,要么连接被关闭(100-Continue 是一个性能亮点并且不受逻辑协议限制):
httpServer.requestHandler(request -> {
if(request.getHeader("Expect").equalsIgnoreCase("100-Continue")){
boolean rejectAndClose = true;
if(rejectAndClose) {
//Reject with a failure code and close the connection
//this is probably best with persistent connection
request.response()
.setStatusCode(405)
.putHeader("Connection", "close")
.end();
}else {
//Reject with a failure code and ignore the body
//this may be appropriate if the body is small
request.response()
.setStatusCode(405)
.end();
}
}
});