原創(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)過程分析
- 建立連接讀取消息流
- 解碼Request
- 業(yè)務(wù)處理
- 編碼Response
- 返回消息關(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凉袱。