tomcat源碼分析06:請求處理流程(二)

注:本文源碼分析基於 tomcat 9.0.43,源碼的gitee倉庫倉庫地址:https://gitee.com/funcy/tomcat.

本文是tomcat源碼分析的第六篇,上一篇文章中我們提前,在Poller線程中,tomcat的會把連接請求會包裝爲SocketProcessorBase,然後丟到線程池中運行。這其中的運行過程是怎麼樣的呢,最終又是怎麼執行到servlet的?本文將爲你一一揭曉。

需要注意的是,從把請求丟到線程池到servlet的執行,其中包含的鏈路非常多,通過調試的方式得到的調用鏈路如下:

這其中包含了http協議的解析、servlet規範的實現,對於這些我們就簡單略過了,僅分析關鍵步驟。

1. 解析http協議:Http11Processor#service

tomcat解析http協議的方法爲Http11Processor#service,方法如下:

public SocketState service(SocketWrapperBase<?> socketWrapper)
    ...
    while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
            sendfileState == SendfileState.DONE && !protocol.isPaused()) {
        try {

            // inputBuffer.parseRequestLine():解析處理請求行
            if (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),
                    protocol.getKeepAliveTimeout())) {
                ...
            }

            // prepareRequestProtocol():處理 http 協議版本
            prepareRequestProtocol();
            if (protocol.isPaused()) {
                ...
            } else {
                keptAlive = true;
                ...
                // inputBuffer.parseHeaders():解析請求頭
                if (!http09 && !inputBuffer.parseHeaders()) {
                    ...
                }

            }
        } catch (...) {
            ...
        }
        ...
        if (getErrorState().isIoAllowed()) {
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                // 準備request請求
                prepareRequest();
            } catch (Throwable t) {
                ...
            }
        }
        ...
        if (getErrorState().isIoAllowed()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                // 處理讀事件
                getAdapter().service(request, response);
                ...
            } catch (...) {
                ...
            }
        }
        ...
    }
    ...
}

這個方法非常長,不過我已經精簡了許多,僅留下了關鍵方法,列舉如下:

  • 解析http請求行:inputBuffer.parseRequestLine()
  • 處理http協議版本:prepareRequestProtocol()
  • 解析http請求頭:inputBuffer.parseHeaders()
  • 準備http請求數據:prepareRequest()
  • 繼續處理請求:CoyoteAdapter#service(request, response)

上面4個方法看得讓我十分惆悵,如果不是對http的每個細節有深入瞭解,不建議研究那幾個方法,真的讓人頭大。

解析完http請求後,繼續調用getAdapter().service(request, response);處理之後的邏輯,也就是 CoyoteAdapter#service(request, response) 方法.

2. 生成httpServletRequest/httpServletResponseCoyoteAdapter#service(request, response)

httpServletRequest/httpServletResponse是在CoyoteAdapter#service(request, response)中生成的,代碼如下:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {

    // 獲取 httpServletRequest,得到的值爲null
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    // 獲取 httpServletResponse,得到的值爲null
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
        // 創建 request 對象
        request = connector.createRequest();
        request.setCoyoteRequest(req);

        // 創建 response 對象
        response = connector.createResponse();
        response.setCoyoteResponse(res);
        ...
    }

    ...

    try {
        // 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // 調用 container 的 valve 處理
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        ...

    } catch (IOException e) {
        // Ignore
    } finally {
        ...
    }
}

tomcat中,RequestResponse有兩種類型:

  • org.apache.coyote.Requestorg.apache.coyote.Response,tomcat 提供的,用來存放的http連接數據的
  • org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response,也是tomcat提供的,不過它分分別實現了HttpServletRequest/HttpServletResponse

這裏我們來看看org.apache.catalina.connector.Request 的創建過程:

request = connector.createRequest();

再進入Connector#createRequest方法:

public Request createRequest() {
    return new Request(this);
}

調用的是構造方法,繼續跟進去:

/**
 * 實現了 HttpServletRequest 
 */
public class Request implements HttpServletRequest {

    /** 連接器 */
    protected final Connector connector;

    /** 這是 `org.apache.coyote.Request`, 用來存放 http 請求連接的數據 */
    protected org.apache.coyote.Request coyoteRequest;

