深入理解Tomcat系列之七:詳解URL請求
前言
這裡分析一個實際的請求是如何在Tomcat中被處理的,以及最後是怎麼樣找到要處理的Servlet的?當我們在瀏覽器中輸入http://hostname:port/contextPath/servletPath
,前面的hostname與port用於建立tcp連線,由於Http也是基於Tcp協議的,所以這裡涉及TCP連線的三次握手。後面的contextPath與servletPath則是與伺服器進行請求的資訊,contextPath指明瞭與伺服器中哪個Context容器進行互動,伺服器會根據這個URL與對應的Context容器建立連線,那麼這個過程是如何實現的呢?
在Tomcat7(本文也是基於Tomcat7)中主要通過一個對映來完成的,這個對映的工作交給org.apache.tomcat.util.http.mapper.Mapper類來完成的,這個類儲存了Container容器所有子容器的資訊,在請求從Connector交給Container容器之前,Mapper會根據hostname和port將host容器與context容器設定到Request的mappingData屬性中,這樣在Connector的請求進入Container容器之前就知道了交給哪個容器了。
這段程式碼如下:
程式碼清單5-4:
// Virtual host mapping
if (mappingData.host == null) {
Host[] hosts = this.hosts;
int pos = findIgnoreCase(hosts, host);
if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {
mappingData.host = hosts[pos].object;
contexts = hosts[pos ].contextList.contexts;
nesting = hosts[pos].contextList.nesting;
} else {
if (defaultHostName == null) {
return;
}
pos = find(hosts, defaultHostName);
if ((pos != -1) && (defaultHostName.equals(hosts[pos ].name))) {
mappingData.host = hosts[pos].object;
contexts = hosts[pos].contextList.contexts;
nesting = hosts[pos].contextList.nesting;
} else {
return;
}
}
}
// Context mapping
if (mappingData.context == null) {
int pos = find(contexts, uri);
if (pos == -1) {
return;
}
int lastSlash = -1;
int uriEnd = uri.getEnd();
int length = -1;
boolean found = false;
while (pos >= 0) {
if (uri.startsWith(contexts[pos].name)) {
length = contexts[pos].name.length();
if (uri.getLength() == length) {
found = true;
break;
} else if (uri.startsWithIgnoreCase("/", length)) {
found = true;
break;
}
}
if (lastSlash == -1) {
lastSlash = nthSlash(uri, nesting + 1);
} else {
lastSlash = lastSlash(uri);
}
uri.setEnd(lastSlash);
pos = find(contexts, uri);
}
uri.setEnd(uriEnd);
if (!found) {
if (contexts[0].name.equals("")) {
context = contexts[0];
}
} else {
context = contexts[pos];
}
if (context != null) {
mappingData.contextPath.setString(context.name);
}
}
if (context != null) {
ContextVersion[] contextVersions = context.versions;
int versionCount = contextVersions.length;
if (versionCount > 1) {
Object[] contextObjects = new Object[contextVersions.length];
for (int i = 0; i < contextObjects.length; i++) {
contextObjects[i] = contextVersions[i].object;
}
mappingData.contexts = contextObjects;
}
if (version == null) {
// Return the latest version
contextVersion = contextVersions[versionCount - 1];
} else {
int pos = find(contextVersions, version);
if (pos < 0 || !contextVersions[pos].name.equals(version)) {
// Return the latest version
contextVersion = contextVersions[versionCount - 1];
} else {
contextVersion = contextVersions[pos];
}
}
mappingData.context = contextVersion.object;
mappingData.contextSlashCount = contextVersion.slashCount;
}
// Wrapper mapping
if ((contextVersion != null) && (mappingData.wrapper == null)) {
internalMapWrapper(contextVersion, uri, mappingData);
}
下面啟動伺服器,在瀏覽器中輸入http://localhost:8080/examples/jsp/jsp2/el/composite.jsp,斷點除錯可以mappingData.host屬性為localhost
,mappingData.contextPath.setString(context.name)中context.name為examples
,mappingData.wrapperPath為/jsp/jsp2/el/composite.jsp
,這驗證了mappingData屬性的有效性,那麼mappingData屬性是如何設定到Request物件的屬性中的呢?
通過org.apache.catalina.connector.Request的原始碼可以知道,其是通過setContextPath方法與setHost方法設定進去的,其原始碼如下:
程式碼清單5-5:
public void setHost(Host host) {
mappingData.host = host;
}
public void setContextPath(String path) {
if (path == null) {
mappingData.contextPath.setString("");
} else {
mappingData.contextPath.setString(path);
}
}
由於請求是從Connector傳過來的,而CoyoteAdapter是Connector中處理請求的最後一個類,那麼設定這兩個屬性的程式碼肯定在CoyoteAdapter類中,果不其然:
程式碼清單5-6:
// This will map the the latest version by default
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
//Mapper的map方法
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData)
throws Exception {
if (host.isNull()) {
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version,
mappingData);
}
intenalMap方法執行就是程式碼清單5-4的內容,這樣就把從Connector傳入請求,並設定Request物件的mappingData屬性的整個流程就打通了。還有一個疑問是為什麼Mapper類中可以擁有Container所有子容器的資訊呢?答案需要回到Tomcat啟動過程圖的第21步的startIntenal方法了:
程式碼清單5-7:
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// Find any components that have already been initialized since the
// MBean listener won't be notified as those components will have
// already registered their MBeans
findDefaultHost();
Engine engine = (Engine) connector.getService().getContainer();
addListeners(engine);
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}
這段程式碼就是將MapperListener作為一個監聽者加到整個Container容器的每一個子容器中,這樣任何一個子容器發生變化,MapperListener都將被通知,響應的mappingData屬性也會改變。最後可以總結訪問請求地址為http://localhost:8080/examples/composite.jsp的處理過程:
- 在埠8080啟動Server,並通知Service完成啟動,Service通知Connector完成初始化和啟動的過程
- Connector首先收到這個請求,會呼叫ProtocolHandler完成http協議的解析,然後交給SocketProcessor處理,解析請求頭,再交給CoyoteAdapter解析請求行和請求體,並把解析資訊封裝到Request和Response物件中
- 把請求(此時應該是Request物件,這裡的Request物件已經封裝了Http請求的資訊)交給Container容器
- Container容器交給其子容器——Engine容器,並等待Engine容器的處理結果
- Engine容器匹配其所有的虛擬主機,這裡匹配到Host
- 請求被移交給hostname為localhost的Host容器,host匹配其所有子容器Context,這裡找到contextPath為/examples的Context容器。如果匹配不到就把該請求交給路徑名為”“的Context去處理
- 請求再次被移交給Context容器,Context繼續匹配其子容器Wrapper,由Wrapper容器載入composite.jsp對應的servlet,這裡編譯的servlet是basic_002dcomparisons_jsp.class檔案
- Context容器根據字尾匹配原則*.jsp找到composite.jsp編譯的java類的class檔案
- Connector構建一個org.apache.catalina.connector.Request以及org.apache.catalina.connector.Response物件,使用反射呼叫Servelt的service方法
- Context容器把封裝了響應訊息的Response物件返回給Host容器
- Host容器把Response返回給Engine容器
- Engine容器返回給Connector
- Connetor容器把Response返回給瀏覽器
- 瀏覽器解析Response報文
- 顯示資源內容
根據前面的內容,其中的對映關係是由MapperListener類完成的。
相關推薦
深入理解Tomcat系列之七:詳解URL請求
前言 這裡分析一個實際的請求是如何在Tomcat中被處理的,以及最後是怎麼樣找到要處理的Servlet的?當我們在瀏覽器中輸入http://hostname:port/contextPath/servletPath,前面的hostname與port用於建立tc
深入理解Tomcat系列之七 詳解URL請求
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
深入理解Tomcat系列之五:Context容器和Wrapper容器
ssa stream servlet實例 可用 igel sse ould rip alt 前言 Context容器是一個Web項目的代表,主要管理Servlet實例,在Tomcat中Servlet實例是以Wrapper出現的。如今問題是怎樣才幹通過C
深入理解Tomcat系列之六 Servlet工作原理
前言Servlet是Web開發中的核心技術,作為一名合格的開發人員,就必須清楚Servlet的工作原理。本章沒有對Servlet技術本身進行詳細的說明,只是針對開發過程中一次Servlet的請求的處理過程進行分析的。Servlet實際上就是一個java類,只不過可以和
深入理解Tomcat系列之三 Connector
前言Connector是Tomcat的聯結器,其主要任務是負責處理瀏覽器傳送過來的請求,並建立一個Request和Response的物件用於和瀏覽器交換資料,然後產生一個執行緒用於處理請求,Connector會把Request和Response物件傳遞給該執行緒,該執
深入理解Spring系列之八:常用的擴充套件介面
Spring不僅提供了一個進行快速開發的基礎框架,而且還提供了很多可擴充套件的介面,用於滿足一些額外的開發需求,本篇將對常用的可擴充套件介面進行歸納總結。 1.InitializingBean介面 InitializingBean介面中只有一個afterPr
深入理解Tomcat系列之四 Engine和Host容器
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
mongo 3.4分片集群系列之五:詳解平衡器
大致 mongos 搭建 一次 相對 時間 表示 部分 man 這個系列大致想跟大家分享以下篇章(我會持續更新的↖(^ω^)↗): 1、mongo 3.4分片集群系列之一:淺談分片集群 2、mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3、mongo 3.4
mongo 3.4分片集群系列之六:詳解配置數據庫
初始化 kpi 更新 並且 color tag 成員 gin sha 這個系列大致想跟大家分享以下篇章(我會持續更新的↖(^ω^)↗): 1、mongo 3.4分片集群系列之一:淺談分片集群 2、mongo 3.4分片集群系列之二:搭建分片集群--哈希分片 3、mongo
深入理解Tomcat系列之一:系統架構
chan 比較 efi 多個 soci 重點 之一 處理流程 containe 前言: Tomcat是Apache基金組織下的開源項目,性質是一個Web服務器。下面這種情況很普遍:在eclipse床架一個web項目並部署到Tomcat中,啟動tomcat,在瀏覽器中輸入一個
深入理解Spring系列之十二:@Transactional是如何工作的
結合Spring框架,在進行資料庫操作的時候,經常使用@Transactional註解,工作經歷中看到很多開發者使用方式都是錯誤的,沒有深入理解過其原理,這是很危險的!!本篇將深入Spring原始碼,分析@Transactional註解的工作原理。相信,看完你會
深入理解Tomcat系列之一:系統架構(轉)
前言 Tomcat是Apache基金組織下的開源專案,性質是一個Web伺服器。下面這種情況很普遍:在eclipse床架一個web專案並部署到Tomcat中,啟動tomcat,在瀏覽器中輸入一個類似http://localhost:8080/webproject/anyname.jsp的url,然後就可以看到
深入理解ajax系列第七篇
gin 開發工程師 tar component fin hasattr mar tex 員工 前面的話 雖然ajax全稱是asynchronous javascript and XML。但目前使用ajax技術時,傳遞JSON已經成為事實上的標準。因為相較於XML而言,J
Office 365 系列之七:安裝 Office 365 ProPlus
office365 安裝office 安裝在線office 在前幾篇文章中已經完成了O365用戶的手動創建和批量導入並分配對應的許可。既然用戶已經具備使用這個服務的許可了,今天我們就一起來看看如何去使用O365,和本地的Office又有何差別,然後介紹使用即點即用技術安裝 Office 3
Exchange 2013系列之七:部署後任務
Exchange AD Windows 到昨天為止,Exchange 的部署基本上就完成了,今天我們來看看部署完成後還需要做一些什麽工作。 一、發送連接器 默認情況下,Exchange 2013是不允許將郵件發送到域外部的,如果想要發送外部郵件,需要建立一個發送連接器。打開ECP管理界面,導航至
深入理解Redis系列之SpringBoot整合Redis
SpringBoot環境 快速搭建一個SpringBoot工程 進入 https://start.spring.io 網站, 使用該網站初始化一個SpringBoot工程 新增相關依賴 因為使用spring initializer已經幫我們把Redis的依賴建立好了; 但是
skyfans之每天一個Liunx命令系列之七:cpuinfo、meminfo
首先宣告:非常抱歉,昨天懶了,沒有更新基本命令的內容,主要原因是一直在看IG的比賽,看完之後3:0的戰績使朋友們高興的出去喝酒了,那看的是真解氣啊。IG算是幫RNG報仇了。但是,從IG整改隊伍的身上,我想RNG也應該多學學某種東西。好廢話不多說,開始正題(我只是
深入理解JVM虛擬機器(七):虛擬機器位元組碼執行引擎
程式碼編譯的結果就是從本地機器碼轉變為位元組碼。我們都知道,編譯器將Java原始碼轉換成位元組碼?那麼位元組碼是如何被執行的呢?這就涉及到了JVM位元組碼執行引擎,執行引擎負責具體的程式碼呼叫及執行過程。就目前而言,所有的執行引擎的基本一致: 輸入:位元組碼檔案
敏捷開發產品管理系列之七:Product Owner團隊
目的在之前的《Product Servant》一篇中曾經提到,作為產品經理或產品總監,都應該有自己的方式來根據市場和使用者情況來管理產品的走向,其中前者更傾向於具體的功能,而後者則更傾向於市場方向的競爭力;前者要求細節,後者要求高度。那麼,這兩個人到底誰是傳統意義上的Pro
logback系列之七:繼承RollingFileAppender,儲存自定義檔名的日誌
繼承類:package com.hk3t.air.system.log; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos