手寫“SpringBoot”:幾十行代碼基于Netty搭建一個(gè) HTTP Server
本文已經(jīng)收錄進(jìn) : https://github.com/Snailclimb/netty-practical-tutorial (Netty 從入門到實(shí)戰(zhàn):手寫 HTTP Server+RPC 框架)。
相關(guān)項(xiàng)目:https://github.com/Snailclimb/jsoncat?(仿 Spring Boot 但不同于 Spring Boot 的一個(gè)輕量級(jí)的 HTTP 框架)
目前正在寫的一個(gè)叫做 jsoncat 的輕量級(jí) HTTP 框架內(nèi)置的 HTTP 服務(wù)器是我自己基于 Netty 寫的,所有的核心代碼加起來不過就幾十行。這得益于 Netty 提供的各種開箱即用的組件,為我們節(jié)省了太多事情。
這篇文章我會(huì)手把手帶著小伙伴們實(shí)現(xiàn)一個(gè)簡易的 HTTP Server。
如果文章有任何需要改善和完善的地方,歡迎在評(píng)論區(qū)指出,共同進(jìn)步!
開始之前為了避免有小伙伴不了解 Netty ,還是先來簡單介紹它!
什么是 Netty?
簡單用 3 點(diǎn)來概括一下 Netty 吧!
Netty 是一個(gè)基于 NIO 的 client-server(客戶端服務(wù)器)框架,使用它可以快速簡單地開發(fā)網(wǎng)絡(luò)應(yīng)用程序。 Netty 極大地簡化并優(yōu)化了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程,并且性能以及安全性等很多方面都要更好。 Netty 支持多種協(xié)議 如 FTP,SMTP,HTTP 以及各種二進(jìn)制和基于文本的傳統(tǒng)協(xié)議。本文所要寫的 HTTP Server 就得益于 Netty 對 HTTP 協(xié)議(超文本傳輸協(xié)議)的支持。
Netty 應(yīng)用場景有哪些?
憑借自己的了解,簡單說一下吧!理論上來說,NIO 可以做的事情 ,使用 Netty 都可以做并且更好。
不過,我們還是首先要明確的是 Netty 主要用來做網(wǎng)絡(luò)通信 。
實(shí)現(xiàn)框架的網(wǎng)絡(luò)通信模塊 :Netty 幾乎滿足任何場景的網(wǎng)絡(luò)通信需求,因此,框架的網(wǎng)絡(luò)通信模塊可以基于 Netty 來做。拿 RPC 框架來說!我們在分布式系統(tǒng)中,不同服務(wù)節(jié)點(diǎn)之間經(jīng)常需要相互調(diào)用,這個(gè)時(shí)候就需要 RPC 框架了。不同服務(wù)指點(diǎn)的通信是如何做的呢?那就可以使用 Netty 來做了!比如我調(diào)用另外一個(gè)節(jié)點(diǎn)的方法的話,至少是要讓對方知道我調(diào)用的是哪個(gè)類中的哪個(gè)方法以及相關(guān)參數(shù)吧! 實(shí)現(xiàn)一個(gè)自己的 HTTP 服務(wù)器 :通過 Netty ,我們可以很方便地使用少量代碼實(shí)現(xiàn)一個(gè)簡單的 HTTP 服務(wù)器。Netty 自帶了編解碼器和消息聚合器,為我們開發(fā)節(jié)省了很多事! 實(shí)現(xiàn)一個(gè)即時(shí)通訊系統(tǒng) :使用 Netty 我們可以實(shí)現(xiàn)一個(gè)可以聊天類似微信的即時(shí)通訊系統(tǒng),這方面的開源項(xiàng)目還蠻多的,可以自行去 Github 找一找。 實(shí)現(xiàn)消息推送系統(tǒng) :市面上有很多消息推送系統(tǒng)都是基于 Netty 來做的。 ......
那些開源項(xiàng)目用到了 Netty?
我們平常經(jīng)常接觸的 Dubbo、RocketMQ、Elasticsearch、gRPC 、Spring Cloud Gateway 等等都用到了 Netty。
可以說大量的開源項(xiàng)目都用到了 Netty,所以掌握 Netty 有助于你更好的使用這些開源項(xiàng)目并且讓你有能力對其進(jìn)行二次開發(fā)。
實(shí)際上還有很多很多優(yōu)秀的項(xiàng)目用到了 Netty,Netty 官方也做了統(tǒng)計(jì),統(tǒng)計(jì)結(jié)果在這里:https://netty.io/wiki/related-projects.html 。
實(shí)現(xiàn) HTTP Server 必知的前置知識(shí)
既然,我們要實(shí)現(xiàn) HTTP Server 那必然先要回顧一下 HTTP 協(xié)議相關(guān)的基礎(chǔ)知識(shí)。
HTTP 協(xié)議
超文本傳輸協(xié)議(HTTP,HyperText Transfer Protocol)主要是為 Web 瀏覽器與 Web 服務(wù)器之間的通信而設(shè)計(jì)的。
當(dāng)我們使用瀏覽器瀏覽網(wǎng)頁的時(shí)候,我們網(wǎng)頁就是通過 HTTP 請求進(jìn)行加載的,整個(gè)過程如下圖所示。

