從零手寫實現 nginx-35-proxy_pass netty 如何實現?

老马啸西风發表於2024-07-28

前言

大家好,我是老馬。很高興遇到你。

我們為 java 開發者實現了 java 版本的 nginx

https://github.com/houbb/nginx4j

如果你想知道 servlet 如何處理的,可以參考我的另一個專案:

手寫從零實現簡易版 tomcat minicat

手寫 nginx 系列

如果你對 nginx 原理感興趣,可以閱讀:

從零手寫實現 nginx-01-為什麼不能有 java 版本的 nginx?

從零手寫實現 nginx-02-nginx 的核心能力

從零手寫實現 nginx-03-nginx 基於 Netty 實現

從零手寫實現 nginx-04-基於 netty http 出入參最佳化處理

從零手寫實現 nginx-05-MIME型別(Multipurpose Internet Mail Extensions,多用途網際網路郵件擴充套件型別)

從零手寫實現 nginx-06-資料夾自動索引

從零手寫實現 nginx-07-大檔案下載

從零手寫實現 nginx-08-範圍查詢

從零手寫實現 nginx-09-檔案壓縮

從零手寫實現 nginx-10-sendfile 零複製

從零手寫實現 nginx-11-file+range 合併

從零手寫實現 nginx-12-keep-alive 連線複用

從零手寫實現 nginx-13-nginx.conf 配置檔案介紹

從零手寫實現 nginx-14-nginx.conf 和 hocon 格式有關係嗎?

從零手寫實現 nginx-15-nginx.conf 如何透過 java 解析處理?

從零手寫實現 nginx-16-nginx 支援配置多個 server

從零手寫實現 nginx-17-nginx 預設配置最佳化

從零手寫實現 nginx-18-nginx 請求頭+響應頭操作

從零手寫實現 nginx-19-nginx cors

從零手寫實現 nginx-20-nginx 佔位符 placeholder

從零手寫實現 nginx-21-nginx modules 模組資訊概覽

從零手寫實現 nginx-22-nginx modules 分模組載入最佳化

從零手寫實現 nginx-23-nginx cookie 的操作處理

從零手寫實現 nginx-24-nginx IF 指令

從零手寫實現 nginx-25-nginx map 指令

從零手寫實現 nginx-26-nginx rewrite 指令

從零手寫實現 nginx-27-nginx return 指令

從零手寫實現 nginx-28-nginx error_pages 指令

從零手寫實現 nginx-29-nginx try_files 指令

從零手寫實現 nginx-30-nginx proxy_pass upstream 指令

從零手寫實現 nginx-31-nginx load-balance 負載均衡

從零手寫實現 nginx-32-nginx load-balance 演算法 java 實現

從零手寫實現 nginx-33-nginx http proxy_pass 測試驗證

從零手寫實現 nginx-34-proxy_pass 配置載入處理

從零手寫實現 nginx-35-proxy_pass netty 如何實現?

netty 如何實現反向代理?

整體思路

  1. 根據原始的 request 請求,構建新的請求物件 forwardedRequest

  2. 根據指定的路由策略,獲取一個目標伺服器。

  3. 根據目標伺服器的 host+port,用 netty 直接模擬 http 客戶端,直接訪問遠端服務端,然後把遠端的響應寫回到當前的客戶端 resp

實現程式碼

核心實現如下:

/**
 * netty 實現反向代理
 * 
 * @since 0.27.0
 * @author 老馬嘯西風
 */
public class NginxRequestDispatchProxyPass extends AbstractNginxRequestDispatch {

    private static final Log logger = LogFactory.getLog(NginxRequestDispatchProxyPass.class);

    @Override
    public void doDispatch(NginxRequestDispatchContext context) {
        // 原始的請求
        final FullHttpRequest request = context.getRequest();
        final ChannelHandlerContext ctx = context.getCtx();

        // 建立一個新的 FullHttpRequest 轉發到目標伺服器
        FullHttpRequest forwardedRequest = new DefaultFullHttpRequest(
                request.protocolVersion(), request.method(), request.uri(), request.content().retainedDuplicate());
        forwardedRequest.headers().set(request.headers());

        final NginxLoadBalanceConfig nginxLoadBalanceConfig = context.getBalanceConfig();

        // 建立一個新的 Bootstrap 進行 HTTP 請求
        Bootstrap b = new Bootstrap();
        b.group(ctx.channel().eventLoop())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        //...

                        ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpResponse>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext clientCtx, FullHttpResponse response) throws Exception {
                                // 將目標伺服器的響應寫回到客戶端
                                FullHttpResponse clientResponse = new DefaultFullHttpResponse(
                                        response.protocolVersion(), response.status(), response.content().retainedDuplicate());

                                clientResponse.headers().set(response.headers());

                                ctx.writeAndFlush(clientResponse).addListener(ChannelFutureListener.CLOSE);
                                clientCtx.close();
                            }

                            @Override
                            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                logger.error("exceptionCaught meet ex", cause);
                                ctx.close();
                            }
                        });
                    }
                });

        // 連線到目標伺服器併傳送請求
        final IServer server = getActualServer(nginxLoadBalanceConfig);
        b.connect(server.host(), server.port()).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                future.channel().writeAndFlush(forwardedRequest);
            } else {
                ctx.close();
            }
        });
    }
}

負載均衡

負載均衡策略,可以看我以前的文章:

從零手寫實現 nginx-32-load balance 負載均衡演算法 java 實現

小結

到這裡開始,我們基本實現了反向代理。

當然,其中還有很多細節需要處理。

相關文章