一、Http請(qǐng)求過(guò)程總覽
瀏覽器請(qǐng)求
http://localhost/test/index.jsp
用戶(hù)點(diǎn)擊網(wǎng)頁(yè)內(nèi)容,請(qǐng)求被發(fā)送到本機(jī)端口8080,被在那里監(jiān)聽(tīng)的Coyote HTTP/1.1 Connector獲得。
Connector將Request包裝成ServletRequest給它所在的Service的Engine來(lái)處理宜肉,并等待Engine的回應(yīng)。
Engine獲得請(qǐng)求localhost/test/index.jsp,匹配所有的虛擬主機(jī)Host腌且。
Engine匹配到名為localhost的Host(即使匹配不到也把請(qǐng)求交給該Host處理,因?yàn)樵揌ost被定義為該Engine的默認(rèn)主機(jī))榛瓮,名為localhost的Host獲得請(qǐng)求/test/index.jsp铺董,匹配它所擁有的所有的Context。Host匹配到路徑為/test的Context(如果匹配不到就把該請(qǐng)求交給路徑名為""的Context去處理)禀晓。
path="/test"的Context獲得請(qǐng)求/index.jsp精续,在它的mapping table中尋找出對(duì)應(yīng)的Servlet。Context匹配到URL PATTERN為*.jsp的Servlet,對(duì)應(yīng)于JspServlet類(lèi)粹懒。
構(gòu)造HttpServletRequest對(duì)象和HttpServletResponse對(duì)象重付,作為參數(shù)調(diào)用JspServlet的doGet()或doPost().執(zhí)行業(yè)務(wù)邏輯、數(shù)據(jù)存儲(chǔ)等程序凫乖。
Context把執(zhí)行完之后的HttpServletResponse對(duì)象返回給Host确垫。
Host把HttpServletResponse對(duì)象返回給Engine弓颈。
Engine把HttpServletResponse對(duì)象返回Connector,Connector把ServletResponse對(duì)象封裝成Response删掀。
Connector把Response返回給客戶(hù)Browser翔冀。
二、具體組件及源碼分析
2.1 外部網(wǎng)絡(luò)請(qǐng)求
2.1.1 Connector
用戶(hù)在瀏覽器中輸入一個(gè)URL地址之后瀏覽器將發(fā)起一個(gè)Http的請(qǐng)求披泪,通過(guò)網(wǎng)絡(luò)傳輸數(shù)據(jù)纤子,請(qǐng)求到我們的Tomcat服務(wù)器中,Tomcat使用Connector接收Socket請(qǐng)求數(shù)據(jù)并通過(guò)Coyote鏈接器封裝底層網(wǎng)絡(luò)通信款票,為Catalina容器提供了統(tǒng)一的接口控硼。
在Coyote中,Tomcat支持一下3種協(xié)議:
- HTTP/1.1協(xié)議:目前最常用的訪(fǎng)問(wèn)協(xié)議
- AJP協(xié)議:Apache提供的一種協(xié)議徽职,用于和Apache HTTP Server集成象颖。
- HTTP/2.0協(xié)議:下一代HTTP協(xié)議,自Tomcat8.5版本開(kāi)始支持姆钉。
針對(duì)HTTP和AJP協(xié)議说订,Coyote又按照I/O方式分別提供了不同的方案。
- BIO:同步阻塞I/O模式潮瓶,數(shù)據(jù)的讀取寫(xiě)入必須阻塞在一個(gè)線(xiàn)程內(nèi)等待其完成陶冷,Tomcat早期網(wǎng)絡(luò)請(qǐng)求的默認(rèn)實(shí)現(xiàn)方式(自8.5版本開(kāi)始,已經(jīng)移除)
- NIO:同步非阻塞的I/O模型(當(dāng)前Tomcat的默認(rèn)實(shí)現(xiàn))
- NIO2:異步非阻塞I/O模型
- APR:Apache可移植運(yùn)行庫(kù)毯辅。
2.1.2 NioEndpoint
我們以NioEndpoint為例埂伦,來(lái)看一下Tomcat是如何實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求收發(fā)的。
/**
* Defaults to using HTTP/1.1 NIO implementation.
* 默認(rèn)使用NIO實(shí)現(xiàn)的http/1.1協(xié)議
*/
public Connector() {
this("HTTP/1.1");
}
public Connector(String protocol) {
boolean apr = AprStatus.getUseAprConnector() && AprStatus.isInstanceCreated()
&& AprLifecycleListener.isAprAvailable();
ProtocolHandler p = null;
try {
//根據(jù)指定的協(xié)議思恐,創(chuàng)建不同的處理器
p = ProtocolHandler.create(protocol, apr);
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
}
//省略部分代碼...
}
我們發(fā)現(xiàn)Tomcat默認(rèn)使用HTTP/1.1協(xié)議創(chuàng)建Http11NioProtocol沾谜,它默認(rèn)使用NioEndpoint
public Http11NioProtocol() {
super(new NioEndpoint());
}
當(dāng)Connector執(zhí)行init()的同時(shí)運(yùn)行了protocolHandler.init()最終運(yùn)行了NioEndpoint.init(),最終就是啟動(dòng)了一個(gè)ServerSocket并綁定監(jiān)聽(tīng)端口胀莹。
protected void initServerSocket() throws Exception {
//省略部分代碼
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.bind(addr, getAcceptCount());
}
當(dāng)Connector執(zhí)行start()方法時(shí)基跑,最終執(zhí)行了:NioEndpoint.startInternal()方法。該方法內(nèi)部啟動(dòng)了一個(gè)Poller線(xiàn)程描焰,一個(gè)Acceptor線(xiàn)程媳否。其中,Acceptor用于監(jiān)聽(tīng)客戶(hù)端連接荆秦,并將Socket連接放入待處理事件緩存池篱竭。Poller循環(huán)從待處理的事件緩存隊(duì)列中拿到請(qǐng)求交給線(xiàn)程池處理。Poller將Socket請(qǐng)求包裝成為:SocketProcessor對(duì)象步绸,執(zhí)行processor.process()方法掺逼,最終調(diào)用:getAdapter().service(request, response);這里的getAdapter即CoyoteAdapter,最終找到容器中管道方法的第一個(gè)閥門(mén)方法瓤介,發(fā)起調(diào)用坪圾,此時(shí)晓折,網(wǎng)絡(luò)請(qǐng)求正式進(jìn)入我們?nèi)萜髦小?/p>
// Calling the container 調(diào)用容器
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
TIPS 如果想要詳細(xì)了解Tomcat接收請(qǐng)求的流程圖可以查看官網(wǎng)提供的資料
http://tomcat.apache.org/tomcat-9.0-doc/architecture/requestProcess/request-process.png
2.2 容器內(nèi)部請(qǐng)求路徑匹配
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
//將coyote鏈接器中的Request,Response轉(zhuǎn)換為我們常用的HttpServletRequest,HttpServletResponse
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
//省略部分代碼...
// 匹配請(qǐng)求路徑兽泄,將當(dāng)前request請(qǐng)求到匹配到的servlet上
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container 調(diào)用容器
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
// 省略部分代碼...
}
在Tomcat啟動(dòng)的時(shí)候 Service持有了一個(gè)Mapper對(duì)象漓概,它借助MapperListener在應(yīng)用啟動(dòng)的過(guò)程中,將Engine病梢,Host胃珍,Context,Wrapper路徑都搜集了起來(lái)蜓陌。當(dāng)一個(gè)請(qǐng)求過(guò)來(lái)的時(shí)候它會(huì)去當(dāng)前 Service中去匹配,最終將請(qǐng)求執(zhí)行到我們StandardWrapperValve觅彰。此時(shí)會(huì)創(chuàng)建ApplicationFilterChain過(guò)濾器鏈;通過(guò)調(diào)用過(guò)濾器內(nèi)部方法internalDoFilter;最終調(diào)用:javax.servlet.http.HttpServlet.service()方法,請(qǐng)求到應(yīng)用中钮热!
三填抬、本文小結(jié)
我們通過(guò)一個(gè)Http請(qǐng)求路徑分析了Tomcat是使用Socket接收網(wǎng)絡(luò)請(qǐng)求,使用Coyote轉(zhuǎn)換我們的網(wǎng)絡(luò)協(xié)議參數(shù)隧期,包裝為ServletRequest和ServletResponse飒责,使用匹配請(qǐng)求路徑的方式最終找到我們應(yīng)用中的Servlet地址。
至此仆潮,可以說(shuō)我們對(duì)Tomcat的主要功能基本上已經(jīng)了解的七七八八了宏蛉,后續(xù)我們將會(huì)陸續(xù)的分享一些工作中常用的小技巧。
程序員的核心競(jìng)爭(zhēng)力其實(shí)還是技術(shù)性置,因此對(duì)技術(shù)還是要不斷的學(xué)習(xí)拾并,關(guān)注 “IT巔峰技術(shù)” 公眾號(hào) ,該公眾號(hào)內(nèi)容定位:中高級(jí)開(kāi)發(fā)鹏浅、架構(gòu)師嗅义、中層管理人員等中高端崗位服務(wù)的,除了技術(shù)交流外還有很多架構(gòu)思想和實(shí)戰(zhàn)案例隐砸。
作者是 《 消息中間件 RocketMQ 技術(shù)內(nèi)幕》 一書(shū)作者芥喇,同時(shí)也是 “RocketMQ 上海社區(qū)”聯(lián)合創(chuàng)始人,曾就職于拼多多凰萨、德邦等公司,現(xiàn)任上市快遞公司架構(gòu)負(fù)責(zé)人械馆,主要負(fù)責(zé)開(kāi)發(fā)框架的搭建胖眷、中間件相關(guān)技術(shù)的二次開(kāi)發(fā)和運(yùn)維管理、混合云及基礎(chǔ)服務(wù)平臺(tái)的建設(shè)霹崎。