https://www.seobility.net/en/wiki/HTTP_headers
HTTP 協(xié)議是基于 TCP 協(xié)議的,因此,發(fā)送 HTTP 請求之前首先要建立 TCP 連接也就是要經(jīng)歷 3 次握手。目前使用的 HTTP 協(xié)議大部分都是 1.1。在 1.1 的協(xié)議里面,默認(rèn)是開啟了 Keep-Alive 的,這樣的話建立的連接就可以在多次請求中被復(fù)用了。
了解了 HTTP 協(xié)議之后,我們再來看一下 HTTP 報(bào)文的內(nèi)容,這部分內(nèi)容很重要!(參考圖片來自:https://iamgopikrishna.wordpress.com/2014/06/13/4/)
HTTP 請求報(bào)文:

HTTP 響應(yīng)報(bào)文:

我們的 HTTP 服務(wù)器會(huì)在后臺(tái)解析 HTTP 請求報(bào)文內(nèi)容,然后根據(jù)報(bào)文內(nèi)容進(jìn)行處理之后返回 HTTP 響應(yīng)報(bào)文給客戶端。
Netty 編解碼器
如果我們要通過 Netty 處理 HTTP 請求,需要先進(jìn)行編解碼。所謂編解碼說白了就是在 Netty 傳輸數(shù)據(jù)所用的 ByteBuf 和 Netty 中針對 HTTP 請求和響應(yīng)所提供的對象比如 HttpRequest 和 HttpContent之間互相轉(zhuǎn)換。
Netty 自帶了 4 個(gè)常用的編解碼器:
HttpRequestEncoder(HTTP 請求編碼器):將HttpRequest和HttpContent編碼為ByteBuf。HttpRequestDecoder(HTTP 請求解碼器):將ByteBuf解碼為HttpRequest和HttpContentHttpResponsetEncoder(HTTP 響應(yīng)編碼器):將HttpResponse和HttpContent編碼為ByteBuf。HttpResponseDecoder(HTTP 響應(yīng)解碼器):將ByteBuf解碼為HttpResponst和HttpContent
網(wǎng)絡(luò)通信最終都是通過字節(jié)流進(jìn)行傳輸?shù)摹?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(150, 84, 181);">ByteBuf 是 Netty 提供的一個(gè)字節(jié)容器,其內(nèi)部是一個(gè)字節(jié)數(shù)組。 當(dāng)我們通過 Netty 傳輸數(shù)據(jù)的時(shí)候,就是通過 ByteBuf 進(jìn)行的。
HTTP Server 端用于接收 HTTP Request,然后發(fā)送 HTTP Response。因此我們只需要 HttpRequestDecoder 和 HttpResponseEncoder 即可。
我手繪了一張圖,這樣看著應(yīng)該更容易理解了。

Netty 對 HTTP 消息的抽象
為了能夠表示 HTTP 中的各種消息,Netty 設(shè)計(jì)了抽象了一套完整的 HTTP 消息結(jié)構(gòu)圖,核心繼承關(guān)系如下圖所示。

