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:
Ursprung
0c481d828d
Commit
7eea1a3ac6
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren