netty系列之:使用netty實現支援http2的伺服器

flydean發表於2021-10-22

簡介

上一篇文章中,我們提到了如何在netty中配置TLS,讓他支援HTTP2。事實上TLS並不是https的一個必須要求,它只是建議的標準。那麼除了TLS之外,還需要如何設定才能讓netty支援http2呢?一起來看看吧。

基本流程

netty支援http2有兩種情況,第一種情況是使用tls,在這種情況下需要新增一個ProtocolNegotiationHandler來對握手之後的協議進行協商,在協商之後,需要決定到底使用哪一種協議。

上一篇文章,我們已經介紹TLS支援http2的細節了,這裡不再贅述,感興趣的朋友可以檢視我之前的文章。

如果不使用tls,那麼有兩種情況,一種是直接使用http1.1了,我們需要為http1.1新增一個ChannelInboundHandler即可。

另一種情況就是使用clear text從HTTP1.1升級到HTTP2。

HTTP/2 ClearText也叫做h2c,我們看一個簡單的升級請求,首先是客戶端請求:

GET /index HTTP/1.1
Host: server.flydean.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c 
HTTP2-Settings: (SETTINGS payload) 

然後是伺服器端的響應,如果伺服器端不支援升級,則返回:


HTTP/1.1 200 OK 
Content-length: 100
Content-type: text/html

(... HTTP/1.1 response ...)

如果伺服器支援升級,則返回:

HTTP/1.1 101 Switching Protocols 
Connection: Upgrade
Upgrade: h2c

(... HTTP/2 response ...)

CleartextHttp2ServerUpgradeHandler

有了上面的基本流程,我們只需要在netty中提供對應的handler類就可以解決netty對http2的支援了。

不過上面的升級流程看起來比較複雜,所以netty為我們提供了一個封裝好的類:CleartextHttp2ServerUpgradeHandler來實現h2c的功能。

這個類需要傳入3個引數,分別是HttpServerCodec、HttpServerUpgradeHandler和ChannelHandler。

HttpServerCodec就是處理http server的編碼類,一般我們使用HttpServerCodec。

HttpServerUpgradeHandler是從http1.1升級到http2的處理類。

netty也提供了一個現成的類:HttpServerUpgradeHandler,來處理升級的編碼。

HttpServerUpgradeHandler需要兩個引數,一個是sourceCodec,也就是http原始的編碼類HttpServerCodec,一個是用來返回UpgradeCodec的工廠類,返回netty自帶的Http2ServerUpgradeCodec。

    public HttpServerUpgradeHandler(SourceCodec sourceCodec, UpgradeCodecFactory upgradeCodecFactory) {
        this(sourceCodec, upgradeCodecFactory, 0);
    }

ChannelHandler是真正處理HTTP2的handler,我們可以根據需要對這個handler進行自定義。

有了UpgradeHandler,將其加入ChannelPipeline即可。

Http2ConnectionHandler

不管是HttpServerUpgradeHandler,還是CleartextHttp2ServerUpgradeHandler,都需要傳入一個真正能夠處理http2的handler。這個handler就是Http2ConnectionHandler。

Http2ConnectionHandler是一個實現類,它已經實現了處理各種inbound frame events的事件,然後將這些事件委託給 Http2FrameListener。

所以Http2ConnectionHandler需要跟Http2FrameListener配合使用。

這裡要詳細講解一下Http2FrameListener,它主要處理HTTP2 frame的各種事件。

先來看下http2FrameListener中提供的event trigger方法:

從上圖可以看到,主要是各種frame的事件觸發方法,其中http2中有這樣幾種frame:

  • DATA frame
  • HEADERS frame
  • PRIORITY frame
  • RST_STREAM frame
  • SETTINGS acknowledgment frame
  • SETTINGS frame
  • PING frame
  • PING acknowledgment
  • PUSH_PROMISE frame
  • GO_AWAY frame
  • WINDOW_UPDATE frame
  • Unknown Frame

這幾種frame基本上列舉了http2 frame中所有的型別。

我們要做的就是自定義一個handler類,繼承Http2ConnectionHandler,然後實現Http2FrameListener介面即可。

    public final class CustHttp2Handler extends Http2ConnectionHandler implements Http2FrameListener

在使用clear text從HTTP1.1升級到HTTP2的過程中,我們需要處理兩個事情,第一個事情就是處理http1.1使用http頭升級到http2,可以重寫繼承自Http2ConnectionHandler的userEventTriggered方法,通過判斷event的型別是否是UpgradeEvent,來觸發對應的Http2FrameListener介面中的方法,比如這裡的onHeadersRead:


    /**
     * 處理HTTP upgrade事件
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof HttpServerUpgradeHandler.UpgradeEvent) {
            HttpServerUpgradeHandler.UpgradeEvent upgradeEvent =
                    (HttpServerUpgradeHandler.UpgradeEvent) evt;
            onHeadersRead(ctx, 1, upgradeToHttp2Headers(upgradeEvent.upgradeRequest()), 0 , true);
        }
        super.userEventTriggered(ctx, evt);
    }

upgradeToHttp2Headers方法將傳入的FullHttpRequest,轉換成為Http2Headers:

    private static Http2Headers upgradeToHttp2Headers(FullHttpRequest request) {
        CharSequence host = request.headers().get(HttpHeaderNames.HOST);
        Http2Headers http2Headers = new DefaultHttp2Headers()
                .method(HttpMethod.GET.asciiName())
                .path(request.uri())
                .scheme(HttpScheme.HTTP.name());
        if (host != null) {
            http2Headers.authority(host);
        }
        return http2Headers;
    }

還有一個要實現的方法,就是sendResponse方法,將資料寫回給客戶端,回寫需要包含headers和data兩部分,如下所示:

    /**
     * 傳送響應資料到客戶端
     */
    private void sendResponse(ChannelHandlerContext ctx, int streamId, ByteBuf payload) {
        Http2Headers headers = new DefaultHttp2Headers().status(OK.codeAsText());
        encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
        encoder().writeData(ctx, streamId, payload, 0, true, ctx.newPromise());
    }

總結

到此,一個處理clear text從HTTP1.1升級到HTTP2的handler就做好了。加上之前講解的TLS擴充套件協議的支援,就構成了一個完整的支援http2的netty伺服器。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/27-netty-http2/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章