HttpObject: 整個(gè) HTTP 消息體系結(jié)構(gòu)的最上層接口。HttpObject接口下又有HttpMessage和HttpContent兩大核心接口。HttpMessage: 定義 HTTP 消息,為HttpRequest和HttpResponse提供通用屬性HttpRequest:HttpRequest對應(yīng) HTTP request。通過HttpRequest我們可以訪問查詢參數(shù)(Query Parameters)和 Cookie。和 Servlet API 不同的是,查詢參數(shù)是通過QueryStringEncoder和QueryStringDecoder來構(gòu)造和解析查詢查詢參數(shù)。HttpResponse:HttpResponse對應(yīng) HTTP response。和HttpMessage相比,HttpResponse增加了 status(相應(yīng)狀態(tài)碼) 屬性及其對應(yīng)的方法。HttpContent: 分塊傳輸編碼(Chunked transfer encoding)是超文本傳輸協(xié)議(HTTP)中的一種數(shù)據(jù)傳輸機(jī)制(HTTP/1.1 才有),允許 HTTP 由應(yīng)用服務(wù)器發(fā)送給客戶端應(yīng)用( 通常是網(wǎng)頁瀏覽器)的數(shù)據(jù)可以分成多“塊”(數(shù)據(jù)量比較大的情況)。我們可以把HttpContent看作是這一塊一塊的數(shù)據(jù)。LastHttpContent: 標(biāo)識(shí) HTTP 請求結(jié)束,同時(shí)包含HttpHeaders對象。FullHttpRequest和FullHttpResponse:HttpMessage和HttpContent聚合后得到的對象。

