1. 程式人生 > >手寫一個類SpringBoot的HTTP框架:幾十行程式碼基於Netty搭建一個 HTTP Server

手寫一個類SpringBoot的HTTP框架:幾十行程式碼基於Netty搭建一個 HTTP Server

> 本文已經收錄進 : [https://github.com/Snailclimb/netty-practical-tutorial](https://github.com/Snailclimb/netty-practical-tutorial) (Netty 從入門到實戰:手寫 HTTP Server+RPC 框架)。 > 相關專案:[https://github.com/Snailclimb/jsoncat](https://github.com/Snailclimb/jsoncat) (仿 Spring Boot 但不同於 Spring Boot 的一個輕量級的 HTTP 框架) 目前正在寫的一個叫做 [jsoncat](https://github.com/Snailclimb/jsoncat) 的輕量級 HTTP 框架內建的 HTTP 伺服器是我自己基於 Netty 寫的,所有的核心程式碼加起來不過就幾十行。這得益於 Netty 提供的各種開箱即用的元件,為我們節省了太多事情。 這篇文章我會手把手帶著小夥伴們實現一個簡易的 HTTP Server。 _如果文章有任何需要改善和完善的地方,歡迎在評論區指出,共同進步!_ 開始之前為了避免有小夥伴不瞭解 Netty ,還是先來簡單介紹它! ## 什麼是 Netty? 簡單用 3 點來概括一下 Netty 吧! 1. Netty 是一個基於 **NIO** 的 client-server(客戶端伺服器)框架,使用它可以快速簡單地開發網路應用程式。 2. Netty 極大地簡化並優化了 TCP 和 UDP 套接字伺服器等網路程式設計,並且效能以及安全性等很多方面都要更好。 3. Netty **支援多種協議** 如 FTP,SMTP,HTTP 以及各種二進位制和基於文字的傳統協議。本文所要寫的 HTTP Server 就得益於 Netty 對 HTTP 協議(超文字傳輸協議)的支援。 ## Netty 應用場景有哪些? 憑藉自己的瞭解,簡單說一下吧!理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做並且更好。 不過,我們還是首先要明確的是 Netty 主要用來做**網路通訊** 。 1. **實現框架的網路通訊模組** : Netty 幾乎滿足任何場景的網路通訊需求,因此,框架的網路通訊模組可以基於 Netty 來做。拿 RPC 框架來說! 我們在分散式系統中,不同服務節點之間經常需要相互呼叫,這個時候就需要 RPC 框架了。不同服務指點的通訊是如何做的呢?那就可以使用 Netty 來做了!比如我呼叫另外一個節點的方法的話,至少是要讓對方知道我呼叫的是哪個類中的哪個方法以及相關引數吧! 2. **實現一個自己的 HTTP 伺服器** :通過 Netty ,我們可以很方便地使用少量程式碼實現一個簡單的 HTTP 伺服器。Netty 自帶了編解碼器和訊息聚合器,為我們開發節省了很多事! 3. **實現一個即時通訊系統** : 使用 Netty 我們可以實現一個可以聊天類似微信的即時通訊系統,這方面的開源專案還蠻多的,可以自行去 Github 找一找。 4. **實現訊息推送系統** :市面上有很多訊息推送系統都是基於 Netty 來做的。 5. ...... ## 那些開源專案用到了 Netty? 我們平常經常接觸的 Dubbo、RocketMQ、Elasticsearch、gRPC 、Spring Cloud Gateway 等等都用到了 Netty。 可以說大量的開源專案都用到了 Netty,所以掌握 Netty 有助於你更好的使用這些開源專案並且讓你有能力對其進行二次開發。 實際上還有很多很多優秀的專案用到了 Netty,Netty 官方也做了統計,統計結果在這裡:https://netty.io/wiki/related-projects.html 。 ## 實現 HTTP Server 必知的前置知識 既然,我們要實現 HTTP Server 那必然先要回顧一下 HTTP 協議相關的基礎知識。 ### HTTP 協議 **超文字傳輸協議(HTTP,HyperText Transfer Protocol)主要是為 Web 瀏覽器與 Web 伺服器之間的通訊而設計的。** 當我們使用瀏覽器瀏覽網頁的時候,我們網頁就是通過 HTTP 請求進行載入的,整個過程如下圖所示。 ![HTTP請求過程](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161641335-589275731.png)

https://www.seobility.net/en/wiki/HTTP_headers

**HTTP 協議是基於 TCP 協議的**,因此,傳送 HTTP 請求之前首先要建立 TCP 連線也就是要經歷 3 次握手。目前使用的 HTTP 協議大部分都是 1.1。在 1.1 的協議裡面,預設是開啟了 Keep-Alive 的,這樣的話建立的連線就可以在多次請求中被複用了。 瞭解了 HTTP 協議之後,我們再來看一下 HTTP 報文的內容,這部分內容很重要!(參考圖片來自:[https://iamgopikrishna.wordpress.com/2014/06/13/4/](https://iamgopikrishna.wordpress.com/2014/06/13/4/)) **HTTP 請求報文:** ![HTTP 請求報文](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161641585-607603671.png) **HTTP 響應報文:** ![HTTP 響應報文](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161641921-1958985143.png) **我們的 HTTP 伺服器會在後臺解析 HTTP 請求報文內容,然後根據報文內容進行處理之後返回 HTTP 響應報文給客戶端。** ### Netty 編解碼器 如果我們要通過 Netty 處理 HTTP 請求,需要先進行編解碼。所謂編解碼說白了就是在 Netty 傳輸資料所用的 `ByteBuf` 和 Netty 中針對 HTTP 請求和響應所提供的物件比如 `HttpRequest` 和 `HttpContent`之間互相轉換。 Netty 自帶了 4 個常用的編解碼器: 1. `HttpRequestEncoder` (HTTP 請求編碼器):將 `HttpRequest` 和 `HttpContent` 編碼為 `ByteBuf` 。 2. `HttpRequestDecoder` (HTTP 請求解碼器):將 `ByteBuf` 解碼為 `HttpRequest` 和 `HttpContent` 3. `HttpResponsetEncoder` (HTTP 響應編碼器):將 `HttpResponse` 和 `HttpContent` 編碼為 `ByteBuf` 。 4. `HttpResponseDecoder`(HTTP 響應解碼器):將 `ByteBuf` 解碼為 `HttpResponst` 和 `HttpContent` **網路通訊最終都是通過位元組流進行傳輸的。 `ByteBuf` 是 Netty 提供的一個位元組容器,其內部是一個位元組陣列。** 當我們通過 Netty 傳輸資料的時候,就是通過 `ByteBuf` 進行的。 **HTTP Server 端用於接收 HTTP Request,然後傳送 HTTP Response。因此我們只需要 `HttpRequestDecoder` 和 `HttpResponseEncoder` 即可。** 我手繪了一張圖,這樣看著應該更容易理解了。 ![](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161642310-1343688893.png) ### Netty 對 HTTP 訊息的抽象 為了能夠表示 HTTP 中的各種訊息,Netty 設計了抽象了一套完整的 HTTP 訊息結構圖,核心繼承關係如下圖所示。 ![](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161642674-1173245271.png) 1. `HttpObject` : 整個 HTTP 訊息體系結構的最上層介面。`HttpObject` 介面下又有 `HttpMessage` 和`HttpContent`兩大核心介面。 2. `HttpMessage`: 定義 HTTP 訊息,為`HttpRequest`和`HttpResponse`提供通用屬性 3. `HttpRequest` : `HttpRequest`對應 HTTP request。通過 `HttpRequest` 我們可以訪問查詢引數(Query Parameters)和 Cookie。和 Servlet API 不同的是,查詢引數是通過`QueryStringEncoder`和`QueryStringDecoder`來構造和解析查詢查詢引數。 4. `HttpResponse` : `HttpResponse` 對應 HTTP response。和`HttpMessage`相比,`HttpResponse` 增加了 status(相應狀態碼) 屬性及其對應的方法。 5. `HttpContent` : **分塊傳輸編碼**(**Chunked transfer encoding**)是超文字傳輸協議(HTTP)中的一種資料傳輸機制(HTTP/1.1 才有),允許 HTTP 由應用伺服器傳送給客戶端應用( 通常是網頁瀏覽器)的資料可以分成多“塊”(資料量比較大的情況)。我們可以把 `HttpContent` 看作是這一塊一塊的資料。 6. `LastHttpContent` : 標識 HTTP 請求結束,同時包含 `HttpHeaders` 物件。 7. `FullHttpRequest` 和 `FullHttpResponse` : `HttpMessage` 和 `HttpContent` 聚合後得到的物件。 ![](https://img2020.cnblogs.com/other/1843652/202010/1843652-20201008161643062-1235625225.png) ### HTTP 訊息聚合器 `HttpObjectAggregator` 是 Netty 提供的 HTTP 訊息聚合器,通過它可以把 `HttpMessage` 和 `HttpContent` 聚合成一個 `FullHttpRequest` 或者 `FullHttpResponse`(取決於是處理請求還是響應),方便我們使用。 另外,訊息體比較大的話,可能還會分成好幾個訊息體來處理,`HttpObjectAggregator` 可以將這些訊息聚合成一個完整的,方便我們處理。 使用方法:將 `HttpObjectAggregator` 新增到 `ChannelPipeline` 中,如果是用於處理 HTTP Request 就將其放在 `HttpResponseEncoder` 之後,反之,如果用於處理 HTTP Response 就將其放在 `HttpResponseDecoder` 之後。 因為,HTTP Server 端用於接收 HTTP Request,對應的使用方式如下。 ```java ChannelPipeline p = ...; p.addLast("decoder", new HttpRequestDecoder()) .addLast("encoder", new HttpResponseEncoder()) .addLast("aggregator", new HttpObjectAggregator(512 * 1024)) .addLast("handler", new HttpServerHandler()); ``` ## 基於 Netty 實現一個 HTTP Server **通過 Netty,我們可以很方便地使用少量程式碼構建一個可以正確處理 GET 請求和 POST 請求的輕量級 HTTP Server。** 原始碼地址:[https://github.com/Snailclimb/netty-practical-tutorial/tree/master/example/http-server](https://github.com/Snailclimb/netty-practical-tutorial/tree/master/example/http-server) 。 ### 新增所需依賴到 pom.xml 第一步,我們需要將實現 HTTP Server 所必需的第三方依賴的座標新增到 `pom.xml`中。 ```xml ``` ### 建立服務端 ```java @Slf4j public class HttpServer { private static final int PORT = 8080; public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // TCP預設開啟了 Nagle 演算法,該演算法的作用是儘可能的傳送大資料快,減少網路傳輸。TCP_NODELAY 引數的作用就是控制是否啟用 Nagle 演算法。 .childOption(ChannelOption.TCP_NODELAY, true) // 是否開啟 TCP 底層心跳機制 .childOption(ChannelOption.SO_KEEPALIVE, true) //表示系統用於臨時存放已完成三次握手的請求的佇列的最大長度,如果連線建立頻繁,伺服器處理建立新連線較慢,可以適當調大這個引數 .option(ChannelOption.SO_BACKLOG, 128) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelIni