構(gòu)建 Netty HTTP/HTTPS 應(yīng)用

2018-08-08 10:42 更新

最常見(jiàn)的一種協(xié)議是 HTTP/HTTPS ,尤其在智能手機(jī)上更是廣泛使用。雖然我們可以通過(guò)HTTP或HTTPS訪問(wèn)一家公司的主頁(yè),但是它其實(shí)還有別的用處。就像許多組織會(huì)通過(guò)HTTP(S)來(lái)公開(kāi) WebService API ,這樣做的目的是可以緩解獨(dú)立平臺(tái)帶來(lái)的弊端。

下面讓我們來(lái)看看 Netty 提供的 ChannelHandler是怎樣允許您使用 HTTP 和 HTTPS 而無(wú)需編寫自己的編解碼器。

HTTP Decoder, Encoder 和 Codec

HTTP 是請(qǐng)求-響應(yīng)模式,客戶端發(fā)送一個(gè) HTTP 請(qǐng)求,服務(wù)就響應(yīng)此請(qǐng)求。Netty 提供了簡(jiǎn)單的編碼、解碼器來(lái)簡(jiǎn)化基于這個(gè)協(xié)議的開(kāi)發(fā)工作。圖8.2和圖8.3顯示 HTTP 請(qǐng)求和響應(yīng)的方法是如何生產(chǎn)和消費(fèi)的

Figure%208

  1. HTTP Request 第一部分是包含的頭信息
  2. HttpContent 里面包含的是數(shù)據(jù),可以后續(xù)有多個(gè) HttpContent 部分
  3. LastHttpContent 標(biāo)記是 HTTP request 的結(jié)束,同時(shí)可能包含頭的尾部信息
  4. 完整的 HTTP request

Figure 8.2 HTTP request component parts

Figure%208

  1. HTTP response 第一部分是包含的頭信息
  2. HttpContent 里面包含的是數(shù)據(jù),可以后續(xù)有多個(gè) HttpContent 部分
  3. LastHttpContent 標(biāo)記是 HTTP response 的結(jié)束,同時(shí)可能包含頭的尾部信息
  4. 完整的 HTTP response

Figure 8.3 HTTP response component parts

如圖8.2和8.3所示的 HTTP 請(qǐng)求/響應(yīng)可能包含不止一個(gè)數(shù)據(jù)部分,它總是終止于 LastHttpContent 部分。FullHttpRequest 和FullHttpResponse 消息是特殊子類型,分別表示一個(gè)完整的請(qǐng)求和響應(yīng)。所有類型的 HTTP 消息(FullHttpRequest ,LastHttpContent 以及那些如清單8.2所示)實(shí)現(xiàn) HttpObject 接口。

表8.2概述 HTTP 解碼器和編碼器的處理和生產(chǎn)這些消息。

Table 8.2 HTTP decoder and encoder

名稱描述
HttpRequestEncoderEncodes HttpRequest , HttpContent and LastHttpContent messages to bytes.
HttpResponseEncoderEncodes HttpResponse, HttpContent and LastHttpContent messages to bytes.
HttpRequestDecoderDecodes bytes into HttpRequest, HttpContent and LastHttpContent messages.
HttpResponseDecoderDecodes bytes into HttpResponse, HttpContent and LastHttpContent messages.

清單8.2所示的是將支持 HTTP 添加到您的應(yīng)用程序是多么簡(jiǎn)單。僅僅添加正確的 ChannelHandler 到 ChannelPipeline 中

Listing 8.2 Add support for HTTP

public class HttpPipelineInitializer extends ChannelInitializer<Channel> {

    private final boolean client;