HTTP 消息聚合器
HttpObjectAggregator 是 Netty 提供的 HTTP 消息聚合器,通過它可以把 HttpMessage 和 HttpContent 聚合成一個(gè) FullHttpRequest 或者 FullHttpResponse(取決于是處理請求還是響應(yīng)),方便我們使用。
另外,消息體比較大的話,可能還會(huì)分成好幾個(gè)消息體來處理,HttpObjectAggregator 可以將這些消息聚合成一個(gè)完整的,方便我們處理。
使用方法:將 HttpObjectAggregator 添加到 ChannelPipeline 中,如果是用于處理 HTTP Request 就將其放在 HttpResponseEncoder 之后,反之,如果用于處理 HTTP Response 就將其放在 HttpResponseDecoder 之后。
因?yàn)?,HTTP Server 端用于接收 HTTP Request,對應(yīng)的使用方式如下。
ChannelPipeline?p?=?...;
?p.addLast("decoder",?new?HttpRequestDecoder())
??.addLast("encoder",?new?HttpResponseEncoder())
??.addLast("aggregator",?new?HttpObjectAggregator(512?*?1024))
??.addLast("handler",?new?HttpServerHandler());
基于 Netty 實(shí)現(xiàn)一個(gè) HTTP Server
通過 Netty,我們可以很方便地使用少量代碼構(gòu)建一個(gè)可以正確處理 GET 請求和 POST 請求的輕量級(jí) HTTP Server。
源代碼地址:https://github.com/Snailclimb/netty-practical-tutorial/tree/master/example/http-server 。
添加所需依賴到 pom.xml
第一步,我們需要將實(shí)現(xiàn) HTTP Server 所必需的第三方依賴的坐標(biāo)添加到 pom.xml中。
<dependency>
????<groupId>io.nettygroupId>
????<artifactId>netty-allartifactId>
????<version>4.1.42.Finalversion>
dependency>
<dependency>
????<groupId>org.slf4jgroupId>
????<artifactId>slf4j-apiartifactId>
????<version>1.7.25version>
dependency>
<dependency>
????<groupId>org.slf4jgroupId>
????<artifactId>slf4j-simpleartifactId>
????<version>1.7.25version>
dependency>
<dependency>
????<groupId>org.projectlombokgroupId>
????<artifactId>lombokartifactId>
????<version>1.18.8version>
????<scope>providedscope>
dependency>
<dependency>
????<groupId>commons-codecgroupId>
????<artifactId>commons-codecartifactId>
????<version>1.14version>
dependency>
創(chuàng)建服務(wù)端
@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默認(rèn)開啟了 Nagle 算法,該算法的作用是盡可能的發(fā)送大數(shù)據(jù)快,減少網(wǎng)絡(luò)傳輸。TCP_NODELAY 參數(shù)的作用就是控制是否啟用 Nagle 算法。
????????????????????.childOption(ChannelOption.TCP_NODELAY,?true)
????????????????????//?是否開啟?TCP?底層心跳機(jī)制
????????????????????.childOption(ChannelOption.SO_KEEPALIVE,?true)
????????????????????//表示系統(tǒng)用于臨時(shí)存放已完成三次握手的請求的隊(duì)列的最大長度,如果連接建立頻繁,服務(wù)器處理創(chuàng)建新連接較慢,可以適當(dāng)調(diào)大這個(gè)參數(shù)
????????????????????.option(ChannelOption.SO_BACKLOG,?128)
????????????????????.handler(new?LoggingHandler(LogLevel.INFO))
????????????????????.childHandler(new?ChannelInitializer()?{
????????????????????????@Override
????????????????????????protected?void?initChannel(SocketChannel?ch)?{
????????????????????????????ch.pipeline().addLast("decoder",?new?HttpRequestDecoder())
????????????????????????????????????.addLast("encoder",?new?HttpResponseEncoder())
????????????????????????????????????.addLast("aggregator",?new?HttpObjectAggregator(512?*?1024))
????????????????????????????????????.addLast("handler",?new?HttpServerHandler());
????????????????????????}
????????????????????});
????????????Channel?ch?=?b.bind(PORT).sync().channel();
????????????log.info("Netty?Http?Server?started?on?port?{}.",?PORT);
????????????ch.closeFuture().sync();
????????}?catch?(InterruptedException?e)?{
????????????log.error("occur?exception?when?start?server:",?e);
????????}?finally?{
????????????log.error("shutdown?bossGroup?and?workerGroup");
????????????bossGroup.shutdownGracefully();
????????????workerGroup.shutdownGracefully();
????????}
????}
}
簡單解析一下服務(wù)端的創(chuàng)建過程具體是怎樣的!
1.創(chuàng)建了兩個(gè) NioEventLoopGroup 對象實(shí)例:bossGroup 和 workerGroup。
bossGroup: 用于處理客戶端的 TCP 連接請求。workerGroup:負(fù)責(zé)每一條連接的具體讀寫數(shù)據(jù)的處理邏輯,真正負(fù)責(zé) I/O 讀寫操作,交由對應(yīng)的 Handler 處理。
舉個(gè)例子:我們把公司的老板當(dāng)做 bossGroup,員工當(dāng)做 workerGroup,bossGroup 在外面接完活之后,扔給 workerGroup 去處理。一般情況下我們會(huì)指定 bossGroup 的 線程數(shù)為 1(并發(fā)連接量不大的時(shí)候) ,workGroup 的線程數(shù)量為 CPU 核心數(shù) *2 。另外,根據(jù)源碼來看,使用 NioEventLoopGroup 類的無參構(gòu)造函數(shù)設(shè)置線程數(shù)量的默認(rèn)值就是 CPU 核心數(shù) *2 。
2.創(chuàng)建一個(gè)服務(wù)端啟動(dòng)引導(dǎo)/輔助類:ServerBootstrap,這個(gè)類將引導(dǎo)我們進(jìn)行服務(wù)端的啟動(dòng)工作。
3.通過 .group() 方法給引導(dǎo)類 ServerBootstrap 配置兩大線程組,確定了線程模型。
4.通過channel()方法給引導(dǎo)類 ServerBootstrap指定了 IO 模型為NIO
NioServerSocketChannel:指定服務(wù)端的 IO 模型為 NIO,與 BIO 編程模型中的ServerSocket對應(yīng)NioSocketChannel: 指定客戶端的 IO 模型為 NIO, 與 BIO 編程模型中的Socket對應(yīng)
5.通過 .childHandler()給引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer ,然后指定了服務(wù)端消息的業(yè)務(wù)處理邏輯也就是自定義的ChannelHandler 對象
6.調(diào)用 ServerBootstrap 類的 bind()方法綁定端口 。
//bind()是異步的,但是,你可以通過 sync()方法將其變?yōu)橥健?/span>
ChannelFuture?f?=?b.bind(port).sync();
自定義服務(wù)端 ChannelHandler 處理 HTTP 請求
我們繼承SimpleChannelInboundHandler ,并重寫下面 3 個(gè)方法:
channelRead():服務(wù)端接收并處理客戶端發(fā)送的 HTTP 請求調(diào)用的方法。exceptionCaught():處理客戶端發(fā)送的 HTTP 請求發(fā)生異常的時(shí)候被調(diào)用。channelReadComplete(): 服務(wù)端消費(fèi)完客戶端發(fā)送的 HTTP 請求之后調(diào)用的方法。
另外,客戶端 HTTP 請求參數(shù)類型為 FullHttpRequest。我們可以把 FullHttpRequest對象看作是 HTTP 請求報(bào)文的 Java 對象的表現(xiàn)形式。
@Slf4j
public?class?HttpServerHandler?extends?SimpleChannelInboundHandler<FullHttpRequest>?{
????private?static?final?String?FAVICON_ICO?=?"/favicon.ico";
????private?static?final?AsciiString?CONNECTION?=?AsciiString.cached("Connection");
????private?static?final?AsciiString?KEEP_ALIVE?=?AsciiString.cached("keep-alive");
????private?static?final?AsciiString?CONTENT_TYPE?=?AsciiString.cached("Content-Type");
????private?static?final?AsciiString?CONTENT_LENGTH?=?AsciiString.cached("Content-Length");
????@Override
????protected?void?channelRead0(ChannelHandlerContext?ctx,?FullHttpRequest?fullHttpRequest)?{
????????log.info("Handle?http?request:{}",?fullHttpRequest);
????????String?uri?=?fullHttpRequest.uri();
????????if?(uri.equals(FAVICON_ICO))?{
????????????return;
????????}
????????RequestHandler?requestHandler?=?RequestHandlerFactory.create(fullHttpRequest.method());
????????Object?result;
????????FullHttpResponse?response;
????????try?{
????????????result?=?requestHandler.handle(fullHttpRequest);
????????????String?responseHtml?=?""?+?result?+?"";
????????????byte[]?responseBytes?=?responseHtml.getBytes(StandardCharsets.UTF_8);
????????????response?=?new?DefaultFullHttpResponse(HTTP_1_1,?OK,?Unpooled.wrappedBuffer(responseBytes));
????????????response.headers().set(CONTENT_TYPE,?"text/html;?charset=utf-8");
????????????response.headers().setInt(CONTENT_LENGTH,?response.content().readableBytes());
????????}?catch?(IllegalArgumentException?e)?{
????????????e.printStackTrace();
????????????String?responseHtml?=?""?+?e.toString()?+?"";
????????????byte[]?responseBytes?=?responseHtml.getBytes(StandardCharsets.UTF_8);
????????????response?=?new?DefaultFullHttpResponse(HTTP_1_1,?INTERNAL_SERVER_ERROR,?Unpooled.wrappedBuffer(responseBytes));
????????????response.headers().set(CONTENT_TYPE,?"text/html;?charset=utf-8");
????????}
????????boolean?keepAlive?=?HttpUtil.isKeepAlive(fullHttpRequest);
????????if?(!keepAlive)?{
????????????ctx.write(response).addListener(ChannelFutureListener.CLOSE);
????????}?else?{
????????????response.headers().set(CONNECTION,?KEEP_ALIVE);
????????????ctx.write(response);
????????}
????}
????@Override
????public?void?exceptionCaught(ChannelHandlerContext?ctx,?Throwable?cause)?{
????????cause.printStackTrace();
????????ctx.close();
????}
????@Override
????public?void?channelReadComplete(ChannelHandlerContext?ctx)?{
????????ctx.flush();
????}
}
我們返回給客戶端的消息體是 FullHttpResponse 對象。通過 FullHttpResponse 對象,我們可以設(shè)置 HTTP 響應(yīng)報(bào)文的 HTTP 協(xié)議版本、響應(yīng)的具體內(nèi)容 等內(nèi)容。
我們可以把 FullHttpResponse 對象看作是 HTTP 響應(yīng)報(bào)文的 Java 對象的表現(xiàn)形式。
FullHttpResponse?response;
String?responseHtml?=?""?+?result?+?"";
byte[]?responseBytes?=?responseHtml.getBytes(StandardCharsets.UTF_8);
//?初始化?FullHttpResponse?,并設(shè)置?HTTP?協(xié)議?、響應(yīng)狀態(tài)碼、響應(yīng)的具體內(nèi)容
response?=?new?DefaultFullHttpResponse(HTTP_1_1,?OK,?Unpooled.wrappedBuffer(responseBytes));
我們通過 FullHttpResponse的headers()方法獲取到 HttpHeaders,這里的 HttpHeaders 對應(yīng)于 HTTP 響應(yīng)報(bào)文的頭部。通過 HttpHeaders對象,我們就可以對 HTTP 響應(yīng)報(bào)文的頭部的內(nèi)容比如 Content-Typ 進(jìn)行設(shè)置。
response.headers().set(CONTENT_TYPE,?"text/html;?charset=utf-8");
response.headers().setInt(CONTENT_LENGTH,?response.content().readableBytes());
本案例中,為了掩飾我們設(shè)置的 Content-Type 為 text/html ,也就是返回 html 格式的數(shù)據(jù)給客戶端。
常見的 Content-Type
| Content-Type | 解釋 |
|---|---|
| text/html | html 格式 |
| text/plain | 純文本格式 |
| text/css | css 格式 |
| text/javascript | js 格式 |
| application/json | json 格式(前后端分離項(xiàng)目常用) |
| image/gif | gif 圖片格式 |
| image/jpeg | jpg 圖片格式 |
| image/png | png 圖片格式 |
請求的具體處理邏輯實(shí)現(xiàn)
因?yàn)檫@里要分別處理 POST 請求和 GET 請求。因此我們需要首先定義一個(gè)處理 HTTP Request 的接口。
public?interface?RequestHandler?{
????Object?handle(FullHttpRequest?fullHttpRequest);
}
HTTP Method 不只是有 GET 和 POST,其他常見的還有 PUT、DELETE、PATCH。只是本案例中實(shí)現(xiàn)的 HTTP Server 只考慮了 GET 和 POST。
GET :請求從服務(wù)器獲取特定資源。舉個(gè)例子: GET /classes(獲取所有班級(jí))POST :在服務(wù)器上創(chuàng)建一個(gè)新的資源。舉個(gè)例子: POST /classes(創(chuàng)建班級(jí))PUT :更新服務(wù)器上的資源(客戶端提供更新后的整個(gè)資源)。舉個(gè)例子: PUT /classes/12(更新編號(hào)為 12 的班級(jí))DELETE :從服務(wù)器刪除特定的資源。舉個(gè)例子: DELETE /classes/12(刪除編號(hào)為 12 的班級(jí))PATCH :更新服務(wù)器上的資源(客戶端提供更改的屬性,可以看做作是部分更新),使用的比較少,這里就不舉例子了。
GET 請求的處理
@Slf4j
public?class?GetRequestHandler?implements?RequestHandler?{
????@Override
????public?Object?handle(FullHttpRequest?fullHttpRequest)?{
????????String?requestUri?=?fullHttpRequest.uri();
????????Map?queryParameterMappings?=?this.getQueryParams(requestUri);
????????return?queryParameterMappings.toString();
????}
????private?Map?getQueryParams(String?uri)? {
????????QueryStringDecoder?queryDecoder?=?new?QueryStringDecoder(uri,?Charsets.toCharset(CharEncoding.UTF_8));
????????Map>?parameters?=?queryDecoder.parameters();
????????Map?queryParams?=?new?HashMap<>();
????????for?(Map.Entry>?attr?:?parameters.entrySet())?{
????????????for?(String?attrVal?:?attr.getValue())?{
????????????????queryParams.put(attr.getKey(),?attrVal);
????????????}
????????}
????????return?queryParams;
????}
}
我這里只是簡單得把 URI 的查詢參數(shù)的對應(yīng)關(guān)系直接返回給客戶端了。
實(shí)際上,獲得了 URI 的查詢參數(shù)的對應(yīng)關(guān)系,再結(jié)合反射和注解相關(guān)的知識(shí),我們很容易實(shí)現(xiàn)類似于 Spring Boot 的 @RequestParam 注解了。
建議想要學(xué)習(xí)的小伙伴,可以自己獨(dú)立實(shí)現(xiàn)一下。不知道如何實(shí)現(xiàn)的話,你可以參考我開源的輕量級(jí) HTTP 框架jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一個(gè)輕量級(jí)的 HTTP 框架)。
POST 請求的處理
@Slf4j
public?class?PostRequestHandler?implements?RequestHandler?{
????@Override
????public?Object?handle(FullHttpRequest?fullHttpRequest)?{
????????String?requestUri?=?fullHttpRequest.uri();
????????log.info("request?uri?:[{}]",?requestUri);
????????String?contentType?=?this.getContentType(fullHttpRequest.headers());
????????if?(contentType.equals("application/json"))?{
????????????return?fullHttpRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
????????}?else?{
????????????throw?new?IllegalArgumentException("only?receive?application/json?type?data");
????????}
????}
????private?String?getContentType(HttpHeaders?headers)?{
????????String?typeStr?=?headers.get("Content-Type");
????????String[]?list?=?typeStr.split(";");
????????return?list[0];
????}
}
對于 POST 請求的處理,我們這里只接受處理 Content-Type 為 application/json 的數(shù)據(jù),如果 POST 請求傳過來的不是 application/json 類型的數(shù)據(jù),我們就直接拋出異常。
實(shí)際上,我們獲得了客戶端傳來的 json 格式的數(shù)據(jù)之后,再結(jié)合反射和注解相關(guān)的知識(shí),我們很容易實(shí)現(xiàn)類似于 Spring Boot 的 @RequestBody 注解了。
建議想要學(xué)習(xí)的小伙伴,可以自己獨(dú)立實(shí)現(xiàn)一下。不知道如何實(shí)現(xiàn)的話,你可以參考我開源的輕量級(jí) HTTP 框架jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一個(gè)輕量級(jí)的 HTTP 框架)。
請求處理工廠類
public?class?RequestHandlerFactory?{
????public?static?final?Map?REQUEST_HANDLERS?=?new?HashMap<>();
????static?{
????????REQUEST_HANDLERS.put(HttpMethod.GET,?new?GetRequestHandler());
????????REQUEST_HANDLERS.put(HttpMethod.POST,?new?PostRequestHandler());
????}
????public?static?RequestHandler?create(HttpMethod?httpMethod)?{
????????return?REQUEST_HANDLERS.get(httpMethod);
????}
}
我這里用到了工廠模式,當(dāng)我們額外處理新的 HTTP Method 方法的時(shí)候,直接實(shí)現(xiàn) RequestHandler 接口,然后將實(shí)現(xiàn)類添加到 RequestHandlerFactory 即可。
啟動(dòng)類
public?class?HttpServerApplication?{
????public?static?void?main(String[]?args)?{
????????HttpServer?httpServer?=?new?HttpServer();
????????httpServer.start();
????}
}
效果
運(yùn)行 HttpServerApplication 的main()方法,控制臺(tái)打印出:
[nioEventLoopGroup-2-1]?INFO?io.netty.handler.logging.LoggingHandler?-?[id:?0x9bb1012a]?REGISTERED
[nioEventLoopGroup-2-1]?INFO?io.netty.handler.logging.LoggingHandler?-?[id:?0x9bb1012a]?BIND:?0.0.0.0/0.0.0.0:8080
[nioEventLoopGroup-2-1]?INFO?io.netty.handler.logging.LoggingHandler?-?[id:?0x9bb1012a,?L:/0:0:0:0:0:0:0:0:8080]?ACTIVE
[main]?INFO?server.HttpServer?-?Netty?Http?Server?started?on?port?8080.
GET 請求

POST 請求

閑聊
抓住國慶的尾巴,和大學(xué)同學(xué)出去嗨了一波,大半年沒玩英雄聯(lián)盟了。之前大學(xué)的時(shí)候還偶爾和同學(xué)去附近的網(wǎng)吧通宵,像是過去了好久一樣



支持原創(chuàng)!文章有幫助可以點(diǎn)個(gè)「在看」或「分享」,我都會(huì)開心很久!
我是Guide哥,Java后端開發(fā),會(huì)一點(diǎn)前端知識(shí),喜歡烹飪,自由的少年。一個(gè)三觀比主角還正的技術(shù)人。我們下期再見!
