<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手寫“SpringBoot”:幾十行代碼基于Netty搭建一個(gè) HTTP Server

          共 14848字,需瀏覽 30分鐘

           ·

          2020-10-11 15:29

          本文已經(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 吧!

          1. Netty 是一個(gè)基于 NIO 的 client-server(客戶端服務(wù)器)框架,使用它可以快速簡單地開發(fā)網(wǎng)絡(luò)應(yīng)用程序。
          2. Netty 極大地簡化并優(yōu)化了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程,并且性能以及安全性等很多方面都要更好。
          3. 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ò)通信 。

          1. 實(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ù)吧!
          2. 實(shí)現(xiàn)一個(gè)自己的 HTTP 服務(wù)器 :通過 Netty ,我們可以很方便地使用少量代碼實(shí)現(xiàn)一個(gè)簡單的 HTTP 服務(wù)器。Netty 自帶了編解碼器和消息聚合器,為我們開發(fā)節(jié)省了很多事!
          3. 實(shí)現(xiàn)一個(gè)即時(shí)通訊系統(tǒng) :使用 Netty 我們可以實(shí)現(xiàn)一個(gè)可以聊天類似微信的即時(shí)通訊系統(tǒng),這方面的開源項(xiàng)目還蠻多的,可以自行去 Github 找一找。
          4. 實(shí)現(xiàn)消息推送系統(tǒng) :市面上有很多消息推送系統(tǒng)都是基于 Netty 來做的。
          5. ......

          那些開源項(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è)過程如下圖所示。

          HTTP請求過程

          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 請求報(bào)文

          HTTP 響應(yīng)報(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)所提供的對象比如 HttpRequestHttpContent之間互相轉(zhuǎn)換。

          Netty 自帶了 4 個(gè)常用的編解碼器:

          1. HttpRequestEncoder (HTTP 請求編碼器):將 HttpRequestHttpContent 編碼為 ByteBuf 。
          2. HttpRequestDecoder (HTTP 請求解碼器):將 ByteBuf 解碼為 HttpRequestHttpContent
          3. HttpResponsetEncoder (HTTP 響應(yīng)編碼器):將 HttpResponseHttpContent 編碼為 ByteBuf
          4. HttpResponseDecoder(HTTP 響應(yīng)解碼器):將 ByteBuf 解碼為 HttpResponstHttpContent

          網(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。因此我們只需要 HttpRequestDecoderHttpResponseEncoder 即可。

          我手繪了一張圖,這樣看著應(yīng)該更容易理解了。

          Netty 對 HTTP 消息的抽象

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

          1. HttpObject : 整個(gè) HTTP 消息體系結(jié)構(gòu)的最上層接口。HttpObject 接口下又有 HttpMessageHttpContent兩大核心接口。
          2. HttpMessage: 定義 HTTP 消息,為HttpRequestHttpResponse提供通用屬性
          3. HttpRequest : HttpRequest對應(yīng) HTTP request。通過 HttpRequest 我們可以訪問查詢參數(shù)(Query Parameters)和 Cookie。和 Servlet API 不同的是,查詢參數(shù)是通過QueryStringEncoderQueryStringDecoder來構(gòu)造和解析查詢查詢參數(shù)。
          4. HttpResponseHttpResponse 對應(yīng) HTTP response。和HttpMessage相比,HttpResponse 增加了 status(相應(yīng)狀態(tài)碼) 屬性及其對應(yīng)的方法。
          5. 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ù)。
          6. LastHttpContent : 標(biāo)識(shí) HTTP 請求結(jié)束,同時(shí)包含 HttpHeaders 對象。
          7. FullHttpRequestFullHttpResponseHttpMessageHttpContent 聚合后得到的對象。

          HTTP 消息聚合器

          HttpObjectAggregator 是 Netty 提供的 HTTP 消息聚合器,通過它可以把 HttpMessageHttpContent 聚合成一個(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í)例:bossGroupworkerGroup。

          • 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è)方法:

          1. channelRead() :服務(wù)端接收并處理客戶端發(fā)送的 HTTP 請求調(diào)用的方法。
          2. exceptionCaught() :處理客戶端發(fā)送的 HTTP 請求發(fā)生異常的時(shí)候被調(diào)用。
          3. 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));

          我們通過 FullHttpResponseheaders()方法獲取到 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/htmlhtml 格式
          text/plain純文本格式
          text/csscss 格式
          text/javascriptjs 格式
          application/jsonjson 格式(前后端分離項(xiàng)目常用)
          image/gifgif 圖片格式
          image/jpegjpg 圖片格式
          image/pngpng 圖片格式

          請求的具體處理邏輯實(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)行 HttpServerApplicationmain()方法,控制臺(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ù)人。我們下期再見!

          瀏覽 92
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲中文字幕视频在线观看 | 日逼欧美 | 影音先锋AV麻豆啪啪资源网 | 鸥美无码| www深夜成人 a√在线 |