前言
談起Tomcat的誕生,最早可以追溯到1995年。近20年來,Tomcat始終是使用最廣泛的Web服務器,由於其使用Java語言開發,所以廣為Java程序員所熟悉。很多人早期的J2EE項目,由程序員自己實現Jsp頁面或者Servlet接受請求,后來借助Struts1、Struts2、Spring等中間件后,實際也是利用Filter或者Servlet處理請求,大家肯定要問了,這些Servlet處理的請求來自哪里?Tomcat作為Web服務器是怎樣將HTTP請求交給Servlet的呢?
本文就Tomcat對HTTP的請求處理細節進行分析。
提示:閱讀本文前,請確保首先理解了《Tomcat源碼分析——生命周期管理》中的內容。
Connector的初始化
根據《Tomcat源碼分析——生命周期管理》一文的內容,我們知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是與HTTP請求處理相關的容器。Service是Server的子容器,而Connector又是Service的子容器。那么這三個容器的初始化順序為:Server->Service->Connector。Connector的實現分為以下幾種:
- Http Connector:基於HTTP協議,負責建立HTTP連接。它又分為BIO Http Connector與NIO Http Connector兩種,后者提供非阻塞IO與長連接Comet支持。
- AJP Connector:基於AJP協議,AJP是專門設計用於Tomcat與HTTP服務器通信定制的協議,能提供較高的通信速度和效率。如與Apache服務器集成時,采用這個協議。
- APR HTTP Connector:用C實現,通過JNI調用的。主要提升對靜態資源(如HTML、圖片、CSS、JS等)的訪問性能。現在這個庫已獨立出來可用在任何項目中。由於APR性能較前兩類有很大提升,所以目前是Tomcat的默認Connector。
現在我們直接來看Connector的initInternal方法吧,見代碼清單1。
代碼清單1
@Override protected void initInternal() throws LifecycleException { super.initInternal(); // Initialize adapter adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); IntrospectionUtils.setProperty(protocolHandler, "jkHome", System.getProperty("catalina.base")); onameProtocolHandler = register(protocolHandler, createObjectNameKeyProperties("ProtocolHandler")); mapperListener.setDomain(getDomain()); onameMapper = register(mapperListener, createObjectNameKeyProperties("Mapper")); }
代碼清單1說明了Connector的初始化步驟如下:
步驟一 構造網絡協議處理的CoyoteAdapter
代碼清單1構造了CoyoteAdapter對象,並且將其設置為ProtocolHandler的Adapter。ProtocolHandler是做什么的呢?Tomcat處理HTTP請求,需要有一個ServerSocket監聽網絡端口來完成任務。接口ProtocolHandler被設計成控制網絡端口監聽組件運行,負責組件的生命周期控制,這個接口實際並沒有定義網絡端口監聽功能的規范,而是用於負責維護組件的生命周期。從ProtocolHandler的名字來看,它應該是網絡協議的處理者,但它實際不負責這個功能,而是將其交給org.apache.coyote.Adapter來完成,這么設計估計是為了方便維護和拓展新功能。Http11Protocol是ProtocolHandler接口的一個實現(是Connector的默認處理協議),被設計用來處理HTTP1.1網絡協議的請求,通過該類可以完成在某個網絡端口上面的監聽,同時以HTTP1.1的協議來解析請求內容,然后將請求傳遞到Connector所寄居的Container容器pipeline流水工作線上處理。此處的ProtocolHandler是何時生成的呢?還記得《TOMCAT源碼分析——SERVER.XML文件的加載與解析》一文中的Digester和Rule嗎?Digester在解析到<Connector>標簽的時候,會執行startElement方法,startElement中會調用Rule的begin(String namespace, String name, Attributes attributes)方法,Connector對應的Rule包括ConnectorCreateRule,ConnectorCreateRule的begin方法的實現見代碼清單2。
代碼清單2
@Override public void begin(String namespace, String name, Attributes attributes) throws Exception { Service svc = (Service)digester.peek(); Executor ex = null; if ( attributes.getValue("executor")!=null ) { ex = svc.getExecutor(attributes.getValue("executor")); } Connector con = new Connector(attributes.getValue("protocol")); if ( ex != null ) _setExecutor(con,ex); digester.push(con); }
代碼清單2中調用了Connector的構造器,傳遞的參數為屬性protocol。我們知道server.xml中的Connector有兩個:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <!-- Define an AJP 1.3 Connector on port 8009 --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
我們看看Connector的構造器實現,見代碼清單3。
代碼清單3
public Connector(String protocol) { setProtocol(protocol); // Instantiate protocol handler try { Class<?> clazz = Class.forName(protocolHandlerClassName); this.protocolHandler = (ProtocolHandler) clazz.newInstance(); } catch (Exception e) { log.error (sm.getString ("coyoteConnector.protocolHandlerInstantiationFailed", e)); } }
setProtocol方法(見代碼清單4)根據protocol參數的不同,調用setProtocolHandlerClassName方法(見代碼清單5)設置protocolHandlerClassName屬性。以HTTP/1.1為例,由於默認情況下Apr不可用,所以protocolHandlerClassName會被設置為org.apache.coyote.http11.Http11Protocol,那么反射生成的protocolHandler就是Http11Protocol實例。Tomcat默認還會配置協議是AJP/1.3的Connector,那么此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。
代碼清單4
/** * Set the Coyote protocol which will be used by the connector. * * @param protocol The Coyote protocol name */ public void setProtocol(String protocol) { if (AprLifecycleListener.isAprAvailable()) { if ("HTTP/1.1".equals(protocol)) { setProtocolHandlerClassName ("org.apache.coyote.http11.Http11AprProtocol"); } else if ("AJP/1.3".equals(protocol)) { setProtocolHandlerClassName ("org.apache.coyote.ajp.AjpAprProtocol"); } else if (protocol != null) { setProtocolHandlerClassName(protocol); } else { setProtocolHandlerClassName ("org.apache.coyote.http11.Http11AprProtocol"); } } else { if ("HTTP/1.1".equals(protocol)) { setProtocolHandlerClassName ("org.apache.coyote.http11.Http11Protocol"); } else if ("AJP/1.3".equals(protocol)) { setProtocolHandlerClassName ("org.apache.coyote.ajp.AjpProtocol"); } else if (protocol != null) { setProtocolHandlerClassName(protocol); } } }
代碼清單5
public void setProtocolHandlerClassName(String protocolHandlerClassName) { this.protocolHandlerClassName = protocolHandlerClassName; }
除此之外,ProtocolHandler還有其它實現,如圖1所示。
圖1 ProtocolHandler類繼承體系
圖1中有關ProtocolHandler的實現類都在org.apache.coyote包中 。前面所說的BIO Http Connector實際就是Http11Protocol,NIO Http Connector實際就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外還有一個MemoryProtocolHandler(這個是做什么的,目前沒搞清楚,有知道的同學告訴我下啊!)。
步驟二 將ProtocolHandler、MapperListener注冊到JMX
BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注冊名為Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的注冊名為Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注冊名為Catalina:type=ProtocolHandler,port=8009。AJP Connector的MapperListener的注冊名為Catalina:type=Mapper,port=8009。有關Tomcat中JMX注冊的內容,請閱讀《TOMCAT源碼分析——生命周期管理》一文。
Connector的啟動
根據《Tomcat源碼分析——生命周期管理》一文的內容,我們知道Tomcat中有很多容器。ProtocolHandler的初始化稍微有些特殊,Server、Service、Connector這三個容器的初始化順序為:Server->Service->Connector。值得注意的是,ProtocolHandler作為Connector的子容器,其初始化過程並不是由Connector的initInternal方法調用的,而是與啟動過程一道被Connector的startInternal方法所調用。由於本文的目的是分析請求,所以直接從Connector的startInternal方法(見代碼清單6)開始。
代碼清單6
/** * Begin processing requests via this Connector. * * @exception LifecycleException if a fatal startup error occurs */ @Override protected void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); // Protocol handlers do not follow Lifecycle conventions. // protocolHandler.init() needs to wait until the connector.start() try { protocolHandler.init(); } catch (Exception e) { throw new LifecycleException (sm.getString ("coyoteConnector.protocolHandlerInitializationFailed", e)); } try { protocolHandler.start(); } catch (Exception e) { String errPrefix = ""; if(this.service != null) { errPrefix += "service.getName(): \"" + this.service.getName() + "\"; "; } throw new LifecycleException (errPrefix + " " + sm.getString ("coyoteConnector.protocolHandlerStartFailed", e)); } // MapperListener doesn't follow Lifecycle conventions either gja mapperListener.init(); }
代碼清單6說明了Connector的startInternal方法的執行順序如下:
- 將Connector容器的狀態更改為啟動中(LifecycleState.STARTING);
- 初始化ProtocolHandler;
- 啟動ProtocolHandler;
- 初始化MapperListener。
初始化ProtocolHandler
簡單起見,我們以Http11Protocol為例剖析ProtocolHandler的init方法,其實現見代碼清單7。
代碼清單7
@Override public void init() throws Exception { ((JIoEndpoint)endpoint).setName(getName()); ((JIoEndpoint)endpoint).setHandler(cHandler); // Verify the validity of the configured socket factory try { if (isSSLEnabled()) { sslImplementation = SSLImplementation.getInstance(sslImplementationName); socketFactory = sslImplementation.getServerSocketFactory(); ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory); } else if (socketFactoryName != null) { socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance(); ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory); } } catch (Exception ex) { log.error(sm.getString("http11protocol.socketfactory.initerror"), ex); throw ex; } if (socketFactory!=null) { Iterator<String> attE = attributes.keySet().iterator(); while( attE.hasNext() ) { String key = attE.next(); Object v=attributes.get(key); socketFactory.setAttribute(key, v); } } try { endpoint.init(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.initerror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.init", getName())); }
從代碼清單7看到,Http11Protocol的初始化步驟如下:
步驟一 設置JIoEndpoint的名稱
JIoEndpoint的名稱默認為http-8080,這里的JIoEndpoint是在調用Http11Protocol的構造器時創建的,Http11Protocol的構造器中還設置了socket的延遲關閉選項soLingerOn、socket的延時關閉秒數soLingerTime、socket連接超時時間soTimeout、提高socket性能的tcpNoDelay等選項,見代碼清單8。
代碼清單8
public Http11Protocol() { endpoint = new JIoEndpoint(); setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT); setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); }
步驟二 設置JIoEndpoint的Handler
JIoEndpoint的handler被設置為cHandler,此cHandler的定義如下:
protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
步驟三 配置ServerSocketFactory
從代碼清單7看到,生成ServerSocketFactory有三種方式:
-
如果在server.xml中配置Connector時指定了SSLEnabled="true"的屬性,那么創建帶有SSL(Secure Sockets Layer 安全套接層)的ServerSocketFactory;
- 如果Http11Protocol指定了socketFactoryName,則使用socketFactoryName反射生成ServerSocketFactory實例;
- 如果不滿足以上2個條件,那么JIoEndpoint的init方法(見代碼清單9)將創建ServerSocketFactory。當SSLEnabled="true"時,JIoEndpoint的init方法還會給ServerSocketFactory設置一些SSL相關的屬性。最后使用此ServerSocketFactory創建serverSocket。此外,acceptorThreadCount屬性用於指定接受連接的線程數,可以通過給Connector設置acceptorThreadCount屬性進行調整,默認值為1。
代碼清單9
@Override public void init() throws Exception { if (initialized) return; // Initialize thread count defaults for acceptor if (acceptorThreadCount == 0) { acceptorThreadCount = 1; } if (serverSocketFactory == null) { serverSocketFactory = ServerSocketFactory.getDefault(); } if (isSSLEnabled()) { serverSocketFactory.setAttribute(SSL_ATTR_ALGORITHM, getAlgorithm()); serverSocketFactory.setAttribute(SSL_ATTR_CLIENT_AUTH, getClientAuth()); serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_FILE, getKeystoreFile()); serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PASS, getKeystorePass()); serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_TYPE, getKeystoreType()); serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PROVIDER, getKeystoreProvider()); serverSocketFactory.setAttribute(SSL_ATTR_SSL_PROTOCOL, getSslProtocol()); serverSocketFactory.setAttribute(SSL_ATTR_CIPHERS, getCiphers()); serverSocketFactory.setAttribute(SSL_ATTR_KEY_ALIAS, getKeyAlias()); serverSocketFactory.setAttribute(SSL_ATTR_KEY_PASS, getKeyPass()); serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_FILE, getTruststoreFile()); serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PASS, getTruststorePass()); serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_TYPE, getTruststoreType()); serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PROVIDER, getTruststoreProvider()); serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_ALGORITHM, getTruststoreAlgorithm()); serverSocketFactory.setAttribute(SSL_ATTR_CRL_FILE, getCrlFile()); serverSocketFactory.setAttribute(SSL_ATTR_TRUST_MAX_CERT_LENGTH, getTrustMaxCertLength()); serverSocketFactory.setAttribute(SSL_ATTR_SESSION_CACHE_SIZE, getSessionCacheSize()); serverSocketFactory.setAttribute(SSL_ATTR_SESSION_TIMEOUT, getSessionTimeout()); serverSocketFactory.setAttribute(SSL_ATTR_ALLOW_UNSAFE_RENEG, getAllowUnsafeLegacyRenegotiation()); } if (serverSocket == null) { try { if (getAddress() == null) { serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog()); } else { serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog(), getAddress()); } } catch (BindException orig) { String msg; if (getAddress() == null) msg = orig.getMessage() + " <null>:" + getPort(); else msg = orig.getMessage() + " " + getAddress().toString() + ":" + getPort(); BindException be = new BindException(msg); be.initCause(orig); throw be; } } //if( serverTimeout >= 0 ) // serverSocket.setSoTimeout( serverTimeout ); initialized = true; }
啟動ProtocolHandler
我們繼續以Http11Protocol為例,剖析ProtocolHandler的start方法,其實現見代碼清單10。
代碼清單10
@Override public void start() throws Exception { if (this.domain != null) { try { tpOname = new ObjectName (domain + ":" + "type=ThreadPool,name=" + getName()); Registry.getRegistry(null, null) .registerComponent(endpoint, tpOname, null ); } catch (Exception e) { log.error("Can't register endpoint"); } rgOname=new ObjectName (domain + ":type=GlobalRequestProcessor,name=" + getName()); Registry.getRegistry(null, null).registerComponent ( cHandler.global, rgOname, null ); } try { endpoint.start(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.starterror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.start", getName())); }
從代碼清單10可以知道JIoEndpoint以Catalina:type=ThreadPool,name=http-8080注冊到JMX,cHandler.global(Http11ConnectionHandler的對象屬性,類型為RequestGroupInfo)以Catalina:type=GlobalRequestProcessor,name=http-8080注冊到JMX。最后調用JIoEndpoint的start方法(見代碼清單11)接受請求的創建線程池並創建一定數量的接收請求線程。
代碼清單11
@Override public void start() throws Exception { // Initialize socket if not done before if (!initialized) { init(); } if (!running) { running = true; paused = false; // Create worker collection if (getExecutor() == null) { createExecutor(); } // Start acceptor threads for (int i = 0; i < acceptorThreadCount; i++) { Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i); acceptorThread.setPriority(threadPriority); acceptorThread.setDaemon(getDaemon()); acceptorThread.start(); } } }
從代碼清單11看出JIoEndpoint的start方法的執行步驟如下:
步驟一 對JIoEndpoint做初始化檢查
這一步實際就是判斷是否已經初始化(即initialized是否為true),如果沒有初始化則需要調用JIoEndpoint的init方法進行初始化。
步驟二 創建線程池與任務隊列
如果JIoEndpoint尚未處於運行中(即running等於true),才會創建線程池和任務隊列。如果尚未創建線程池(即調用getExecutor方法等於null),則需要調用createExecutor方法(見代碼清單12)創建線程池和任務隊列TaskQueue。
代碼清單12
public void createExecutor() { internalExecutor = true; TaskQueue taskqueue = new TaskQueue(); TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority()); executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf); taskqueue.setParent( (ThreadPoolExecutor) executor); }
步驟三 創建接收請線程
如果JIoEndpoint尚未處於運行中(即running等於true),才會創建接收請求線程。從代碼清單11可以看出接收請求線程的數量主要由acceptorThreadCount控制,代碼清單9已經告訴我們acceptorThreadCount的默認值為1,但是我們可以通過給Connector增加acceptorThreadCount屬性來修改接收請求線程的數量。這些接收請求線程的主要工作由Acceptor完成,Acceptor的實質是一個Runnable,見代碼清單13。
代碼清單13
/** * Server socket acceptor thread. */ protected class Acceptor implements Runnable { /** * The background thread that listens for incoming TCP/IP connections and * hands them off to an appropriate processor. gja */ public void run() { // Loop until we receive a shutdown command while (running) { // Loop if endpoint is paused while (paused) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore } } // Accept the next incoming connection from the server socket try { Socket socket = serverSocketFactory.acceptSocket(serverSocket); serverSocketFactory.initSocket(socket); // Hand this socket off to an appropriate processor if (!processSocket(socket)) { // Close socket right away try { socket.close(); } catch (IOException e) { // Ignore } } }catch ( IOException x ) { if ( running ) log.error(sm.getString("endpoint.accept.fail"), x); } catch (Throwable t) { log.error(sm.getString("endpoint.accept.fail"), t); } // The processor will recycle itself when it finishes } } }
初始化MapperListener
MapperListener的init方法用於初始化,見代碼清單14。
代碼清單14
/** * Initialize associated mapper. */ public void init() { // 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 jiaan findDefaultHost(); Engine engine = (Engine) connector.getService().getContainer(); engine.addContainerListener(this); Container[] conHosts = engine.findChildren(); for (Container conHost : conHosts) { Host host = (Host) conHost; if (!LifecycleState.NEW.equals(host.getState())) { host.addLifecycleListener(this); // Registering the host will register the context and wrappers registerHost(host); } } }
從代碼清單14看到MapperListener的初始化步驟如下:
步驟一 查找默認Host
StandardService的子容器包括:StandardEngine、Connector和Executor。MapperListener本身會持有Connector,所以可以通過各個容器的父子關系,找到Connector的同級容器StandardEngine。StandardHost是StandardEngine的子容器,Engine和Host的默認配置如下:
<Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" resolveHosts="false"/> </Host> </Engine>
findDefaultHost方法(見代碼清單15)可以獲取上面配置中的默認Host,Engine元素的defaultHost屬性值必須要與配置的某個Host元素的name屬性值相同。如果defaultHost的屬性值配置無誤,則會添加為MapperListener的Mapper對象屬性的defaultHostName。
代碼清單15
private void findDefaultHost() { Engine engine = (Engine) connector.getService().getContainer(); String defaultHost = engine.getDefaultHost(); boolean found = false; if (defaultHost != null && defaultHost.length() >0) { Container[] containers = engine.findChildren(); for (Container container : containers) { Host host = (Host) container; if (defaultHost.equalsIgnoreCase(host.getName())) { found = true; break; } String[] aliases = host.findAliases(); for (String alias : aliases) { if (defaultHost.equalsIgnoreCase(alias)) { found = true; break; } } } } if(found) { mapper.setDefaultHostName(defaultHost); } else { log.warn(sm.getString("mapperListener.unknownDefaultHost", defaultHost)); } }
步驟二 將Host及其子容器Context,Context的子容器Wrapper注冊到MapperListener的Mapper對象
Mapper的數據結構,見代碼清單16。
代碼清單16
/** * Array containing the virtual hosts definitions. */ protected Host[] hosts = new Host[0]; /** * Default host name. */ protected String defaultHostName = null; /** * Context associated with this wrapper, used for wrapper mapping. */ protected Context context = new Context(); protected static abstract class MapElement { public String name = null; public Object object = null; } protected static final class Host extends MapElement { public ContextList contextList = null; } protected static final class ContextList { public Context[] contexts = new Context[0]; public int nesting = 0; } protected static final class Context extends MapElement { public String path = null; public String[] welcomeResources = new String[0]; public javax.naming.Context resources = null; public Wrapper defaultWrapper = null; public Wrapper[] exactWrappers = new Wrapper[0]; public Wrapper[] wildcardWrappers = new Wrapper[0]; public Wrapper[] extensionWrappers = new Wrapper[0]; public int nesting = 0; } protected static class Wrapper extends MapElement { public String path = null; public boolean jspWildCard = false; }
根據代碼清單16,我們知道Mapper中維護着一個Host數組,每個Host中有一個ContextList,這個ContextList中維護着一個Context數組。每個Context維護着一個defaultWrapper,三個Wrapper數組(exactWrappers、wildcardWrappers、extensionWrappers)。下面對Host、Context及Wrapper進行功能上的介紹:
- Host:代表一個虛擬主機,各Host的name不能相同,appBase代表各虛擬主機的應用發布位置;
- Context:代表一個應用,Context可以根據應用的/WEB-INF/web.xml文件中定義的servlet來處理請求。一個Host下可以有多個Context;
- Wrapper: 代表一個Servlet或者jsp,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。
以我本地為例,注冊到Mapper中的Host及其子容器如圖2所示。
圖2 注冊到Mapper中的Host及其Context子容器
圖2說明Host內一共5個Context,由於我的Tomcat是從svn拉下來的,所以webapps目錄下的.svn文件夾也是一個Context,除了這個天外來客,我將其它與請求有關的容器整理后用圖3來展示。
圖3 我本地的Host、Context及Wrapper
至此,Tomcat中為請求處理的准備工作已經完成。有關請求的處理過程請繼續閱讀《Tomcat源碼分析——請求原理分析(中)》一文。
如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4875249.html