    public HttpPipelineInitializer(boolean client) {
        this.client = client;
    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (client) {
            pipeline.addLast("decoder", new HttpResponseDecoder());  //1
            pipeline.addLast("encoder", new HttpRequestEncoder());  //2
        } else {
            pipeline.addLast("decoder", new HttpRequestDecoder());  //3
            pipeline.addLast("encoder", new HttpResponseEncoder());  //4
        }
    }
}
  1. client: 添加 HttpResponseDecoder 用于處理來(lái)自 server 響應(yīng)
  2. client: 添加 HttpRequestEncoder 用于發(fā)送請(qǐng)求到 server
  3. server: 添加 HttpRequestDecoder 用于接收來(lái)自 client 的請(qǐng)求
  4. server: 添加 HttpResponseEncoder 用來(lái)發(fā)送響應(yīng)給 client

HTTP消息聚合

安裝 ChannelPipeline 中的初始化之后,你能夠?qū)Σ煌?HttpObject 消息進(jìn)行操作。但由于 HTTP 請(qǐng)求和響應(yīng)可以由許多部分組合而成,你需要聚合他們形成完整的消息。為了消除這種繁瑣任務(wù), Netty 提供了一個(gè)聚合器,合并消息部件到 FullHttpRequest 和 FullHttpResponse 消息。這樣您總是能夠看到完整的消息內(nèi)容。

這個(gè)操作有一個(gè)輕微的成本,消息段需要緩沖,直到完全可以將消息轉(zhuǎn)發(fā)到下一個(gè) ChannelInboundHandler 管道。但好處是,你不必?fù)?dān)心消息碎片。

實(shí)現(xiàn)自動(dòng)聚合只需添加另一個(gè) ChannelHandler 到 ChannelPipeline。清單8.3顯示了這是如何實(shí)現(xiàn)的。

Listing 8.3 Automatically aggregate HTTP message fragments

public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {

    private final boolean client;

    public HttpAggregatorInitializer(boolean client) {
        this.client = client;
    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (client) {
            pipeline.addLast("codec", new HttpClientCodec());  //1
        } else {
            pipeline.addLast("codec", new HttpServerCodec());  //2
        }
        pipeline.addLast("aggegator", new HttpObjectAggregator(512 * 1024));  //3
    }
}
  1. client: 添加 HttpClientCodec
  2. server: 添加 HttpServerCodec 作為我們是 server 模式時(shí)
  3. 添加 HttpObjectAggregator 到 ChannelPipeline, 使用最大消息值是 512kb

HTTP 壓縮

使用 HTTP 時(shí)建議壓縮數(shù)據(jù)以減少傳輸流量,壓縮數(shù)據(jù)會(huì)增加 CPU 負(fù)載,現(xiàn)在的硬件設(shè)施都很強(qiáng)大,大多數(shù)時(shí)候壓縮數(shù)據(jù)時(shí)一個(gè)好主意。Netty 支持“gzip”和“deflate”,為此提供了兩個(gè) ChannelHandler 實(shí)現(xiàn)分別用于壓縮和解壓??聪旅娲a:

HTTP Request Header

客戶端可以通過(guò)提供下面的頭顯示支持加密模式。然而服務(wù)器不是,所以不得不壓縮它發(fā)送的數(shù)據(jù)。

GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate

下面是一個(gè)例子

Listing 8.4 Automatically compress HTTP messages

public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {

    private final boolean isClient;
    public HttpAggregatorInitializer(boolean isClient) {
        this.isClient = isClient;
    }
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        if (isClient) {
            pipeline.addLast("codec", new HttpClientCodec()); //1
            pipeline.addLast("decompressor",new HttpContentDecompressor()); //2
        } else {
            pipeline.addLast("codec", new HttpServerCodec()); //3
            pipeline.addLast("compressor",new HttpContentCompressor()); //4
        }
    }
}
  1. client: 添加 HttpClientCodec
  2. client: 添加 HttpContentDecompressor 用于處理來(lái)自服務(wù)器的壓縮的內(nèi)容
  3. server: HttpServerCodec
  4. server: HttpContentCompressor 用于壓縮來(lái)自 client 支持的 HttpContentCompressor

壓縮與依賴

注意,Java 6或者更早版本,如果要壓縮數(shù)據(jù),需要添加 jzlib 到 classpath

