Tomcat源碼學習之Connector(二)

Connector

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

Connector 中具體是用ProtocolHandler 來處理請求的,不一樣的ProtocolHandler 表明不一樣的鏈接類型,好比, Http11Protocol 使用的是普通Socket 來鏈接的, Http 11 NioProtocol 使用的是NioSocket 來鏈接的。

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類

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的建立

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

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

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

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
    ...
}
複製代碼

Adaper

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中的流程就比較清晰了,以下:

最終將Response返回給Connector完成一次http的請求。

Mapper

在Tomcat中,當一個請求到達時,該請求最終由哪一個Servlet來處理是靠Mapper路由映射器完成的。Mapper由Service管理。

存儲結構

MapElement

protected abstract static class MapElement<T> {

    public final String name;   // 名字

    public final T object;      // 對應的對象,如host, context, wrapper

}
複製代碼

MappedHost

protected static final class MappedHost extends MapElement<Host> {

    // host包含的context列表,即MappedContext數組的包裝
    public volatile ContextList contextList;
}
複製代碼

MappedContext

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];
}
複製代碼

MappedWrapper

protected static class MappedWrapper extends MapElement<Wrapper> {
    public final boolean jspWildCard;
    public final boolean resourceOnly;
}
複製代碼

Mapper類

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解析以後,會調用CoyoteAdapterservice()函數轉發給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);
        }
    }
}
複製代碼
相關文章
相關標籤/搜索