    ...

    public Request(Connector connector) {
        this.connector = connector;

        formats = new SimpleDateFormat[formatsTemplate.length];
        for(int i = 0; i < formats.length; i++) {
            formats[i] = (SimpleDateFormat) formatsTemplate[i].clone();
        }
    }

    /**
     * 部分 httpServletRequest 的方法如下:
     * 他們最終調用的是 coyoteRequest
     */

    @Override
    public String getMethod() {
        return coyoteRequest.method().toString();
    }

    @Override
    public String getRequestURI() {
        return coyoteRequest.requestURI().toString();
    }
    ...
}

到這裏,Request 只是創建了出來,並沒有做什麼實質性的工作,我們繼續看下去。

3. 解析reqeust:postParseRequest(...)

讓我們回到CoyoteAdapter#service()方法,創建完Request/Response,接下來就是把這個裏面裝東西了:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
    ...
    try {
        // 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        ...
    } 
    ...
}

處理參數解析的方法爲CoyoteAdapter#postParseRequest,裏面處理了servlet規範的一些參數,其實就是給httpServletRequest類型的Request進行賦值,它的代碼如下:

protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
        org.apache.coyote.Response res, Response response) throws IOException, ServletException {

    // 解析 scheme
    if (req.scheme().isNull()) {
        // 解析 scheme,設置 httServletRequest 類型的 request
        req.scheme().setString(connector.getScheme());
        request.setSecure(connector.getSecure());
    } else {
        request.setSecure(req.scheme().equals("https"));
    }

    // 處理代理
    String proxyName = connector.getProxyName();
    int proxyPort = connector.getProxyPort();
    if (proxyPort != 0) {
        req.setServerPort(proxyPort);
    } else if (req.getServerPort() == -1) {
        if (req.scheme().equals("https")) {
            req.setServerPort(443);
        } else {
            req.setServerPort(80);
        }
    }
    if (proxyName != null) {
        req.serverName().setString(proxyName);
    }

    MessageBytes undecodedURI = req.requestURI();
    // 處理請求方法
    if (undecodedURI.equals("*")) {
        if (req.method().equalsIgnoreCase("OPTIONS")) {
            StringBuilder allow = new StringBuilder();
            allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
            // Trace if allowed
            if (connector.getAllowTrace()) {
                allow.append(", TRACE");
            }
            res.setHeader("Allow", allow.toString());
            connector.getService().getContainer().logAccess(request, response, 0, true);
            return false;
        } else {
            response.sendError(400, "Invalid URI");
        }
    }

    // 其他的一些解析操作就不看了
    ...

    while (mapRequired) {
        // 解析servlet相關內容,如 Host, Context,Wrapper 等
        connector.getService().getMapper().map(serverName, decodedURI,
                version, request.getMappingData());

        ...
    }

    ...

    return true;
}

這個方法依舊十分長,所做的工作爲解析http請求的數據,將其轉換爲HttpServletRequest所需要的參數,對於裏面的一些細節就不分析了。

這個方法中調用了這樣一段代碼:

// 解析servlet相關內容,如 Host, Context,Wrapper 等
connector.getService().getMapper().map(serverName, decodedURI,
        version, request.getMappingData());

這段代碼最終調用的是Mapper#map(),內容如下:

public void map(MessageBytes host, MessageBytes uri, String version,
                MappingData mappingData) throws IOException {
    // 拿到了 host 與 uri
    if (host.isNull()) {
        String defaultHostName = this.defaultHostName;
        if (defaultHostName == null) {
            return;
        }
        host.getCharChunk().append(defaultHostName);
    }
    host.toChars();
    uri.toChars();
    // 處理映射
    internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}

這個方法拿到hosturi後,繼續請求Mapper#internalMap方法,在這個方法裏會處理http的請求路徑,也就是根據請求路徑找到最終執行的servlet,我們繼續。

4. 處理映射路徑:Mapper#internalMap

繼續跟進Mapper#internalMap方法:

