Connector 用於接收請求並將請求封裝成Request 和Response 來具體處理,最底層是使用Socket 來進行鏈接的, Request 和Response 是按照HTTP 協議來封裝的,因此Connector 同時實現了TCP/IP 協議和HTTP 協議, Request 和Response 封裝完以後交給Container 進行處理,Container 就是Servlet 的容器, Container 處理完以後返回給Connector,最後Connector 使用Socket 將處理結果返回給客戶端,這樣整個請求就處理完了。前端
Connector主要包含三個模塊:Http11NioProtocol(Endpoint+processor)
, Mapper
, CoyoteAdapter
,http請求在Connector中的流程以下java
ProtocolHandler 裏面有2 個很是重要的組件: Endpoint 、Processor。apache
Endpoint:用於處理底層Socket 的網絡鏈接。
Processor:用於將Endpoint 接收到的Socket 封裝成Request。設計模式
也就是說Endpoint用來實現TCP/IP 協議, Processor 用來實現HTTP 協議。數組
Endpoint 的抽象實現AbstractEndpoint 裏面定義的Acceptor 和AsyncTimeout 兩個內部類和一個Handler 接口。Acceptor 用於監昕請求, AsyncTimeout 用於檢查異步request 的超時,Handler 用於處理接收到的Socket,在內部調用了Processor 進行處理。tomcat
Connector 類自己的做用主要是在其建立時建立ProtocolHandler,而後在生命週期的相關方法中調用了ProtocolHandler 的相關生命週期方法。服務器
Connector 的使用方法是經過Connector 標籤配置在conf/server.xml 文件中,因此Connector 是在Catalina 的load 方法中根據conf/server.xml 配置文件建立Server對象時建立的。Connector 的生命週期方法是在Service 中調用的。以下:網絡
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
</Service>
複製代碼
Connector 的建立過程主要是初始化ProtocolHandler。server.xrnl 配置文件中Connector 標籤的protocol 屬性會設置到Connector 構造函數的參數中,它用於指定ProtocolHandler 的類型,Connector 的構造函數代碼以下:app
public Connector(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
}
} else {
protocolHandlerClassName = protocol;
}
// Instantiate protocol handler
ProtocolHandler 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;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
複製代碼
Apr 是Apache Portable Runtime 的縮寫,是Apache 提供的一個運行時環境,若是要使用Apr 須要先安裝,安裝後Tomcat 能夠本身檢測出來。若是安裝了Apr, 方法會根據配置的HTTP/1.1 屬性對應地將protocolHandlerClassName 設置爲org.apache.coyote.http11.Http11.AprProtocol ,若是沒有安裝Apr,會根據配置的HTTP/1.1 屬性將protocoHandlerClassName設置爲com..apache.coyote.http11.Http11NioProtocol,而後就會根據protocolHandlerClassName 來建立ProtocolHandler。異步
ProtocolHandler 有一個抽象實現類AbstractProtocol, AbstractProtocol 下面分了三種類型: Ajp 、HTTP 和Spdy 。
Ajp是Apache JServ Protocol 的縮寫, Apache 的定向包協議,主要用於與前端服務器(如Apache )進行通訊,它是長鏈接,不須要每次通訊都從新創建鏈接,這樣就節省了開銷; Spdy 協議Google開發的協議,做用相似HTTP ,比HTTP 效率高,不過這只是Google 制定的企業級協議,使用並不普遍,並且在HTTP/2 協議中已經包含了Spdy 所提供的優點,因此Spdy 協議日常不多使用,不過Tomcat 提供了支持。
以默認配置中的Http11NioProtocol爲例來分析,它使用HTTP11協議, TCP 層使用NioSocket 來傳輸數據。構造方法以下:
public Http11NioProtocol() { super(new NioEndpoint());}
複製代碼
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
複製代碼
可見在構造方法中建立了endpoint。
Endpoint 用於處理具體鏈接和傳輸數據, NioEndpoint 繼承自org.apache.tomcat. util.net.AbstractEndpoint,在NioEndpoint 中新增了Poller 和SocketProcessor 內部類, NioEndpoint 中處理請求的具體流程如圖:
其主要調用bind()方法進行初始化:
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
selectorPool.open(getName());
}
複製代碼
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {
// 初始化socket
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
...
}
serverSock.configureBlocking(true); //mimic APR behavior
}
複製代碼
初始化主要是創建socket鏈接,接着看生命週期的startInternal方法:
public void startInternal() throws Exception {
// 建立socketProcessor
if (socketProperties.getProcessorCache() != 0) {
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
}
if (socketProperties.getEventCache() != 0) {
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
}
if (socketProperties.getBufferPool() != 0) {
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
}
...
// 啓動poller線程
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
複製代碼
能夠發現它建立了一個Poller以及啓動Poller線程與Acceptor線程來處理請求。同時它還初始化了SocketProcessor,這是它的內部類,Poller收到請求後就會交由它處理。而SocketProcessor又會將請求傳遞到Handler,以下:
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
複製代碼
最後由handler將請求傳遞給processor。
Processor的結構以下:
當Handler創建好socket鏈接後,請求將會交由Processor處理,AbstractProcessor構造方法以下:
protected AbstractProcessor(Adapter adapter, Request coyoteRequest, Response coyoteResponse) {
this.adapter = adapter;
asyncStateMachine = new AsyncStateMachine(this);
request = coyoteRequest;
response = coyoteResponse;
response.setHook(this);
request.setResponse(response);
request.setHook(this);
userDataHelper = new UserDataHelper(getLog());
}
複製代碼
以後它將調用Adapter 將請求傳遞到Container 中,最後對處理的結果進行了處理,若有沒有啓動異步處理、處理過程巾有沒有拋出異常等。主要體如今Service方法中:
public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException {
...
// Process the request in the adapter
if (getErrorState().isIoAllowed()) {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
getAdapter().service(request, response);
...
}
// Finish the handling of the request
...
}
複製代碼
Adapter 只有一個實現類,那就是org.apache.catalina.connector 包下的CoyoteAdapter 類。
Processor 在其process 方法中會調用Adapter 的service 方法來處理請求, Adapter 的service 方法主要是調用Container 管道中的invoke方法來處理請求,在處理以前對Request和Response作了處理,將原來建立的org.apache.coyote 包下的Request 和Response 封裝成了org.apache.catal ina.connector 的Request 和Response ,並在處理完成後判斷再啓動了Comet(長鏈接推模式)和是否啓動了異步請求,並做出相應處理。調用Container 管道的相應代碼片斷以下:
connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);
複製代碼
咱們知道,tomcat有四個Container,採用了責任鏈的設計模式,每個Container定義了一個Pipeline,每個Pipeline又定義了多個Valve,表明須要處理的任務。Pipeline就像是每一個容器的邏輯總線,在Pipeline上按照配置的順序,加載各個Valve。經過Pipeline完成各個Valve之間的調用,各個Valve實現具體的應用邏輯。
有了責任鏈設計模式的概念,http請求由Connector轉發至Container,在Container中的流程就比較清晰了,以下:
在Tomcat中,當一個請求到達時,該請求最終由哪一個Servlet來處理是靠Mapper路由映射器完成的。Mapper由Service管理。
protected abstract static class MapElement<T> {
public final String name; // 名字
public final T object; // 對應的對象,如host, context, wrapper
}
複製代碼
protected static final class MappedHost extends MapElement<Host> {
// host包含的context列表,即MappedContext數組的包裝
public volatile ContextList contextList;
}
複製代碼
protected static final class MappedContext extends MapElement<Void> {
// 一個Context可能會對應許多個不一樣的版本的context,通常狀況下是1個
public volatile ContextVersion[] versions;
}
複製代碼
其中ContextVersion包含了Context下的全部Servlet,有多種映射方式,如精確的map,通配符的map,擴展名的map,以下:
protected static final class ContextVersion extends MapElement<Context> {
// 對wrapper的精確的map
public MappedWrapper[] exactWrappers = new MappedWrapper[0];
// 基於通配符的map
public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
// 基於擴展名的map
public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
}
複製代碼
protected static class MappedWrapper extends MapElement<Wrapper> {
public final boolean jspWildCard;
public final boolean resourceOnly;
}
複製代碼
public final class Mapper {
// host數組,host裏面又包括了context和wrapper數組
volatile MappedHost[] hosts = new MappedHost[0];
// 下面三個是添加host, context, wrapper的函數,都是同步的
// 並且保證添加後是有序的
public synchronized void addHost(String name, String[] aliases, Host host) { }
public void addContextVersion(String hostName, Host host, String path, String version, Context context, String[] welcomeResources, WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
}
protected void addWrapper(ContextVersion context, String path, Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
}
// 根據name,查找一個MapElement(host, context, 或者wrapper)
private static final <T> int find(MapElement<T>[] map, CharChunk name, int start, int end) {
// ...省略
// 核心是二分法
while (true) {
i = (b + a) >>> 1;
int result = compare(name, start, end, map[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, start, end, map[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
}
複製代碼
下面介紹在一次http請求中,哪一個環節調用了Mapper做路由映射,已經路由映射的過程。
http請求通過http11Processor
解析以後,會調用CoyoteAdapter
的service()
函數轉發給Container
,咱們來詳細看一下這一步。
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
...
try {
// 解析並配置請求參數
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// 調用container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
}catch{
....
}
...
}
複製代碼
咱們進入postParseRequest方法:
protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws IOException, ServletException {
...
while (mapRequired) {
// This will map the the latest version by default
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
...
}
...
}
複製代碼
CoyoteAdapter
會獲取Connector
中的Service
中的Mapper
,而後調用map()
方法。
public final class Mapper {
public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {
...
// 調用私有方法internalMap,傳入host, uri, version, 結果將會保存在mappingData
internalMap(host.getCharChunk(), uri.getCharChunk(), version,
mappingData);
}
private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws IOException {
...
// 查找映射的host
MappedHost[] hosts = this.hosts;
// 跟mapper的find方法同樣,採用二分法查找
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
mappingData.host = mappedHost.object;
...
// 查找映射的context
contextVersion = exactFind(contextVersions, version);
// 查找映射的wrapper
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
}
}
複製代碼