<dependency>
    <groupId>com.jcraft</groupId>
        <artifactId>jzlib</artifactId>
    <version>1.1.3</version>
</dependency>

使用 HTTPS

啟用 HTTPS,只需添加 SslHandler

Listing 8.5 Using HTTPS

public class HttpsCodecInitializer extends ChannelInitializer<Channel> {

    private final SslContext context;
    private final boolean client;

    public HttpsCodecInitializer(SslContext context, boolean client) {
        this.context = context;
        this.client = client;
    }

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        SSLEngine engine = context.newEngine(ch.alloc());
        pipeline.addFirst("ssl", new SslHandler(engine));  //1

        if (client) {
            pipeline.addLast("codec", new HttpClientCodec());  //2
        } else {
            pipeline.addLast("codec", new HttpServerCodec());  //3
        }
    }
}
  1. 添加 SslHandler 到 pipeline 來(lái)啟用 HTTPS
  2. client: 添加 HttpClientCodec
  3. server: 添加 HttpServerCodec ,如果是 server 模式的話

上面的代碼就是一個(gè)很好的例子,解釋了 Netty 的架構(gòu)是如何讓“重用”變成了“杠桿”。我們可以添加一個(gè)新的功能,甚至是一樣重要的加密支持,幾乎沒(méi)有工作量,只需添加一個(gè)ChannelHandler 到 ChannelPipeline。

WebSocket

HTTP 是不錯(cuò)的協(xié)議,但是如果需要實(shí)時(shí)發(fā)布信息怎么做?有個(gè)做法就是客戶端一直輪詢請(qǐng)求服務(wù)器,這種方式雖然可以達(dá)到目的,但是其缺點(diǎn)很多,也不是優(yōu)秀的解決方案,為了解決這個(gè)問(wèn)題,便出現(xiàn)了 WebSocket。

WebSocket 允許數(shù)據(jù)雙向傳輸,而不需要請(qǐng)求-響應(yīng)模式。早期的WebSocket 只能發(fā)送文本數(shù)據(jù),然后現(xiàn)在不僅可以發(fā)送文本數(shù)據(jù),也可以發(fā)送二進(jìn)制數(shù)據(jù),這使得可以使用 WebSocket 構(gòu)建你想要的程序。下圖是WebSocket 的通信示例圖:

WebSocket 規(guī)范及其實(shí)現(xiàn)是為了一個(gè)更有效的解決方案。簡(jiǎn)單的說(shuō), 一個(gè)WebSocket 提供一個(gè) TCP 連接兩個(gè)方向的交通。結(jié)合 WebSocket API 它提供了一個(gè)替代 HTTP 輪詢雙向通信從頁(yè)面到遠(yuǎn)程服務(wù)器。

也就是說(shuō),WebSocket 提供真正的雙向客戶機(jī)和服務(wù)器之間的數(shù)據(jù)交換。 我們不會(huì)對(duì)內(nèi)部太多的細(xì)節(jié),但我們應(yīng)該提到,雖然最早實(shí)現(xiàn)僅限于文本數(shù)據(jù),但現(xiàn)在不再是這樣,WebSocket可以用于任意數(shù)據(jù),就像一個(gè)正常的套接字。

圖8.4給出了一個(gè)通用的 WebSocket 協(xié)議。在這種情況下的通信開(kāi)始于普通 HTTP ,并“升級(jí)”為雙向 WebSocket。

Figure%208

  1. Client (HTTP) 與 Server 通訊
  2. Server (HTTP) 與 Client 通訊
  3. Client 通過(guò) HTTP(s) 來(lái)進(jìn)行 WebSocket 握手,并等待確認(rèn)
  4. 連接協(xié)議升級(jí)至 WebSocket

Figure 8.4 WebSocket protocol

