『互聯(lián)網(wǎng)架構(gòu)』軟件架構(gòu)-netty之http協(xié)議應(yīng)用實踐(58)

原創(chuàng)文章,歡迎轉(zhuǎn)載。轉(zhuǎn)載請注明:轉(zhuǎn)載自IT人故事會,謝謝狭吼!
原文鏈接地址:『互聯(lián)網(wǎng)架構(gòu)』軟件架構(gòu)-netty之http協(xié)議應(yīng)用實踐(58)

做了多年web開發(fā),http真是熟悉的陌生人(經(jīng)常在用殖妇,但是從頭到尾理清楚真的是很有難度的)刁笙,其實http概述和netty中的http應(yīng)用真有必要說下,一起聊聊netty實現(xiàn)tomcat的功能谦趣,請求服務(wù)的功能疲吸。
源碼:https://github.com/limingios/netFuture/tree/master/源碼/『互聯(lián)網(wǎng)架構(gòu)』軟件架構(gòu)-io與nio線程模型reactor模型(上)(53)/nio

(一)Http協(xié)議概述

  • 1.什么是Http協(xié)議

HTTP是一個屬于【應(yīng)用層】的面向?qū)ο蟮膮f(xié)議,由于其簡捷前鹅、快速的方式摘悴,適用于分布式超媒體信息系統(tǒng)。

  • 2.HTTP協(xié)議的主要特點

支持客戶/服務(wù)器模式舰绘。

簡單快速
客戶向服務(wù)器請求服務(wù)時蹂喻,只需傳送請求方法和路徑。請求方法常用的有GET捂寿、HEAD口四、POST。每種方法規(guī)定了客戶與
服務(wù)器聯(lián)系的類型不同秦陋。由于HTTP協(xié)議簡單蔓彩,使得HTTP服務(wù)器的程序規(guī)模小,因而通信速度很快驳概。

靈活
HTTP允許傳輸任意類型的數(shù)據(jù)對象赤嚼。正在傳輸?shù)念愋陀蒀ontent-Type加以標(biāo)記。

無連接
無連接的含義是限制每次連接只處理一個請求顺又。服務(wù)器處理完客戶的請求探膊,并收到客戶的應(yīng)答后,即斷開連接待榔。采用這種方式可以節(jié)省傳輸時間。

無狀態(tài)
HTTP協(xié)議是無狀態(tài)協(xié)議流济。無狀態(tài)是指協(xié)議對于事務(wù)處理沒有記憶能力锐锣。缺少狀態(tài)意味著如果后續(xù)處理需要前面的信息,則它必須重傳绳瘟,這樣可能導(dǎo)致每次連接傳送的數(shù)據(jù)量增大雕憔。另一方面,在服務(wù)器不需要先前信息時它的應(yīng)答就較快糖声。從HTTP協(xié)議來講是無狀態(tài)的斤彼,其實在應(yīng)用的時候分瘦,很多情況通過回話的方式還是有狀態(tài)的。

(二)Http協(xié)議交互過程

協(xié)議交互本質(zhì)是指協(xié)議兩端(客戶端琉苇、服務(wù)端)嘲玫,互聯(lián)網(wǎng)上沒有協(xié)議是混亂的,正如如果現(xiàn)實中沒有法律也會打亂一樣并扇。

1.傳輸數(shù)據(jù)

傳輸數(shù)據(jù)一般基于TCP/IP 實現(xiàn)去团,體現(xiàn)到開發(fā)語言上就是我們所熟悉的Socket 編程。

2.交換數(shù)據(jù)

交換數(shù)據(jù)本質(zhì)是指穷蛹,兩端(客戶端土陪、服務(wù)端)能各自識別對方所發(fā)送的數(shù)據(jù)。那么這就需要制定一套【報文編碼】格式肴熏,雙方以該格式編碼數(shù)據(jù)發(fā)送給對方鬼雀。

Http 對應(yīng)的Request 與Response報文

