13
0
geforkt von Mirrors/Velocity

Introduce a connection pool for Mojang's session servers.

This has the potential to cut the time that players spend at the
"logging in..." (or "encrypting..." for 1.13+) screen by a fair
amount (gains of 200+ ms were noted for my own home connection).

While this sounds minor, I really do like to aim for all the details
and this is one of them.
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-08-03 02:25:57 -04:00
Ursprung 0c481d828d
Commit 7eea1a3ac6
2 geänderte Dateien mit 65 neuen und 37 gelöschten Zeilen

Datei anzeigen

@ -1,21 +1,50 @@
package com.velocitypowered.proxy.connection.http; package com.velocitypowered.proxy.connection.http;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.channel.pool.*;
import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import java.net.InetSocketAddress;
import java.net.URL; import java.net.URL;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class NettyHttpClient { public class NettyHttpClient {
private final VelocityServer server; private final ChannelPoolMap<InetSocketAddress, SimpleChannelPool> poolMap;
public NettyHttpClient(VelocityServer server) { public NettyHttpClient(VelocityServer server) {
this.server = server; Bootstrap bootstrap = server.initializeGenericBootstrap();
this.poolMap = new AbstractChannelPoolMap<InetSocketAddress, SimpleChannelPool>() {
@Override
protected SimpleChannelPool newPool(InetSocketAddress key) {
return new FixedChannelPool(bootstrap.remoteAddress(key), new ChannelPoolHandler() {
@Override
public void channelReleased(Channel channel) throws Exception {
channel.pipeline().remove("collector");
}
@Override
public void channelAcquired(Channel channel) throws Exception {
System.out.println("ACQUIRED");
}
@Override
public void channelCreated(Channel channel) throws Exception {
if (key.getPort() == 443) {
SslContext context = SslContextBuilder.forClient().build();
SSLEngine engine = context.newEngine(channel.alloc());
channel.pipeline().addLast("ssl", new SslHandler(engine));
}
channel.pipeline().addLast("http", new HttpClientCodec());
}
}, 8);
}
};
} }
public CompletableFuture<String> get(URL url) { public CompletableFuture<String> get(URL url) {
@ -27,35 +56,25 @@ public class NettyHttpClient {
} }
CompletableFuture<String> reply = new CompletableFuture<>(); CompletableFuture<String> reply = new CompletableFuture<>();
server.initializeGenericBootstrap() InetSocketAddress address = new InetSocketAddress(host, port);
.handler(new ChannelInitializer<Channel>() { poolMap.get(address)
@Override .acquire()
protected void initChannel(Channel ch) throws Exception { .addListener(future -> {
if (ssl) { if (future.isSuccess()) {
SslContext context = SslContextBuilder.forClient().build(); Channel channel = (Channel) future.getNow();
SSLEngine engine = context.newEngine(ch.alloc()); channel.pipeline().addLast("collector", new SimpleHttpResponseCollector(reply));
ch.pipeline().addLast(new SslHandler(engine));
} DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery());
ch.pipeline().addLast(new HttpClientCodec()); request.headers().add(HttpHeaderNames.HOST, url.getHost());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity");
@Override channel.writeAndFlush(request);
public void channelActive(ChannelHandlerContext ctx) throws Exception {
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery()); reply.whenComplete((resp, err) -> {
request.headers().add(HttpHeaderNames.HOST, url.getHost()); // Make sure to release this connection
request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity"); poolMap.get(address).release(channel, channel.voidPromise());
ctx.writeAndFlush(request);
}
}); });
ch.pipeline().addLast(new SimpleHttpResponseCollector(reply)); } else {
} reply.completeExceptionally(future.cause());
})
.connect(host, port)
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
reply.completeExceptionally(future.cause());
}
} }
}); });
return reply; return reply;

Datei anzeigen

@ -2,10 +2,7 @@ package com.velocitypowered.proxy.connection.http;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -14,6 +11,7 @@ import java.util.concurrent.CompletableFuture;
class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter { class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
private final StringBuilder buffer = new StringBuilder(1024); private final StringBuilder buffer = new StringBuilder(1024);
private final CompletableFuture<String> reply; private final CompletableFuture<String> reply;
private boolean canKeepAlive;
SimpleHttpResponseCollector(CompletableFuture<String> reply) { SimpleHttpResponseCollector(CompletableFuture<String> reply) {
this.reply = reply; this.reply = reply;
@ -23,18 +21,23 @@ class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try { try {
if (msg instanceof HttpResponse) { if (msg instanceof HttpResponse) {
HttpResponseStatus status = ((HttpResponse) msg).status(); HttpResponse response = (HttpResponse) msg;
HttpResponseStatus status = response.status();
if (status != HttpResponseStatus.OK) { if (status != HttpResponseStatus.OK) {
ctx.close();
reply.completeExceptionally(new RuntimeException("Unexpected status code " + status.code())); reply.completeExceptionally(new RuntimeException("Unexpected status code " + status.code()));
return;
} }
this.canKeepAlive = HttpUtil.isKeepAlive(response);
} }
if (msg instanceof HttpContent) { if (msg instanceof HttpContent) {
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8)); buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
if (msg instanceof LastHttpContent) { if (msg instanceof LastHttpContent) {
ctx.close(); if (!canKeepAlive) {
ctx.close();
}
reply.complete(buffer.toString()); reply.complete(buffer.toString());
} }
} }
@ -42,4 +45,10 @@ class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
ReferenceCountUtil.release(msg); ReferenceCountUtil.release(msg);
} }
} }
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
reply.completeExceptionally(cause);
}
} }