private final void internalMap(CharChunk host, CharChunk uri,
        String version, MappingData mappingData) throws IOException {
    // 這一步相當於獲取到項目中所有的 Host
    MappedHost[] hosts = this.hosts;
    // 查找 host
    MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
    ...
    mappingData.host = mappedHost.object;

    if (uri.isNull()) {
        return;
    }

    uri.setLimit(-1);

    // 上面已經查找到了host,接下來就從該host下的所有context中查找
    ContextList contextList = mappedHost.contextList;
    MappedContext[] contexts = contextList.contexts;
    // 查找 context
    int pos = find(contexts, uri);
    if (pos == -1) {
        return;
    }
    ...

    mappingData.contextPath.setString(context.name);

    ContextVersion contextVersion = null;

    // 前面已經找到了 context,接下來就是從該context下所有的contextVersions中查找
    ContextVersion[] contextVersions = context.versions;
    final int versionCount = contextVersions.length;
    if (versionCount > 1) {
        Context[] contextObjects = new Context[contextVersions.length];
        for (int i = 0; i < contextObjects.length; i++) {
            contextObjects[i] = contextVersions[i].object;
        }
        mappingData.contexts = contextObjects;
        if (version != null) {
            // 根據版本號查找contextVersions
            contextVersion = exactFind(contextVersions, version);
        }
    }
    if (contextVersion == null) {
        contextVersion = contextVersions[versionCount - 1];
    }
    mappingData.context = contextVersion.object;
    mappingData.contextSlashCount = contextVersion.slashCount;

    if (!contextVersion.isPaused()) {
        // 處理 wrapper
        internalMapWrapper(contextVersion, uri, mappingData);
    }

}

需要注意的是,這裏的HostContext並不是前面提到的StandardHostStandardContext,而是MappedHostMappedContextMappedHost中存放的是一個個的MappedContextMappedContext中存放的是一個個ContextVersion

MappedHost內容如下::

protected static final class MappedHost extends MapElement<Host> {

    /** 存放一個個MappedContext的結構,繼續看下去 */
    public volatile ContextList contextList;

    ...
}

protected static final class ContextList {
    /** 用數組存放 MappedContext */
    public final MappedContext[] contexts;

    ...
}

MappedContext內容如下:

protected static final class MappedContext extends MapElement<Void> {
    /** 存放多個ContextVersion,使用數組存放 */
    public volatile ContextVersion[] versions;
    ...
}

4.1 查找 host

那麼他們是如何查找host的呢?在tomcat創建host時,默認爲設置一個hostname:

public class Tomcat {

    protected String hostname = "localhost";

    public Host getHost() {
        // 獲取 engine,不存在時會創建
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }
        // 創建 Host
        Host host = new StandardHost();
        host.setName(hostname);
        // 添加到 engine
        getEngine().addChild(host);
        return host;
    }

    ...
}

在http請求時,tomcat會解析傳入的host,當這兩者匹配時,就表示找到了對應的host,看下Mapper#exactFindIgnoreCase就明白了:

private static final <T, E extends MapElement<T>> E exactFindIgnoreCase(
        E[] map, CharChunk name) {
    // 查找map數組中與name相等或最接近的元素,返回下標
    int pos = findIgnoreCase(map, name);

    if (pos >= 0) {
        // 再一次判斷,因爲 findIgnoreCase(...) 返回的是與給定最接近或相等的下標
        E result = map[pos];
        if (name.equalsIgnoreCase(result.name)) {
            return result;
        }
    }
    return null;
}

4.2 查找 Context

Context又是如何查找的呢?在添加Context時,我們是這麼做的:

// 創建 context
String docBase = System.getProperty("java.io.tmpdir");
Context context = tomcat.addContext("", docBase);

tomcat.addContext(...)方法中,我們可以給Context指定一個請求路徑,像這樣:

Context context = tomcat.addContext("/api", docBase);

這樣就表示以api爲前綴的請求使用該Context處理,Mapper#internalMap方法在查找Context時,就是根據uri查找對匹配的Context的。

4.3 處理wrapper匹配:Mapper#internalMapWrapper

查找到Context後,接着就是處理Wrapper了,方法爲Mapper#internalMapWrapper

private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path,
        MappingData mappingData) throws IOException {

    // 獲取所有的 exactWrappers
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    // 進行查找操作
    internalMapExactWrapper(exactWrappers, path, mappingData);
    // 省略了很多的內容
    ...
}

