Tomcat原始碼分析(五)--容器處理連線之servlet的對映
阿新 • • 發佈:2019-02-15
本文所要解決的問題:一個http請求過來,容器是怎麼知道選擇哪個具體servlet?
我們知道,一個Context容器表示一個web應用,一個Wrapper容器表示一個servlet,所以上面的問題可以轉換為怎麼由Context容器選擇servlet,答案是對映器。對映器是實現了Mapper介面的類,作用就是根據請求連線(主要是協議和路徑)來選擇下一個容器,可以看做是一個雜湊表,根據關鍵欄位來選擇具體的值,Mapper介面的定義為:
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方法,最終會到達StandardContextValue閥的invoke方法裡面,在這個invoke方法中有一句這樣的程式碼:
wrapper = (Wrapper) context.map(request, true);
這句程式碼表示容器會呼叫map方法來對映請求到具體的wrapper上,意思就是說,根據連線請求request來選擇wrapper。上面的map會呼叫父類ContainerBase的map方法來找到具體的對映器,至於這個對映器和容器是怎麼關聯上的,具體請參考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:
public String findServletMapping(String pattern) {
synchronized (servletMappings) {
return ((String) servletMappings.get(pattern));
}
}
findChild方法跟findServletMapping方法一樣,也是在一個HashMap找wrapper容器。至此,已經能夠回答一開始的問題了,StandardContext容器根據對映器來選擇wrapper。當然,容器Engine和Host也是根據對映器來選擇它們的下一級容器的。