tomcat8源码分析-Connector初始化
談起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
@Overrideprotected void initInternal() throws LifecycleException {super.initInternal();// Initialize adapteradapter = new CoyoteAdapter(this);protocolHandler.setAdapter(adapter);// Make sure parseBodyMethodsSet has a defaultif (null == parseBodyMethodsSet) {setParseBodyMethods(getParseBodyMethods());}if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",getProtocolHandlerClassName()));}if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&protocolHandler instanceof AbstractHttp11JsseProtocol) {AbstractHttp11JsseProtocol<?> jsseProtocolHandler =(AbstractHttp11JsseProtocol<?>) protocolHandler;if (jsseProtocolHandler.isSSLEnabled() &&jsseProtocolHandler.getSslImplementationName() == null) {// OpenSSL is compatible with the JSSE configuration, so use it if APR is availablejsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());}}try {protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}}?
代碼清單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
@Overridepublic 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);}String sslImplementationName = attributes.getValue("sslImplementationName");if (sslImplementationName != null) {setSSLImplementationName(con, sslImplementationName);}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) {//DeprcatedsetProtocol(protocol);// Instantiate protocol handlerProtocolHandler p = null;try {Class<?> clazz = Class.forName(protocolHandlerClassName);p = (ProtocolHandler) clazz.getConstructor().newInstance();} catch (Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);} finally {this.protocolHandler = p;}if (Globals.STRICT_SERVLET_COMPLIANCE) {uriCharset = StandardCharsets.ISO_8859_1;} else {uriCharset = StandardCharsets.UTF_8;}}
setProtocol方法已經作廢。以HTTP/1.1為例,由于默認情況下Apr不可用,所以protocolHandlerClassName會被設置為"org.apache.coyote.http11.Http11NioProtocol",那么反射生成的protocolHandler就是Http11NioProtocol實例。Tomcat默認還會配置協議是AJP/1.3的Connector,那么此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。
代碼清單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注冊到JMX
BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注冊名為Catalina:type=ProtocolHandler,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注冊名為Catalina:type=ProtocolHandler,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
@Overrideprotected void startInternal() throws LifecycleException {// Validate settings before startingif (getPort() < 0) {throw new LifecycleException(sm.getString("coyoteConnector.invalidPort", Integer.valueOf(getPort())));}setState(LifecycleState.STARTING);try {protocolHandler.start();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);}}代碼清單6說明了Connector的startInternal方法的執行順序如下:
初始化PROTOCOLHANDLER
簡單起見,我們以AbstractProtocol為例剖析ProtocolHandler的init方法,其實現見代碼清單7。
代碼清單7
@Overridepublic void init() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.init", getName()));}if (oname == null) {// Component not pre-registered so register itoname = createObjectName();if (oname != null) {Registry.getRegistry(null, null).registerComponent(this, oname, null);}}if (this.domain != null) {rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());Registry.getRegistry(null, null).registerComponent(getHandler().getGlobal(), rgOname, null);}String endpointName = getName();endpoint.setName(endpointName.substring(1, endpointName.length()-1));endpoint.setDomain(domain);endpoint.init();}從代碼清單7看到,AbstractProtocol的初始化步驟如下:
步驟一?將PROTOCOLHANDLER注冊到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源碼分析——生命周期管理》一文。
AbstractProtocol的構造器中還設置了提高socket性能的tcpNoDelay等選項,見代碼清單8。
代碼清單8
public AbstractProtocol(AbstractEndpoint<S> endpoint) {this.endpoint = endpoint;//Deprecated
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);}
步驟二 初始化endpoint,代碼清單9是AbstractEndpoint類的init方法
代碼清單9
public void init() throws Exception {if (bindOnInit) {bind();bindState = BindState.BOUND_ON_INIT;}if (this.domain != null) {// Register endpoint (as ThreadPool - historical name)oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");Registry.getRegistry(null, null).registerComponent(this, oname, null);for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {registerJmx(sslHostConfig);}}}?
啟動PROTOCOLHANDLER
我們繼續以AbstractProtocol為例,剖析ProtocolHandler的start方法,其實現見代碼清單10。
代碼清單10
@Overridepublic void start() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.start", getName()));}endpoint.start();// Start async timeout threadasyncTimeout = new AsyncTimeout();Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");int priority = endpoint.getThreadPriority();if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {priority = Thread.NORM_PRIORITY;}timeoutThread.setPriority(priority);timeoutThread.setDaemon(true);timeoutThread.start();}?調用endpoint的start方法(見代碼清單11)接受請求的創建線程池并創建一定數量的接收請求線程。清單11是endpoint的start方法:
代碼清單11
//AbstractEndpoint.java public final void start() throws Exception {if (bindState == BindState.UNBOUND) {bind();bindState = BindState.BOUND_ON_START;}startInternal();//要調用子類NioEndpoint.java類的實現方法 }//NioEndpoint.java 代碼如下: @Overridepublic void startInternal() throws Exception {if (!running) {running = true;paused = false;processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());// Create worker collectionif ( getExecutor() == null ) {createExecutor();}initializeConnectionLatch();// Start poller threadspollers = new Poller[getPollerThreadCount()];for (int i=0; i<pollers.length; i++) {pollers[i] = new Poller();Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();}startAcceptorThreads();}}從代碼清單11看出NioEndpoint的start方法的執行步驟如下:
步驟一 創建線程池與任務隊列
如果NioEndpoint尚未處于運行中(即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);}protected final void startAcceptorThreads() {int count = getAcceptorThreadCount();acceptors = new Acceptor[count];for (int i = 0; i < count; i++) {acceptors[i] = createAcceptor();String threadName = getName() + "-Acceptor-" + i;acceptors[i].setThreadName(threadName);Thread t = new Thread(acceptors[i], threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}}
步驟二?創建接收請線程
如果NioEndpoint尚未處于運行中(即running等于true),才會創建接收請求線程。從代碼清單11可以看出接收請求線程的數量主要由acceptorThreadCount控制,代碼清單9已經告訴我們acceptorThreadCount的默認值為1,但是我們可以通過給Connector增加acceptorThreadCount屬性來修改接收請求線程的數量。這些接收請求線程的主要工作由Acceptor完成,Acceptor的實質是一個Runnable,見代碼清單13。
代碼清單13
public abstract static class Acceptor implements Runnable {public enum AcceptorState {NEW, RUNNING, PAUSED, ENDED}protected volatile AcceptorState state = AcceptorState.NEW;public final AcceptorState getState() {return state;}private String threadName;protected final void setThreadName(final String threadName) {this.threadName = threadName;}protected final String getThreadName() {return threadName;}}最后初始化MAPPERLISTENER
MapperListener繼承LifecycleMBeanBase,它的startInternal方法用于初始化,見代碼清單14。
代碼清單14
@Overridepublic void startInternal() throws LifecycleException {setState(LifecycleState.STARTING);Engine engine = service.getContainer();if (engine == null) {return;}findDefaultHost();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);}}}?
從代碼清單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 = service.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, service));}}?
?步驟二 將Host及其子容器Context,Context的子容器Wrapper注冊到MapperListener的Mapper對象
Mapper的數據結構,見代碼清單16。
代碼清單16public final class Mapper {private static final Log log = LogFactory.getLog(Mapper.class);private static final StringManager sm = StringManager.getManager(Mapper.class);// ----------------------------------------------------- Instance Variables/*** Array containing the virtual hosts definitions.*/// Package private to facilitate testingvolatile MappedHost[] hosts = new MappedHost[0];/*** Default host name.*/private String defaultHostName = null;private volatile MappedHost defaultHost = null;/*** Mapping from Context object to Context version to support* RequestDispatcher mappings.*/private final Map<Context, ContextVersion> contextObjectToContextVersionMap =new ConcurrentHashMap<>(); // ------------------------------------------------------- Host Inner Classprotected static final class MappedHost extends MapElement<Host> {public volatile ContextList contextList;/*** Link to the "real" MappedHost, shared by all aliases.*/private final MappedHost realHost;/*** Links to all registered aliases, for easy enumeration. This field* is available only in the "real" MappedHost. In an alias this field* is <code>null</code>.*/private final List<MappedHost> aliases;/*** Constructor used for the primary Host** @param name The name of the virtual host* @param host The host*/public MappedHost(String name, Host host) {super(name, host);realHost = this;contextList = new ContextList();aliases = new CopyOnWriteArrayList<>();}/*** Constructor used for an Alias** @param alias The alias of the virtual host* @param realHost The host the alias points to*/public MappedHost(String alias, MappedHost realHost) {super(alias, realHost.object);this.realHost = realHost;this.contextList = realHost.contextList;this.aliases = null;}public boolean isAlias() {return realHost != this;}public MappedHost getRealHost() {return realHost;}public String getRealHostName() {return realHost.name;}public Collection<MappedHost> getAliases() {return aliases;}public void addAlias(MappedHost alias) {aliases.add(alias);}public void addAliases(Collection<? extends MappedHost> c) {aliases.addAll(c);}public void removeAlias(MappedHost alias) {aliases.remove(alias);}}// ------------------------------------------------ ContextList Inner Classprotected static final class ContextList {public final MappedContext[] contexts;public final int nesting;public ContextList() {this(new MappedContext[0], 0);}private ContextList(MappedContext[] contexts, int nesting) {this.contexts = contexts;this.nesting = nesting;}public ContextList addContext(MappedContext mappedContext,int slashCount) {MappedContext[] newContexts = new MappedContext[contexts.length + 1];if (insertMap(contexts, newContexts, mappedContext)) {return new ContextList(newContexts, Math.max(nesting,slashCount));}return null;}public ContextList removeContext(String path) {MappedContext[] newContexts = new MappedContext[contexts.length - 1];if (removeMap(contexts, newContexts, path)) {int newNesting = 0;for (MappedContext context : newContexts) {newNesting = Math.max(newNesting, slashCount(context.name));}return new ContextList(newContexts, newNesting);}return null;}}// ---------------------------------------------------- Context Inner Classprotected static final class MappedContext extends MapElement<Void> {public volatile ContextVersion[] versions;public MappedContext(String name, ContextVersion firstVersion) {super(name, null);this.versions = new ContextVersion[] { firstVersion };}}protected static final class ContextVersion extends MapElement<Context> {public final String path;public final int slashCount;public final WebResourceRoot resources;public String[] welcomeResources;public MappedWrapper defaultWrapper = null;public MappedWrapper[] exactWrappers = new MappedWrapper[0];public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];public MappedWrapper[] extensionWrappers = new MappedWrapper[0];public int nesting = 0;private volatile boolean paused;public ContextVersion(String version, String path, int slashCount,Context context, WebResourceRoot resources,String[] welcomeResources) {super(version, context);this.path = path;this.slashCount = slashCount;this.resources = resources;this.welcomeResources = welcomeResources;}public boolean isPaused() {return paused;}public void markPaused() {paused = true;}}// ---------------------------------------------------- Wrapper Inner Classprotected static class MappedWrapper extends MapElement<Wrapper> {public final boolean jspWildCard;public final boolean resourceOnly;public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,boolean resourceOnly) {super(name, wrapper);this.jspWildCard = jspWildCard;this.resourceOnly = resourceOnly;}}
根據代碼清單16,我們知道Mapper中維護著一個MappedHost數組,每個MappedHost中有一個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 的裝載、初始化、執行以及資源回收。
?以我本地為例,在MapperListener類中的startInternal方法中的最后打個斷點,并運行debugger模式,就可以看到注冊到Mapper中的Host及其子容器,如圖2所示。
圖2 注冊到Mapper中的Host及其Context子容器
?圖2說明Host內一共5個Context,由于我的Tomcat是從svn拉下來的,所以webapps目錄下的.svn文件夾也是一個Context,除了這個天外來客,我將其它與請求有關的容器整理后用圖3來展示。
圖3 我本地的Host、Context及Wrapper
可以看docs這個context的所有屬性,如下圖:
至此,Tomcat中為請求處理的準備工作已經完成。有關請求的處理過程請繼續閱讀《Tomcat源碼分析——請求原理分析(中)》一文。
本文原創首發:博客園,原文鏈接:http://www.cnblogs.com/jiaan-geng/p/4875249.html
轉載于:https://www.cnblogs.com/nizuimeiabc1/p/8672723.html
總結
以上是生活随笔為你收集整理的tomcat8源码分析-Connector初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PYTHON自动化Day3-列表/元组/
- 下一篇: WPF数据验证方式