這個方法也是非常長,處理了各種匹配規則,這裏我們僅看其中一個,瞭解下吧匹配流程吧,進入Mapper#internalMapExactWrapper方法:

private final void internalMapExactWrapper(MappedWrapper[] wrappers, 
        CharChunk path, MappingData mappingData) {
    // 路徑匹配,請求的uri是否匹配servlet path
    MappedWrapper wrapper = exactFind(wrappers, path);
    if (wrapper != null) {
        mappingData.requestPath.setString(wrapper.name);
        // 找到後賦值
        mappingData.wrapper = wrapper.object;
        if (path.equals("/")) {
            mappingData.pathInfo.setString("/");
            mappingData.wrapperPath.setString("");
            mappingData.contextPath.setString("");
            mappingData.matchType = MappingMatch.CONTEXT_ROOT;
        } else {
            mappingData.wrapperPath.setString(wrapper.name);
            mappingData.matchType = MappingMatch.EXACT;
        }
    }
}

這裏的path就是uri的路徑,這一步是根據路徑來判斷是否匹配的,即判斷傳入的uriservlet path是否切爾西,匹配成功後,就賦值給mappingDatawrapper屬性了。

至些,請求對應的hostcontextwrapepr就都找到了。

5. 調用XxxValve

讓我們回到CoyoteAdapter#service方法:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {
    ...

    try {
        // 處理參數,指 servlet 規範的一些參數,如果請求方法,sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // 調用 container 的 valve 處理
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
        }
        ...

    } catch (IOException e) {
        // Ignore
    } finally {
        ...
    }
}

在解析完http參數、請求對應的servlet後,接着就開始調用containerPipeline 處理了:

// 調用 container 的 valve 處理
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

整個pipeline結構如下:

執行時就是這樣一個接一個的Pipeline調用。Pipeline裏存放的是什麼呢?這就是所謂有ValveStandardEngineStandardHostStandardContextStandardWrapper等都着對應的Valve

5.1 StandardEngineValve#invoke

我們跟進connector.getService().getContainer().getPipeline().getFirst().invoke(request, response),由於connector.getService().getContainer()得到的是StandardEngine,因此運行的是StandardEngineValve#invoke方法:

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // 使用對應的host來處理
    Host host = request.getHost();
    ...
    // 調用 host 的 valve 處理
    host.getPipeline().getFirst().invoke(request, response);
}

該方法關鍵代碼有兩個:

  • request.getHost():獲取該請求對應的host,這個就是前面Mapper#internalMap千辛萬苦找到的與該請求對應的host
  • host.getPipeline().getFirst().invoke(request, response):繼續調用StandardHostValve#invoke方法

5.2 StandardHostValve#invoke

我們再進入StandardHostValve#invoke方法:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {
    // 從 request 中得到使用的 context
    Context context = request.getContext();
    ...

    try {
        
        ...

        try {
            if (!response.isErrorReportRequired()) {
                // 調用 context 的 valve 處理
                context.getPipeline().getFirst().invoke(request, response);
            }
        } catch (Throwable t) {
            ...
        }

    } finally {
        ...
    }

這個方法的調用套路與StandardEngineValve#invoke是一樣的:

  • request.getContext():從request中得到該請求對應的context,這個也是在Mapper#internalMap方法找到的
  • context.getPipeline().getFirst().invoke(request, response)繼續調用StandardContextValve#invoke處理

5.3 StandardContextValve#invoke

StandardContextValve#invoke方法如下:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    ...

    // 從 request 中拿到 wrapper
    Wrapper wrapper = request.getWrapper();
    ...
    // 調用 wrapper 的 valve 處理
    wrapper.getPipeline().getFirst().invoke(request, response);
}

嗯,還是與StandardEngineValve#invoke方法一樣的套路,我們繼續看StandardWrapperValve#invoke方法。

6. StandardWrapperValve#invoke

我們再來看看Pipeline調用圖:

