本文所要解決的問題:一個http請求過來,容器是怎麼知道選擇哪個具體servlet?
我們知道,一個Context容器表示一個web應用,一個Wrapper容器表示一個servlet,所以上面的問題可以轉換為怎麼由Context容器選擇servlet,答案是映射器。映射器是實現瞭Mapper接口的類,作用就是根據請求連接(主要是協議和路徑)來選擇下一個容器,可以看做是一個哈希表,根據關鍵字段來選擇具體的值,Mapper接口的定義為:
[java]
public interface Mapper {
public Container getContainer();//返回與該映射器相關聯的容器
public void setContainer(Container container);
public String getProtocol();//返回與該映射器處理的協議
public void setProtocol(String protocol);
public Container map(Request request, boolean update); //映射函數,實現該函數
}
在Tomcat源碼分析(四)–容器處理鏈接之責任鏈模式中已經知道,請求連接到達StandardContext容器的invoke方法,在StandardContext容器有一句這樣的代碼:
[java]
wrapper = (Wrapper) context.map(request, true);
這句代碼表示容器會調用map方法來映射請求到具體的wrapper上,意思就是說,根據連接請求request來選擇wrapper。上面的map會調用父類ContainerBase的map方法來找到具體的映射器,至於這個映射器和容器是怎麼關聯上的,具體請參考 Tomcat源碼分析(三)–連接器是如何與容器關聯的?這篇文章,大致原理是一樣的。StandardContext容器有一個標準的映射器實現類StandardContextMapper,所以最終會調用到映射器StandardContextMapper的map方法,這個方法是選擇servlet的關鍵(省略瞭一些代碼):
[java]
public Container map(Request request, boolean update) {
// Identify the context-relative URI to be mapped
String contextPath =
((HttpServletRequest) request.getRequest()).getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI = requestURI.substring(contextPath.length());
// Apply the standard request URI mapping rules from the specification
Wrapper wrapper = null;
String servletPath = relativeURI;
String pathInfo = null;
String name = null;
// Rule 1 — Exact Match
if (wrapper == null) {
if (debug >= 2)
context.log(" Trying exact match");
if (!(relativeURI.equals("/")))
name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
if (wrapper != null) {
servletPath = relativeURI;
pathInfo = null;
}
}
// Rule 2 — Prefix Match
if (wrapper == null) {
if (debug >= 2)
context.log(" Trying prefix match");
servletPath = relativeURI;
while (true) {
name = context.findServletMapping(servletPath + "/*");
if (name != null)
wrapper = (Wrapper) context.findChild(name);
if (wrapper != null) {
pathInfo = relativeURI.substring(servletPath.length());
if (pathInfo.length() == 0)
pathInfo = null;
break;
}
int slash = servletPath.lastIndexOf('/');
if (slash < 0)
break;
servletPath = servletPath.substring(0, slash);
}
}
// Rule 3 — Extension Match
if (wrapper == null) {
if (debug >= 2)
context.log(" Trying extension match");
int slash = relativeURI.lastIndexOf('/');
if (slash >= 0) {
String last = relativeURI.substring(slash);
int period = last.lastIndexOf('.');
if (period >= 0) {
String pattern = "*" + last.substring(period);
name = context.findServletMapping(pattern);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
if (wrapper != null) {
servletPath = relativeURI;
pathInfo = null;
}
}
}
}
// Rule 4 — Default Match
if (wrapper == null) {
if (debug >= 2)
context.log(" Trying default match");
name = context.findServletMapping("/");
if (name != null)
wrapper = (Wrapper) context.findChild(name);
if (wrapper != null) {
servletPath = relativeURI;
pathInfo = null;
}
}
代碼很長,但是很容易看懂,就是分4中匹配模式(完全匹配,前綴匹配,擴展匹配,默認匹配)來選擇wrapper,關鍵代碼就是name = context.findServletMapping和wrapper = (Wrapper) context.findChild(name);這裡面context都是StandardContext。context.findServletMapping是根據匹配模式來找到servlet名字,context.findChild是根據servlet名字找到具體的wrapper。findServletMapping方法很簡單,就是在一個HashMap裡面得到servlet名字,代碼如下,其中servletMappings是一個HashMap:
[java]
public String findServletMapping(String pattern) {
synchronized (servletMappings) {
return ((String) servletMappings.get(pattern));
}
}
findChild方法跟findServletMapping方法一樣,也是在一個HashMap找wrapper容器。至此,已經能夠回答一開始的問題瞭,StandardContext容器根據映射器來選擇wrapper。當然,容器Engine和Host也是根據映射器來選擇它們的下一級容器的。
作者:haitao111313