亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Netty Http 協議

1. 前言

我們通常使用 Netty 來開發 TCP 協議,一般的應用場景都是客戶端和服務端長連接通訊的模式,其實,除了 TCP 協議之外 Netty 還支持其他常見的應用協議,比如:Http、WebSocket 等。我們所熟悉的 Tomcat 在 6.x 之后其實底層就是基于 Netty 去實現的。接下來我們主要講解如何通過 Netty 開發支持 Http 協議服務端,客戶端則是通過瀏覽器發起請求。

2. 學習目的

其實 Netty 開發 Http 協議在我們的開發當中其實并不常用,其主要的的應用場景是開發類型 Tomcat 這種類型的 Web 容器,有了成熟的 Tomcat、Jboss、WebLogic,不需要我們去重新造一遍輪子,但是為什么還需要去學習它呢?

學習本節主要有兩個目的:

  1. 有助于以后學習 Tomcat 的原理,Tomcat 的通訊部分是基于 Netty 去實現的;
  2. 有助于理解整個 Java 體系的通訊架構原理,很多我們平時使用最多、接觸最多、熟練使用的技術,但是我們往往不懂得其底層原理是什么,Tomcat 和 Http 就是其中被廣泛熟知,但是很少同學有興趣去了解其原理的。

3. 環境搭建

下面,我們將實現一個 Demo,具體需求如下:

  1. 使用 Netty 開發一個 Web 服務器,端口是 8080;
  2. 客戶端請求,則不再是使用 Netty 編寫的客戶端代碼了,而是通過瀏覽器輸入地址進行訪問。
  3. 服務端響應,我們的 Web 服務器往瀏覽器輸出信息,并且能夠在瀏覽器上打印相關信息。

環境搭建步驟:

  1. 創建一個 Maven 項目;
  2. 導入 Netty 坐標。

實例:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.6.Final</version>
</dependency>

4. 代碼實現

Netty 核心原理是對客戶端發送過來的數據進行解碼,以及給客戶端發送數據時需要進行數據的編碼。同樣的原理,Netty 對于 Http 協議的開發,其實也是針對 Http 格式是數據進行編碼和解碼而已,并沒有很多神奇的地方。當然我們對 Http 格式非常的熟悉,可以自己手工去實現這個復雜的過程,Netty 也考慮到了簡化開發的復雜度,因此給我們提供了相應的編解碼類。接下來,我們一起感受一下。

4.1. Netty 主啟動類

public class TestServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap
                    .group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        protected void initChannel(NioSocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            //1.Netty提供的針對Http的編解碼
                            pipeline.addLast(new HttpServerCodec());
                            //2.自定義處理Http的業務Handler
                            pipeline.addLast(new TestHttpServerHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

代碼說明:

這個是 Netty 的基本模板類,跟我們之前寫的并沒有什么不同,只是它給我們提了一個特殊的類 HttpServerCodec,從字面上都能猜到它就是針對 Http 服務的編解碼器。

4.2. Netty 業務 Handler 類

public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if(msg instanceof HttpRequest) {
            //1.打印瀏覽器的請求地址
            System.out.println("客戶端地址:" + ctx.channel().remoteAddress());
            
            //2.給瀏覽器發送的信息,封裝成ByteBuf
            ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);

            //3.構造一個http的相應,即 httpresponse
            FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, 
                HttpResponseStatus.OK, 
                content);
			//4.設置響應頭信息-響應格式
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            //5.設置響應頭信息-響應數據長度
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());

            //6.將構建好 response返回
            ctx.writeAndFlush(response);
        }
    }
}

代碼說明:

  1. 瀏覽器發送過來的數據,被 Netty 給封裝成了 HttpObject 對象,我們需要判斷 HttpObject 具體所屬類型是不是 HttpRequest;
  2. 請求信息: 可以打印瀏覽器的請求信息,比如:請求地址、請求方式、請求體內容、請求頭內容等;
  3. 響應信息: 給瀏覽器響應,必須構造 HttpResponse 對象,并且可以設置響應頭信息、響應體信息。

特殊說明:如果不嚴格按照 Http 響應格式進行輸出,瀏覽器是無法讀取服務端的響應。

4.3 測試

瀏覽器請求截圖:
圖片描述

服務端打印截圖:
圖片描述

疑惑:為什么瀏覽器每次請求,服務端都會打印兩次呢?

原因:瀏覽器每次都發起兩次請求,一次是業務請求,一次是瀏覽器的圖標請求,具體如下圖所示:
圖片描述

4.4. 靜態資源過濾

我們需要把非業務請求,也就是靜態資源的請求給過濾掉,避免資源的浪費,具體實現如下所示:

public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if(msg instanceof HttpRequest) {
            //1.打印瀏覽器的請求地址
            System.out.println("客戶端地址" + ctx.channel().remoteAddress());
            //2.強制轉換成HttpRequest
            HttpRequest httpRequest = (HttpRequest) msg;
            //3.獲取uri, 過濾指定的資源
            URI uri = new URI(httpRequest.uri());
            if("/favicon.ico".equals(uri.getPath())) {
                System.out.println("請求了 favicon.ico, 不做響應");
                return;
            }
            //4.給瀏覽器發送的信息,封裝成ByteBuf
            ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);

            //5.構造一個http的相應,即 httpresponse
            FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, 
                HttpResponseStatus.OK, 
                content);
			//6.設置響應頭信息-響應格式
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            //7.設置響應頭信息-響應數據長度
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
            //8.將構建好 response返回
            ctx.writeAndFlush(response);
        }
    }
}

代碼說明:

需要獲取瀏覽器請求的 uri,并且手工判斷 uri 是否等于 /favicon.ico,如果是則不往下處理;同類我們可以判斷是否是 js、css、img 等資源文件。

5. 小結

本節主要是了解了 Netty 如何開發一個 Web 服務器,并且和瀏覽器進行通信,需要注意的地方有幾點,具體如下:

  1. 格式要求,無論是解碼和編碼都需要嚴格按照 Http 協議格式要求,否則給瀏覽器響應數據時,瀏覽器不能識別;
  2. 可以跟進 Http 格式,獲取和設置相關信息,比如:請求 IP 地址、請求 uri 地址、請求方式、請求頭內容、請求體內容等;響應頭、響應體等;
  3. 靜態資源的過濾,一般情況下需要過濾掉,否則消耗服務器資源。