從調用上來看,StandardWrapperValve已經調用到底了,servlet的調用就是在這裏發生的,該方法代碼如下:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

    ...

    try {
        if (!unavailable) {
            // 獲取 servlet,如果實例存在,直接返回,否則就創建實例並調用 Servlet#init 方法
            servlet = wrapper.allocate();
        }
    } catch (...) {
        ...
    }

    ...

    // 創建過濾器鏈
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    Container container = this.container;
    try {
        if ((servlet != null) && (filterChain != null)) {
            // 在這裏調用 filterChain.doFilter
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 調用 filter
                        filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                    }
                } finally {
                    String log = SystemLogHandler.stopCapture();
                    if (log != null && log.length() > 0) {
                        context.getLogger().info(log);
                    }
                }
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else {
                    // 調用 filter
                    filterChain.doFilter
                        (request.getRequest(), response.getResponse());
                }
            }

        }
    } catch (...) {
        ...
    } finally {
        ...
    }
}

這個方法依舊比較長,不過非關鍵點我都已經取消了,只留下了三個關鍵操作:

  • servlet = wrapper.allocate():獲取servlet,如果servletloadOnStart小於0,那麼在這裏纔會實例化並調用Servlet#init方法
  • ApplicationFilterFactory.createFilterChain(...):創建過濾器鏈
  • filterChain.doFilter(...):調用過濾器操作

接下來我們就來分析這幾個方法。

6.1 StandardWrapper#allocate

在前面分析servlet的加載時,對於loadOnStartup小於0的servlet並沒有處理(沒有實例化,沒有執行servlet#init方法),這個方法裏會實例化loadOnStartup小於0的servlet的,代碼如下:

public Servlet allocate() throws ServletException {

    ...

    boolean newInstance = false;

    // If not SingleThreadedModel, return the same instance every time
    if (!singleThreadModel) {
        // 實例不存在且未初始化
        if (instance == null || !instanceInitialized) {
            synchronized (this) {
                if (instance == null) {
                    try {
                        ...
                        // 調用 StandardWrapper#loadServlet 方法
                        instance = loadServlet();
                        newInstance = true;
                        ...
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e);
                    }
                }
                if (!instanceInitialized) {
                    initServlet(instance);
                }
            }
        }

        ...
    }
    ...
}

這個先判斷實例是否存在,是否進行進行過初始化操作,如果滿足條件則調用StandardWrapper#loadServlet方法,關於這個方法前面的文章中已經分析過了,它所做的主要工作就兩個:

  • 實例化servlet
  • 調用Servlet#init(...)方法

6.2 ApplicationFilterFactory#createFilterChain

ApplicationFilterFactory#createFilterChain方法所做的是創建過濾器鏈,代碼如下:

public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {

    if (servlet == null)
        return null;

    ApplicationFilterChain filterChain = null;
    // 創建 filterChain 對象
    if (request instanceof Request) {
        Request req = (Request) request;
        if (Globals.IS_SECURITY_ENABLED) {
            filterChain = new ApplicationFilterChain();
        } else {
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = new ApplicationFilterChain();
                req.setFilterChain(filterChain);
            }
        }
    } else {
        filterChain = new ApplicationFilterChain();
    }

    filterChain.setServlet(servlet);
    filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

    // 拿到 warpper 對應的 Context,然後從 context中拿到 filterMaps
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    ...

    // 添加滿足servlet路徑的filter到過濾器鏈
    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        // 判斷哪些filter滿足servlet的路徑
        if (!matchFiltersURL(filterMap, requestPath))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }

    // 添加滿足servlet名稱的filter到過濾器鏈
    for (FilterMap filterMap : filterMaps) {
        if (!matchDispatcher(filterMap, dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMap, servletName))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    return filterChain;
}

ApplicationFilterFactory#createFilterChain方法所略長,不過邏輯相當清晰,它主要是用來創建過濾器鏈的,流程如下:

  1. 創建ApplicationFilterChain對象
  2. 獲取當前wrapper對應的Context,然後從Context拿到filterMaps,在Context中有一個結構專門用來存放filter的,關於filter的加載,前面的文章也分析了,這裏就不再分析了
  3. 查找滿足條件的filter,然後添加到filterChain中,注意,這裏滿足條件的filter是指滿足當前servletfilter,處理匹配條件時,是通過servlet路徑與servlet名稱進行匹配的,兩者滿足其一即可。

6.3 ApplicationFilterChain#doFilter