注: 我們可以通過抓包工具(fiddler)可以直接看到該報文格式。

報文約定好以后兩端都需要對其進(jìn)行解碼和編碼操作

3.Http協(xié)議內(nèi)容組成

請求方法

方法 描述
GET 請求指定的頁面信息蛙吏,并返回實體主體源哩。
HEAD 類似于get請求,只不過返回的響應(yīng)中沒有具體的內(nèi)容出刷,用于獲取報頭
POST 向指定資源提交數(shù)據(jù)進(jìn)行處理請求(例如提交表單或者上傳文件)璧疗。數(shù)據(jù)被包含在請求體中。POST請求可能會導(dǎo)致新的資源的建立和/或已有資源的修改馁龟。
PUT 從客戶端向服務(wù)器傳送的數(shù)據(jù)取代指定的文檔的內(nèi)容崩侠。
DELETE 請求服務(wù)器刪除指定的頁面。
CONNECT HTTP/1.1協(xié)議中預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器坷檩。
OPTIONS 允許客戶端查看服務(wù)器的性能却音。
TRACE 回顯服務(wù)器收到的請求,主要用于測試或診斷矢炼。

部分請求頭

請求頭 說明
Host 接受請求的服務(wù)器地址系瓢,可以是IP:端口號,也可以是域名
User-Agent 發(fā)送請求的應(yīng)用程序名稱
Connection 指定與連接相關(guān)的屬性句灌,如Connection:Keep-Alive
Accept-Charset 通知服務(wù)端可以發(fā)送的編碼格式
Accept-Encoding 通知服務(wù)端可以發(fā)送的數(shù)據(jù)壓縮格式
Accept-Language 通知服務(wù)端可以發(fā)送的語言

部分響應(yīng)頭

響應(yīng)頭 說明
Server 服務(wù)器應(yīng)用程序軟件的名稱和版本
Content-Type 響應(yīng)正文的類型(是圖片還是二進(jìn)制字符串)
Content-Length 實體報頭域用于指明實體正文的長度夷陋,以字節(jié)方式存儲的十進(jìn)制數(shù)字來表示響應(yīng)正文長度
Content-Charset 響應(yīng)正文使用的編碼
Content-Encoding 響應(yīng)正文使用的數(shù)據(jù)壓縮格式
Content-Language 響應(yīng)正文使用的語言

部分響應(yīng)狀態(tài)

狀態(tài)碼 說明
200 響應(yīng)成功
302 跳轉(zhuǎn),跳轉(zhuǎn)地址通過響應(yīng)頭中的Location屬性指定(JSP中Forward和Redirect之間的區(qū)別)
400 客戶端請求有語法錯誤胰锌,不能被服務(wù)器識別
403 服務(wù)器接收到請求骗绕,但是拒絕提供服務(wù)(認(rèn)證失敗)
404 請求資源不存在
500 服務(wù)器內(nèi)部錯誤

(二)基于Netty 實現(xiàn)Http協(xié)議過程分析

Http協(xié)議分為三部分:

1.遠(yuǎn)程數(shù)據(jù)傳輸
2.報文編解碼
3.業(yè)務(wù)處理

但如果是開發(fā)基于Http普通應(yīng)用资昧,完全沒有必要重復(fù)造輪子酬土,,我們只需實現(xiàn)業(yè)務(wù)即可「翊現(xiàn)比較成熟的中間件有:Tomcat撤缴、Jetty刹枉、Jboos。這些中間有個缺點是較重屈呕,如果需要輕量實現(xiàn)可采用:netty 或JDK自還http 實現(xiàn)JDK Http源碼參見:com.sun.net.httpserver.HttpServer

  • netty 實現(xiàn)http

源碼:nio/http中微宝。

1.初始ServerBootstrap
2.通過ChannelInitializer 初始 pipeline
3.基于SimpleChannelInboundHandler HttpServer處理類

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

