使用Vert.x 2.1.6中的HTTP服务器(基于Netty 4.0.21)处理HTTP请求,从文件系统读取静态文件并写入客户端。 有时,在多个同时请求(读取:在浏览器中快速单击)期间,随机请求永远不会得到任何响应。 例如。单击注销链接将触发对背景图像,徽标,几个css文件等的请求。 除了徽标文件之外,所有文件都会成功返回,http请求只会挂起。 这种情况并不总是会发生,但似乎是因为在启动新的请求之前没有等待先前的请求完成。
为了弄清楚发生了什么,我将LoggingHandlers添加到Vert.x DefaultHttpServer创建的netty管道中。 下面的示例显示了file logo.png(大小为12754B)的成功和不成功请求的输出。 输出给我留下了两个问题:
EDIT:我意识到该通道用于多个HTTP请求,因此尾随字节只是正在写入的另一个文件。
请求:
GET /authenticate/res/images/logo.png HTTP/1.1
Host: 192.168.0.12:18443
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Referer: https://192.168.0.12:18443/authenticate/res/css/login.css
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
成功时输出:
$ grep 0xafd76300 /var/log/server.log
2017-09-06 15:17:12,609 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] REGISTERED
2017-09-06 15:17:12,609 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] REGISTERED
2017-09-06 15:17:12,609 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] ACTIVE
2017-09-06 15:17:12,609 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] ACTIVE
2017-09-06 15:17:12,625 [SslHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] HANDSHAKEN: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
2017-09-06 15:17:12,625 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] USER_EVENT: io.netty.handler.ssl.SslHandshakeCompletionEvent@38f7a12a
2017-09-06 15:17:12,625 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] USER_EVENT: io.netty.handler.ssl.SslHandshakeCompletionEvent@38f7a12a
2017-09-06 15:17:12,629 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] RECEIVED: DefaultHttpRequest(decodeResult: success)
2017-09-06 15:17:12,633 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] RECEIVED(0B)
2017-09-06 15:17:12,634 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(12754B)
2017-09-06 15:17:12,634 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] FLUSH
2017-09-06 15:17:12,635 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(0B)
2017-09-06 15:17:12,635 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(12774B)
2017-09-06 15:17:12,635 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] FLUSH
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(0B)
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] FLUSH
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(10B)
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] WRITE(0B)
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] FLUSH
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] CLOSE()
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 => /172.16.238.11:8443] CLOSE()
2017-09-06 15:17:12,636 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 :> /172.16.238.11:8443] INACTIVE
2017-09-06 15:17:12,637 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 :> /172.16.238.11:8443] UNREGISTERED
2017-09-06 15:17:12,637 [LoggingHandler] DEBUG: [id: 0xafd76300, /192.168.10.113:60180 :> /172.16.238.11:8443] UNREGISTERED
不成功时的输出:
$ grep 0x09db306c /var/log/server.log
2017-09-06 15:17:25,569 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] REGISTERED
2017-09-06 15:17:25,569 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] REGISTERED
2017-09-06 15:17:25,569 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] ACTIVE
2017-09-06 15:17:25,569 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] ACTIVE
2017-09-06 15:17:25,571 [SslHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] HANDSHAKEN: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
2017-09-06 15:17:25,571 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] USER_EVENT: io.netty.handler.ssl.SslHandshakeCompletionEvent@38f7a12a
2017-09-06 15:17:25,572 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] USER_EVENT: io.netty.handler.ssl.SslHandshakeCompletionEvent@38f7a12a
2017-09-06 15:17:25,608 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] RECEIVED: DefaultHttpRequest(decodeResult: success)
2017-09-06 15:17:25,608 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] RECEIVED(0B)
2017-09-06 15:17:25,609 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] FLUSH
2017-09-06 15:17:25,610 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] WRITE(12754B)
2017-09-06 15:17:25,610 [LoggingHandler] DEBUG: [id: 0x09db306c, /192.168.10.113:60300 => /172.16.238.11:8443] WRITE(0B)
在HttpServer上注册的请求处理程序如下所示:
Vertx vertx = ...
HttpServerRequest req = ...
HttpServerResponse response = req.response();
vertx.fileSystem().readFile(file, (AsyncResult<Buffer> e) -> {
try {
if (e.succeeded()) {
Buffer buf = e.result();
response.putHeader("Content-Type", MimeMapper.getTypeForFile(file)+";charset=UTF-8");
response.putHeader("Content-Length", Integer.toString(buf.length()));
response.write(buf);
response.end();
response.close();
LOG.trace("Write success (%d) %s...", buf.length(), file);
} else {
LOG.error("Write failure", e.cause());
}
} catch (Exception x) {
LOG.error("Write failure", x);
}
});
编辑:调用response.close()
具有立即取消注册和取消激活频道的效果。如果没有该呼叫,则在最后一次写入后约5分钟自动触发取消注册。在不成功的情况下,永远不会触发取消注册,通道将无限期地保持在活动模式。
我通过添加两个LoggingHandler修改了Vert.x 2 DefaultHttpServer(版本2.1.6,ssl和压缩激活)中的Netty管道:
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(availableWorkers);
bootstrap.channel(NioServerSocketChannel.class);
tcpHelper.applyConnectionOptions(bootstrap);
tcpHelper.checkSSL(vertx);
bootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (tcpHelper.isSSL()) {
pipeline.addLast("ssl", tcpHelper.createSslHandler(vertx, false));
}
pipeline.addLast("flashpolicy", new FlashPolicyHandler());
pipeline.addLast("httpDecoder", new HttpRequestDecoder(4096, 8192, 8192, false));
pipeline.addLast("httpEncoder", new VertxHttpResponseEncoder());
pipeline.addLast("reqlog", new LoggingHandler());
if (compressionSupported) {
pipeline.addLast("deflater", new HttpChunkContentCompressor());
}
if (tcpHelper.isSSL() || compressionSupported) {
// only add ChunkedWriteHandler when SSL is enabled otherwise it is not needed as FileRegion is used.
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); // For large file / sendfile support
}
pipeline.addLast("handler", new ServerHandler());
pipeline.addLast("rsplog", new LoggingHandler());
}
});
答案 0 :(得分:1)
所以我似乎找到了错误的来源。对我来说,它看起来像是一个经典的并发问题。我通过更改vertx-core版本2.1.6中的单行代码来修复它,将write
操作更改为writeAndFlush
。该修复可能绕过了原作者的意图,并将导致在netty频道上进行一些不必要的刷新操作。
这就是我认为正在发生的事情。要在代码中遵循我的推理,请查看2.1.6中的org.vertx.java.core.net.impl.ConnectionBase
。涉及两个标志:read
和needsFlush
。我在这里假设有两个线程同时访问该通道。
HttpServer
收到http请求,打开Netty频道并成功读取整个请求。处理程序在读取时处于READ模式,然后返回WRITE模式,刷新写缓冲区:read = false
,needsFlush = false
。read = true
read = true
开始,写入将通过设置needsFlush = true
开始,并准备对响应进行排队。read = false
,刷新(空)写缓冲区(自needsFlush = true
起)并设置{ {1}}。needsFlush = false
标志,它只是将字节写入通道而不进行刷新。现在永远不会刷新频道,因为不会再发生read
。要解决此问题,我已更新
endReadAndFlush
执行org.vertx.java.core.http.impl.VertxHttpHandler.write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
而非ctx.writeAndFlush(msg, promise)
。
可能有一些我忽略的东西,这将解决问题而无需修补vertx2。也许可以配置vertx2为每个http请求创建一个新的netty通道,而不是为多个请求重用相同的通道?如果有人可以提议,我很乐意接受这样一个替代解决方案。
编辑:也许我应该补充一点,我在重新编译Vertx2时将Netty升级到4.0.51。但两个版本的问题都是一样的,升级后可能会有点糟糕。