netty series: come on, teach you how to make a simple proxy server

Posted by markmuir on Wed, 29 Dec 2021 01:21:53 +0100

brief introduction

Einstein said: all greatness comes from simple details. netty provides us with such powerful eventloop and channel. Through the effective use of these simple things, we can get very powerful applications, such as the agent to be talked about today.

Proxy and reverse proxy

I believe that as long as programmers should have heard of nginx server, a very important function of this super excellent nginx is to do reverse proxy. So, I have a little partner to ask. If there is a reverse agent, there must be a forward agent. What's the difference between them?

Let's talk about the positive agent first. For example, the traffic stars have been hit recently. Although they have been suppressed, the stars are stars. Most people can't see them. If someone needs to talk to the stars, they need to go through the star's agent first, and the agent will convey the words to the stars. This broker is a forward agent. We use a forward proxy to access the object to be accessed.

So what is reverse proxy? For example, there are A lot of artificial intelligence now. If we talk to intelligent robot A, then A transfers our dialogue to the hidden person behind us. This person uses his wisdom to answer our dialogue and hand it over to intelligent robot A for output, and finally realizes artificial intelligence. This process is called reverse proxy.

The principle of implementing agent with netty

So how to implement this proxy server in netty?

First of all, the proxy server is a server, so we need to create a server using ServerBootstrap in netty:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new SimpleDumpProxyInitializer(REMOTE_HOST, REMOTE_PORT))
             .childOption(ChannelOption.AUTO_READ, false)
             .bind(LOCAL_PORT).sync().channel().closeFuture().sync();

In this local server, we pass in ProxyInitializer. In this handler initializer, we pass in a custom handler:

    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(
                new LoggingHandler(LogLevel.INFO),
                new SimpleDumpProxyInboundHandler(remoteHost, remotePort));
    }

In the custom handler, we use Bootstrap to create a client to connect to the remote proxy server. We put the creation of this client in the channelActive method:

// Open outbound connection
        Bootstrap b = new Bootstrap();
        b.group(inboundChannel.eventLoop())
         .channel(ctx.channel().getClass())
         .handler(new SimpleDumpProxyOutboundHandler(inboundChannel))
         .option(ChannelOption.AUTO_READ, false);
        ChannelFuture f = b.connect(remoteHost, remotePort);

After the client has established a connection, you can read data from the inboundChannel:

outboundChannel = f.channel();
        f.addListener(future -> {
            if (future.isSuccess()) {
                // After the connection is established, read the inbound data
                inboundChannel.read();
            } else {
                // Close inbound channel
                inboundChannel.close();
            }
        });

Because it is a proxy service, we need to forward the data read by the inboundChannel to the outboundChannel. Therefore, in channelRead, we need to write as follows:

    public void channelRead(final ChannelHandlerContext ctx, Object msg) {
        // Read the messages in the inboundChannel and write them to the outboundChannel
        if (outboundChannel.isActive()) {
            outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    // flush succeeded. Read the next message
                    ctx.channel().read();
                } else {
                    future.channel().close();
                }
            });
        }
    }

After the outboundChannel is successfully written, continue to read the inboundChannel.

Similarly, there is also a handler for the client's outboundChannel. In this handler, we need to reverse write the data read by the outboundChannel to the inboundChannel:

    public void channelRead(final ChannelHandlerContext ctx, Object msg) {
        // Read the messages in the outboundChannel and write them to the inboundChannel
        inboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                ctx.channel().read();
            } else {
                future.channel().close();
            }
        });
    }

After the inboundChannel is successfully written, continue reading the outboundChannel.

Such a simple proxy server is completed.

actual combat

If we proxy the local 8000 port to www.163.com COM port 80, what happens? Run our program to access http://localhost:8000 , we will see the following page:

Why don't we display normal pages as we think? That's because the domain name after our proxy is localhost instead of the normal www.163 COM, so the server does not recognize our request and reports an error.

summary

The simple forwarding request between proxy servers in this paper can not handle the above scenario, so how to solve the above problem? Please look forward to my follow-up articles!

Examples of this article can be referred to: learn-netty4

This article has been included in http://www.flydean.com/35-netty-simple-proxy/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to find!

Welcome to my official account: "those things in procedure", understand technology, know you better!

Topics: Java Netty Nginx server