import java.net.ContentHandler;
import java.util.concurrent.ThreadFactory;

/**
 * Created by idig8.com
 */
public class HttpSimpleServer {
    //open 啟動服務(wù)
    public void openServer() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.channel(NioServerSocketChannel.class);
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup work = new NioEventLoopGroup(8);
        bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                ch.pipeline().addLast("http-server", new HttpServerHandler());
            }
        });
        bootstrap.group(boss, work);
        try {
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("服務(wù)啟動:8080");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    private static class HttpServerHandler extends SimpleChannelInboundHandler {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
            String html = "<!DOCTYPE html>\n" +
                    "<html lang=\"en\">\n" +
                    "<head>\n" +
                    "    <meta charset=\"UTF-8\">\n" +
                    "    <title>hello idig8.com</title>\n" +
                    "</head>\n" +
                    "<body>\n" +
                    "hello idig8.com\n" +
                    "</body>\n" +
                    "</html>";
            response.content().writeBytes(html.getBytes("UTF-8"));
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

    public static void main(String[] args) {
        HttpSimpleServer simpleServer = new HttpSimpleServer();
        simpleServer.openServer();
    }


}

實現(xiàn)過程分析

  1. 建立連接讀取消息流
  2. 解碼Request
  3. 業(yè)務(wù)處理
  4. 編碼Response
  5. 返回消息關(guān)閉連接

Channel 與 ChannelPipeline

1.Channel:
a. ServerSocketChannel
b. SocketChannel

2.pipeline:一個pipeline 當(dāng)中包含了多個ChandlerHandler,而且是有順序的

3.ChandlerHandler
a. HttpRequestDecode:解碼請求
b. HttpResponseEncode :編碼返回結(jié)果
在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應(yīng), 它們的組成關(guān)系如下:

一個 Channel 包含了一個 ChannelPipeline, 而 ChannelPipeline 中又維護(hù)了一個由 ChannelHandlerContext 組成的雙向鏈表. 這個鏈表的頭是 HeadContext, 鏈表的尾是 TailContext,并且每個 ChannelHandlerContext 中又關(guān)聯(lián)著一個 ChannelHandler

bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
ch.pipeline().addLast("http-server", new HttpServerHandler());
}
});

HttpRequest 在netty 當(dāng)中的表示結(jié)構(gòu)

HttpResponse在netty 當(dāng)中的結(jié)構(gòu)

PS:說了下http協(xié)議和如何通過netty完成http服務(wù)。下次一起說說通過netty完成websocket凉袱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芥吟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子专甩,更是在濱河造成了極大的恐慌钟鸵,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涤躲,死亡現(xiàn)場離奇詭異棺耍,居然都是意外死亡,警方通過查閱死者的電腦和手機种樱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蒙袍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嫩挤,你說我怎么就攤上這事害幅。” “怎么了岂昭?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵以现,是天一觀的道長。 經(jīng)常有香客問我约啊,道長邑遏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任恰矩,我火速辦了婚禮记盒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘外傅。我一直安慰自己纪吮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布萎胰。 她就那樣靜靜地躺著碾盟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奥洼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天晚胡,我揣著相機與錄音灵奖,去河邊找鬼嚼沿。 笑死,一個胖子當(dāng)著我的面吹牛瓷患,可吹牛的內(nèi)容都是我干的骡尽。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼擅编,長吁一口氣:“原來是場噩夢啊……” “哼攀细!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起爱态,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤谭贪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锦担,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俭识,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年洞渔,在試婚紗的時候發(fā)現(xiàn)自己被綠了套媚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡磁椒,死狀恐怖堤瘤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浆熔,我是刑警寧澤本辐,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站蘸拔,受9級特大地震影響师郑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜调窍,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一宝冕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧邓萨,春花似錦地梨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至歉甚,卻和暖如春万细,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纸泄。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工赖钞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腰素,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓雪营,卻偏偏與公主長得像弓千,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子献起,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容