添加應(yīng)用程序支持 WebSocket 只需要添加適當(dāng)?shù)目蛻舳嘶蚍?wù)器端WebSocket ChannelHandler 到管道。這個(gè)類將處理特殊 WebSocket 定義的消息類型,稱為“幀。“如表8.3所示,這些可以歸類為“數(shù)據(jù)”和“控制”幀。

Table 8.3 WebSocketFrame types

名稱描述
BinaryWebSocketFrameData frame: binary data
TextWebSocketFrameData frame: text data
ContinuationWebSocketFrameData frame: text or binary data that belongs to a previous BinaryWebSocketFrame or TextWebSocketFrame
CloseWebSocketFrameControl frame: a CLOSE request, close status code and a phrase
PingWebSocketFrameControl frame: requests the send of a PongWebSocketFrame
PongWebSocketFrameControl frame: sent as response to a PingWebSocketFrame

由于 Netty 的主要是一個(gè)服務(wù)器端技術(shù)重點(diǎn)在這里創(chuàng)建一個(gè) WebSocket server 。清單8.6使用 WebSocketServerProtocolHandler 提出了一個(gè)簡(jiǎn)單的例子。該類處理協(xié)議升級(jí)握手以及三個(gè)“控制”幀 Close, Ping 和 Pong。Text 和 Binary 數(shù)據(jù)幀將被傳遞到下一個(gè)處理程序(由你實(shí)現(xiàn))進(jìn)行處理。

Listing 8.6 Support WebSocket on the server

public class WebSocketServerInitializer extends ChannelInitializer<Channel> {
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(
                new HttpServerCodec(),
                new HttpObjectAggregator(65536),  //1
                new WebSocketServerProtocolHandler("/websocket"),  //2
                new TextFrameHandler(),  //3
                new BinaryFrameHandler(),  //4
                new ContinuationFrameHandler());  //5
    }

    public static final class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
            // Handle text frame
        }
    }

    public static final class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
            // Handle binary frame
        }
    }

    public static final class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
        @Override
        public void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
            // Handle continuation frame
        }
    }
}
  1. 添加 HttpObjectAggregator 用于提供在握手時(shí)聚合 HttpRequest
  2. 添加 WebSocketServerProtocolHandler 用于處理色好給你寄握手如果請(qǐng)求是發(fā)送到"/websocket." 端點(diǎn),當(dāng)升級(jí)完成后,它將會(huì)處理Ping, Pong 和 Close 幀
  3. TextFrameHandler 將會(huì)處理 TextWebSocketFrames
  4. BinaryFrameHandler 將會(huì)處理 BinaryWebSocketFrames
  5. ContinuationFrameHandler 將會(huì)處理ContinuationWebSocketFrames

加密 WebSocket 只需插入 SslHandler 到作為 pipline 第一個(gè) ChannelHandler

詳見(jiàn) Chapter 11 WebSocket

SPDY

SPDY(讀作“SPeeDY”)是Google 開(kāi)發(fā)的基于 TCP 的應(yīng)用層協(xié)議,用以最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶的網(wǎng)絡(luò)使用體驗(yàn)。SPDY 并不是一種用于替代 HTTP 的協(xié)議,而是對(duì) HTTP 協(xié)議的增強(qiáng)。SPDY 實(shí)現(xiàn)技術(shù):

  • 壓縮報(bào)頭
  • 加密所有
  • 多路復(fù)用連接
  • 提供支持不同的傳輸優(yōu)先級(jí)

SPDY 主要有5個(gè)版本:

  • 1 - 初始化版本,但沒(méi)有使用
  • 2 - 新特性,包含服務(wù)器推送
  • 3 - 新特性包含流控制和更新壓縮
  • 3.1 - 會(huì)話層流程控制
  • 4.0 - 流量控制,并與 HTTP 2.0 更加集成

SPDY 被很多瀏覽器支持,包括 Google Chrome, Firefox, 和 Opera

Netty 支持 版本 2 和 3 (包含3.1)的支持。這些版本被廣泛應(yīng)用,可以支持更多的用戶。更多內(nèi)容詳見(jiàn) Chapter 12


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)