獲取到過濾器鏈後,接下來就是執行了,方法爲ApplicationFilterChain#doFilter

public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if( Globals.IS_SECURITY_ENABLED ) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        try {
            java.security.AccessController.doPrivileged(
                    (java.security.PrivilegedExceptionAction<Void>) () -> {
                        internalDoFilter(req,res);
                        return null;
                    }
            );
        } catch( PrivilegedActionException pe) {
            ...
        }
    } else {
        // 執行過濾器
        internalDoFilter(request,response);
    }
}

調用了internalDoFilter(...)執行,繼續跟進去:

/** 當前正在使用的過濾器的下標索引 */
private int pos = 0;

/** 當前過濾器鏈中過濾器的數量 */
private int n = 0;

/** 存放過濾器的地方 */
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

/**
 * 執行過濾器
 */
private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // 判斷是否執行完了
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            // 獲取要執行的 filter
            Filter filter = filterConfig.getFilter();

            ...

            // 執行 filter的doFilter(...) 方法
            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res, this};
                SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
            } else {
                filter.doFilter(request, response, this);
            }
        } catch (...) {
            ...
        }
        return;
    }

    try {
        ...
        // 調用 servlet#service 方法
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            // 執行 servlet#service 方法
            servlet.service(request, response);
        }
    } catch (...) {
        ...
    }
}

internalDoFilter(...)分爲兩個部分:

  1. 執行filter.doFilter(...)方法
  2. 執行servlet.service(...)方法

ApplicationFilterChain中有兩個成員變量:

  • n:當前過濾器鏈中過濾器的數量
  • post:當前使用的過濾器的下標索引

在過濾器的調用時,會根據這兩個參數的大小判斷要不要進入過濾器的調用:

 // 判斷是否執行完了
if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
        // 獲取要執行的 filter
        Filter filter = filterConfig.getFilter();

        ...
        // 執行 filter的doFilter(...) 方法
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();

            Object[] args = new Object[]{req, res, this};
            SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
        } else {
            filter.doFilter(request, response, this);
        }
    } catch (...) {
        ...
    }
    return;
}

這個filter.doFilter(request, response, this)就是我們真正調用Filter的代碼了。

執行完當前的filter後,是怎麼執行到下一個的呢?我們在實現Filter時,都會有這麼一句代碼:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
    // 你的業務邏輯
    ...
    // 調用 FilterChain#doFilter 方法
    chain.doFilter(request, response)

    // 你的業務邏輯
    ...
}

tomcat中,FilterChain#doFilter調用的就是ApplicationFilterChain#doFilter方法,接着又到了ApplicationFilterChain#internalDoFilter方法,這次調用時,pos的值會加1,然後繼續調用Filter#doFilter方法,之後又調回ApplicationFilterChain#doFilter方法...如此循環往復的方法,在設計模式中,有一個專業的名字:責任鏈模式

每一次調用ApplicationFilterChain#doFilter方法,pos的值就會加1,直到pos >= n後,filter就全部執行完了,接着就開始執行servlet了:

private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // 判斷是否執行完了
    if (pos < n) {
        // 執行 filter
        ...
    }

    try {
        ...
        // 調用 servlet#service 方法
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            // 執行 servlet#service 方法
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            // 執行 servlet#service 方法
            servlet.service(request, response);
        }
    } catch (...) {
        ...
    }
}

關於servlet的執行,其實非常簡單:前面已經獲取到了servlet的實例,這裏就直接調用其service()方法即可。

7. 總結

本文主要分析了tomcat線程池處理請求的整個過程,包括http協議的解析、httpServletRequest/httpServletResponse的創建及準備、解析映射路徑(查找對應的hostcontextwrapper)、執行filterservlet,不過對於http的解析、servlet規範的實現,本文只是點出了處理的方法,並沒有深入到這些方法的細節。


限於作者個人水平,文中難免有錯誤之處,歡迎指正!原創不易,商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

本文首發於微信公衆號 Java技術探祕,鏈接:https://mp.weixin.qq.com/s/o6ZKNqdcdB89EP0JEnTsPg. 如果您喜歡本文,歡迎關注該公衆號,讓我們一起在技術的世界裏探祕吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章