1. 程式人生 > >Netty實現簡單HTTP代理伺服器

Netty實現簡單HTTP代理伺服器

自上次使用Openresty+Lua+Nginx的來加速自己的網站,用上了比較時髦的技術,感覺算是讓自己的網站響應速度達到極限了,直到看到了Netty,公司就是打算用Netty來替代Openresty這一套,所以,自己也學了好久,琢磨了好一趟才知道怎麼用,現在用來寫一套HTTP代理伺服器吧,之後再測試一下效能。

一、Netty中的HTTP

參考自《Netty實戰》

一個完整的HttpRequest請求

FullHttpRequest:

  1. HTTP Request 第一部分是包含的頭資訊
  2. HttpContent 裡面包含的是資料,可以後續有多個 HttpContent 部分
  3. LastHttpContent 標記是 HTTP request 的結束,同時可能包含頭的尾部資訊
  4. 完整的 HTTP request

一個完整的HttpResponse請求

FullHttpResponse:

  1. HTTP response 第一部分是包含的頭資訊
  2. HttpContent 裡面包含的是資料,可以後續有多個 HttpContent 部分
  3. LastHttpContent 標記是 HTTP response 的結束,同時可能包含頭的尾部資訊
  4. 完整的 HTTP response

二、Netty實現HTTP代理伺服器的流程

在實現Http代理伺服器之前,我們先來檢視一下Netty實現代理伺服器的完整流程:

Netty的Http服務的流程是: 1、Client向Server傳送http請求,在通常的情況中,client一般指的是瀏覽器,也可以由自己用netty實現一個客戶端。此時,客戶端需要用到HttpRequestEncoder將http請求進行編碼。 2、Server端對http請求進行解析,服務端中,需要用到HttpRequestDecoder來對請求進行解碼,然後實現自己的業務需求。 3、Server端向client傳送http響應,處理完業務需求後,將相應的內容,用HttpResponseEncoder進行編碼,返回資料。 4、Client對http響應進行解析,用HttpResponseDecoder進行解碼。

而Netty實現Http代理伺服器的過程跟上面的所說無意,只不過是在自己的業務層增加了回源到tomcat伺服器這一過程。結合上自己之前實現過的用OpenResty+Nginx來做代理伺服器這一套,此處的Netty實現的過程也與此類似。此處貼上一下OpenResty+Nginx實現的流程圖:

而使用了Netty之後,便是將中間的OpenResty+Nginx換成了Netty,下面我們來看一下具體的實現過程。

三、主要程式碼如下:

HttpServer

public class HttpServer {
    public void start(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            // server端傳送的是httpResponse,所以要使用HttpResponseEncoder進行編碼
                            ch.pipeline().addLast(
                                    new HttpResponseEncoder());
                            // server端接收到的是httpRequest,所以要使用HttpRequestDecoder進行解碼
                            ch.pipeline().addLast(
                                    new HttpRequestDecoder());
                            ch.pipeline().addLast(
                                    new HttpServerHandler());
                            //增加自定義實現的Handler
                            ch.pipeline().addLast(new HttpServerCodec());
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture f = b.bind(port).sync();

            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        HttpServer server = new HttpServer();
        server.start(8080);
    }
}

HttpServerHandler

@Slf4j
public class HttpServerHandler extends ChannelInboundHandlerAdapter {

    private RedisUtil redisUtil = new RedisUtil();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        if (msg instanceof HttpRequest) {
            DefaultHttpRequest request = (DefaultHttpRequest) msg;
            String uri = request.uri();
            if ("/favicon.ico".equals(uri)) {
                return;
            }
            log.info(new Date().toString());
            Jedis jedis = redisUtil.getJedis();
            String s = jedis.get(uri);
            if (s == null || s.length() == 0) {
                //這裡我們的處理是回源到tomcat伺服器進行抓取,然後
                //將抓取的內容放回到redis裡面
                try {
                    URL url = new URL("http://119.29.188.224:8080" + uri);
                    log.info(url.toString());
                    URLConnection urlConnection = url.openConnection();
                    HttpURLConnection connection = (HttpURLConnection) urlConnection;
                    connection.setRequestMethod("GET");
                    //連線
                    connection.connect();
                    //得到響應碼
                    int responseCode = connection.getResponseCode();
                    if (responseCode == HttpURLConnection.HTTP_OK) {
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader
                                (connection.getInputStream(), StandardCharsets.UTF_8));
                        StringBuilder bs = new StringBuilder();
                        String l;
                        while ((l = bufferedReader.readLine()) != null) {
                            bs.append(l).append("\n");
                        }
                        s = bs.toString();
                    }
                    jedis.set(uri, s);
                    connection.disconnect();
                } catch (Exception e) {
                    log.error("", e);
                    return;
                }
            }
            jedis.close();
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HTTP_1_1, OK, Unpooled.wrappedBuffer(s != null ? s
                    .getBytes() : new byte[0]));
            response.headers().set(CONTENT_TYPE, "text/html");
            response.headers().set(CONTENT_LENGTH,
                    response.content().readableBytes());
            response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            ctx.write(response);
            ctx.flush();
        } else {
            //這裡必須加丟擲異常,要不然ab測試的時候一直卡住不動,暫未解決
            throw new Exception();
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }
}

四、效能測試

下面的是ab測試,在1GHz、2G記憶體的centos7機器(阿里雲伺服器)下進行測試,測試命令ab -c 100 -n 10000 localhost:8000/,併發數為100,總數為10000。

效能:

整體響應時間的分佈比(單位:ms):

看完之後,我自己也震驚了,Netty實現的不僅穩定、吞吐率還比OpenResty的高出一倍,OpenResty的居然還有那麼多的失敗次數,不知是不是我的程式碼的問題還是測試例子不規範,至今,我還是OpenResty的腦殘粉。總體的來說,Netty實現的伺服器效能還是比較強的,不僅能夠快速地開發高效能的面向協議的伺服器和客戶端,還可以在Netty上輕鬆實現各種自定義的協議。

五、原始碼地址

參考: