简体   繁体   English

具有netty的非阻塞反向代理

[英]Non-blocking reverse proxy with netty

I'm trying to write a non-blocking proxy with netty 4.1. 我正在尝试用netty 4.1编写一个非阻塞代理。 I have a "FrontHandler" which handles incoming connections, and then a "BackHandler" which handles outgoing ones. 我有一个处理传入连接的“FrontHandler”,然后是一个处理传出连接的“BackHandler”。 I'm following the HexDumpProxyHandler ( https://github.com/netty/netty/blob/ed4a89082bb29b9e7d869c5d25d6b9ea8fc9d25b/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java#L67 ) 我正在关注HexDumpProxyHandler( https://github.com/netty/netty/blob/ed4a89082bb29b9e7d869c5d25d6b9ea8fc9d25b/example/src/main/java/io/netty/example/proxy/HexDumpProxyFrontendHandler.java#L67

In this code I have found: 在这段代码中,我发现:

@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
    if (outboundChannel.isActive()) {
        outboundChannel.writeAndFlush(msg).addListener(new ChannelFutureListener() {, I've seen:

Meaning that the incoming message is only written if the outbound client connection is already ready. 这意味着仅在出站客户端连接已准备好时才写入传入消息。 This is obviously not ideal in a HTTP proxy case, so I am thinking what would be the best way to handle it. 这在HTTP代理案例中显然不理想,所以我在想什么是处理它的最佳方法。

I am wondering if disabling auto-read on the front-end connection (and only trigger reads manually once the outgoing client connection is ready) is a good option. 我想知道是否禁用前端连接上的自动读取(并且只有在外发客户端连接准备就绪时才触发读取)是一个不错的选择。 I could then enable autoRead over the child socket again, in the "channelActive" event of the backend handler. 然后,我可以在后端处理程序的“channelActive”事件中再次对子套接字启用autoRead。 However, I am not sure about how many messages would I get in the handler for each "read()" invocation (using HttpDecoder, I assume I would get the initial HttpRequest, but I'd really like to avoid getting the subsequent HttpContent / LastHttpContent messages until I manually trigger the read() again and enable autoRead over the channel). 但是,我不确定每次“read()”调用会在处理程序中获得多少消息(使用HttpDecoder,我假设我会得到最初的HttpRequest,但我真的想避免获得后续的HttpContent / LastHttpContent消息,直到我再次手动触发read()并通过通道启用autoRead。

Another option would be to use a Promise to get the Channel from the client ChannelPool: 另一个选择是使用Promise从客户端ChannelPool获取Channel:

private void setCurrentBackend(HttpRequest request) {
    pool.acquire(request, backendPromise);

    backendPromise.addListener((FutureListener<Channel>) future -> {
        Channel c = future.get();
        if (!currentBackend.compareAndSet(null, c)) {
            pool.release(c);
            throw new IllegalStateException();
        }
    });
}

and then do the copying from input to output thru that promise. 然后通过该承诺进行从输入到输出的复制。 Eg: 例如:

private void handleLastContent(ChannelHandlerContext frontCtx, LastHttpContent lastContent) {
    doInBackend(c -> {
        c.writeAndFlush(lastContent).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                future.channel().read();
            } else {
                pool.release(c);
                frontCtx.close();
            }
        });
    });
}
private void doInBackend(Consumer<Channel> action) {
    Channel c = currentBackend.get();
    if (c == null) {
        backendPromise.addListener((FutureListener<Channel>) future -> action.accept(future.get()));
    } else {
        action.accept(c);
    }
}

but I'm not sure about how good it is to keep the promise there forever and do all the writes from "front" to "back" by adding listeners to it. 但是我不确定永远保持承诺是多么好,并且通过添加听众来完成从“前”到“后”的所有写入。 I'm also not sure about how to instance the promise so that the operations are performed in the right thread... right now I'm using: 我也不确定如何实现promise,以便在正确的线程中执行操作......现在我正在使用:

backendPromise = group.next().<Channel> newPromise(); // bad
// or
backendPromise = frontCtx.channel().eventLoop().newPromise(); // OK?

(where group is the same eventLoopGroup as used in the ServerBootstrap of the frontend). (其中group是与前端的ServerBootstrap中使用的相同的eventLoopGroup)。

If they're not handled thru the right thread, I assume it could be problematic to have the "else { }" optimization in the "doInBackend" method to avoid using the Promise and write to the channel directly. 如果它们没有通过正确的线程处理,我认为在“doInBackend”方法中进行“else {}”优化可能会有问题,以避免使用Promise并直接写入通道。

The no-autoread approach doesn't work by itself, because the HttpRequestDecoder creates several messages even if only one read() was performed. no-autoread方法本身不起作用,因为即使只执行了一个read(),HttpRequestDecoder也会创建几条消息。

I have solved it by using chained CompletableFutures. 我已经使用链式CompletableFutures解决了这个问题。

I have worked on a similar proxy application based on the MQTT protocol. 我已经开发了基于MQTT协议的类似代理应用程序。 So it was basically used to create a real-time chat application. 所以它基本上用于创建实时聊天应用程序。 The application that I had to design however was asynchronous in nature so I naturally did not face any such problem. 我必须设计的应用程序本质上是异步的,所以我自然不会遇到任何这样的问题。 Because in case the 因为万一

outboundChannel.isActive() == false

then I can simply keep the messages in a queue or a persistent DB and then process them once the outboundChannel is up. 然后我可以简单地将消息保存在队列或持久数据库中,然后在outboundChannel启动后处理它们。 However, since you are talking about an HTTP application, so this means that the application is synchronous in nature meaning that the client cannot keep on sending packets until the outboundChannel is up and running. 但是,由于您正在讨论HTTP应用程序,因此这意味着应用程序本质上是同步的,这意味着客户端无法继续发送数据包,直到outboundChannel启动并运行。 So the option you suggest is that the packet will only be read once the channel is active and you can manually handle the message reads by disabling the auto read in ChannelConfig . 因此,您建议的选项是只有在通道处于活动状态时才会读取数据包,您可以通过禁用ChannelConfig的自动读取来手动处理消息读取。

However, what I would like to suggest is that you should check if the outboundChannel is active or not. 但是,我想建议您应该检查outboundChannel是否处于活动状态。 In case the channel is active, send he packet forward for processing. 如果通道处于活动状态,则向前发送数据包以进行处理。 In case the channel is not active, you should reject the packet by sending back a response similar to Error404 如果通道未处于活动状态,则应通过发回类似于Error404的响应来拒绝该数据包

Along with this you should configure your client to keep on retrying sending the packets after certain intervals and accordingly handle what needs to be done in case the channel takes too long a time to become active and become readable. 除此之外,您应该将客户端配置为在一定时间间隔后继续重试发送数据包,并相应地处理需要执行的操作,以防通道花费太长时间变为活动状态并变得可读。 Manually handling channelRead is generally not preferred and is an anti pattern. 手动处理channelRead通常不是优选的并且是反模式。 You should let Netty handle that for you in the most efficient way. 您应该让Netty以最有效的方式为您处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM