深入理解 Tomcat(九)源碼剖析之請(qǐng)求過程


前言

不知不覺锭沟,這已經(jīng)是我們深入理解tomcat的第九篇文章了,我們?cè)诘诎似治隽藅omcat的連接器识补,分析了連接器的的Connector,Http11Protocol凭涂,Http11ConnectionHandler巢株,JIoEndpoint槐瑞,Acceptor 等等這些有關(guān)連接器的類和組件,當(dāng)時(shí)我們分析到Acceptor的run方法后就停止分析了阁苞,因?yàn)楹竺娴拇a與請(qǐng)求過程高度相關(guān)困檩,而且請(qǐng)求過程這段代碼時(shí)比較復(fù)雜的,需要很大的篇幅去講述那槽。廢話不多說悼沿,今天我們就開始分析 http://localhost:8080 在tomcat中是如何運(yùn)作的,是如何到達(dá)的Servlet的骚灸。

序列圖

首先來一張樓主畫的序列圖糟趾,然后,我們這篇文章基本就按照我們的這張圖來講述了。這張圖有47個(gè)層次的調(diào)用义郑,上傳到簡(jiǎn)書就變模糊了蝶柿,因此樓主將圖片放到了github上,大家可以看的清楚一點(diǎn)非驮。

時(shí)序圖-點(diǎn)擊查看


樓主這次分析只锭,會(huì)先啟動(dòng)tomcat, 然后在

1. JIoEndpoint 分析

上次我們分析連接器的時(shí)候提到了一個(gè)干實(shí)事不摸魚的好員工院尔,JIoEndpoint蜻展,該類在創(chuàng)建Http11Protocol對(duì)象的時(shí)候會(huì)一起被創(chuàng)建,可以說他們是依賴關(guān)系邀摆。并且 JIoEndpoint 也包含一個(gè) Http11ConnectionHandler 協(xié)議連接處理器類纵顾,該類是 Http11Protocol 的靜態(tài)內(nèi)部類。而 Http11ConnectionHandler 由依賴 Http11Protocol栋盹,可以說三者是一種循環(huán)的關(guān)系施逾,Http11Protocol 依賴著 JIoEndpoint,Http11ConnectionHandler 依賴著 Http11Protocol 例获,JIoEndpoint 依賴著 Http11ConnectionHandler 汉额。而處理 HTTP BIO 模式的連接主要由 JIoEndpoint 和它的兩個(gè)(共4個(gè)內(nèi)部類和一個(gè)內(nèi)部接口)內(nèi)部類 Acceptor 和 SocketProcessor 完成的。下面是JIoEndpoint 的類結(jié)構(gòu)圖:

Acceptor 我們?cè)谏洗畏治鲞B接器的時(shí)候已經(jīng)分析過了榨汤,它其實(shí)是用于接收HTTP 請(qǐng)求的線程蠕搜。用于從 SocketServer 接受請(qǐng)求。

SocketProcessor 則是我們今天分析的源頭收壕,因?yàn)閺奈覀兊牡诎似恼轮兄兰斯啵珹cceptor 將請(qǐng)求處理之后會(huì)交給 SocketProcessor 進(jìn)行真正的處理。那么我們就來分析分析該類蜜宪。

2. SocketProcessor 分析

首先該類是一個(gè)繼承了 Runnable 的內(nèi)部類虫埂,還記得在連接器啟動(dòng)的時(shí)候,會(huì)啟動(dòng)一個(gè)線程池圃验,該連接池就是用于執(zhí)行該線程的掉伏。那么我們就看看該類的實(shí)現(xiàn):

 /**
     * This class is the equivalent of the Worker, but will simply use in an
     * external Executor thread pool.
     *
     * 這個(gè)類相當(dāng)于工作人員,但是只會(huì)在一個(gè)外部執(zhí)行器線程池中使用
     */
    protected class SocketProcessor implements Runnable {

        protected SocketWrapper<Socket> socket = null;
        protected SocketStatus status = null;

        public SocketProcessor(SocketWrapper<Socket> socket) {
            if (socket==null) throw new NullPointerException();
            this.socket = socket;
        }

        public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
            this(socket);
            this.status = status;
        }

        @Override
        public void run() {
            boolean launch = false;
            synchronized (socket) {
                try {
                    SocketState state = SocketState.OPEN;

                    try {
                        // SSL handshake
                        serverSocketFactory.handshake(socket.getSocket()); // 什么都不做
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("endpoint.err.handshake"), t);
                        }
                        // Tell to close the socket
                        state = SocketState.CLOSED;
                    }

                    if ((state != SocketState.CLOSED)) {// open
                        if (status == null) { // status == null
                            state = handler.process(socket, SocketStatus.OPEN);// AbstractProtocol.process(); state 變?yōu)?close
                        } else { // handler == Http11Protocol$Http11ConnectionHandler
                            state = handler.process(socket,status); // state = closed
                        }
                    }
                    if (state == SocketState.CLOSED) {
                        // Close socket
                        if (log.isTraceEnabled()) {
                            log.trace("Closing socket:"+socket);
                        }
                        countDownConnection();// 進(jìn)入該方法
                        try {
                            socket.getSocket().close(); // 關(guān)閉流
                        } catch (IOException e) {
                            // Ignore
                        }
                    } else if (state == SocketState.OPEN ||
                            state == SocketState.UPGRADING  ||
                            state == SocketState.UPGRADED){
                        socket.setKeptAlive(true);
                        socket.access();
                        launch = true; // 此時(shí)才走finally try 邏輯
                    } else if (state == SocketState.LONG) {
                        socket.access();
                        waitingRequests.add(socket);// 長(zhǎng)連接澳窑,
                    }
                } finally {
                    if (launch) {
                        try {
                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
                        } catch (RejectedExecutionException x) {
                            log.warn("Socket reprocessing request was rejected for:"+socket,x);
                            try {
                                //unable to handle connection at this time
                                handler.process(socket, SocketStatus.DISCONNECT);
                            } finally {
                                countDownConnection();
                            }


                        } catch (NullPointerException npe) {
                            if (running) {
                                log.error(sm.getString("endpoint.launch.fail"),
                                        npe);
                            }
                        }
                    }
                }
            }
            socket = null; // 完成請(qǐng)求
            // Finish up this request
        }

    }

首先該類由2個(gè)屬性斧散,一個(gè)是 SocketWrapper<Socket> ,看名字就知道他其實(shí)就是 Socket 的包裝類照捡,另一個(gè)是 SocketStatus颅湘, 也就是 Socket 的狀態(tài)。我們看看該類的 run 方法的執(zhí)行邏輯:

  1. 首先處理socket的SSL栗精。實(shí)際上 DefaultServerSocketFactory(也就是我們默認(rèn)的) 是空的,什么都不做。
  2. 判斷socket狀態(tài)悲立,如果是null鹿寨,則設(shè)置為open,執(zhí)行 Http11ConnectionHandler 的 process 方法薪夕。
  3. 執(zhí)行 Http11ConnectionHandler 的 process 方法會(huì)返回一個(gè) SocketState脚草,后面會(huì)根據(jù)該狀態(tài)執(zhí)行不同的邏輯,如果是關(guān)閉原献,則減去一個(gè)連接數(shù)馏慨,并且關(guān)閉流。如果是開或則升級(jí)狀態(tài)姑隅,則進(jìn)入finally塊繼續(xù)交給線程池執(zhí)行写隶。如果是長(zhǎng)連接,則放入ConcurrentLinkedQueue 隊(duì)列讲仰,供另一個(gè)線程 AsyncTimeout 執(zhí)行(最后還是交給 SocketProcessor 執(zhí)行 )慕趴;

可以看到這個(gè)方法不是很復(fù)雜,并且我們能感覺到主要邏輯會(huì)在第二步鄙陡,因此我們就進(jìn)入到第二步的 process 方法中查看冕房。

3. Http11ConnectionHandler process 方法剖析

進(jìn)入handler 的process 方法,實(shí)際上是進(jìn)入了 Http11ConnectionHandler 的父類 AbstractConnectionHandler 的 process 方法趁矾,該方法是個(gè)模板方法耙册,讓我們看看該方法:

      public SocketState process(SocketWrapper<S> socket,
                SocketStatus status) {
            Processor<S> processor = connections.remove(socket.getSocket()); // connections 是用于緩存長(zhǎng)連接的socket

            if (status == SocketStatus.DISCONNECT && processor == null) { // 如果是斷開連接狀態(tài)且協(xié)議處理器為null
                //nothing more to be done endpoint requested a close
                //and there are no object associated with this connection
                return SocketState.CLOSED;
            }

            socket.setAsync(false);// 非異步

            try {
                if (processor == null) {
                    processor = recycledProcessors.poll();// 如果從緩存中沒取到,從可以循環(huán)使用的 ConcurrentLinkedQueue 獲取
                }
                if (processor == null) {
                    processor = createProcessor(); // 如果還沒有毫捣,則創(chuàng)建一個(gè)
                }

                initSsl(socket, processor); // 設(shè)置SSL 屬性觅玻,默認(rèn)為null,可以配置

                SocketState state = SocketState.CLOSED;
                do {
                    if (status == SocketStatus.DISCONNECT &&
                            !processor.isComet()) {
                        // Do nothing here, just wait for it to get recycled
                        // Don't do this for Comet we need to generate an end
                        // event (see BZ 54022)
                    } else if (processor.isAsync() ||
                            state == SocketState.ASYNC_END) {
                        state = processor.asyncDispatch(status); // 如果是異步的
                    } else if (processor.isComet()) {
                        state = processor.event(status); // 事件驅(qū)動(dòng)培漏?溪厘??
                    } else if (processor.isUpgrade()) {
                        state = processor.upgradeDispatch(); // 升級(jí)轉(zhuǎn)發(fā)牌柄?畸悬??
                    } else {
                        state = processor.process(socket); // 默認(rèn)的 AbstractHttp11Processor.process
                    }
    
                    if (state != SocketState.CLOSED && processor.isAsync()) {
                        state = processor.asyncPostProcess();
                    }

                    if (state == SocketState.UPGRADING) {
                        // Get the UpgradeInbound handler
                        UpgradeInbound inbound = processor.getUpgradeInbound();
                        // Release the Http11 processor to be re-used
                        release(socket, processor, false, false);
                        // Create the light-weight upgrade processor
                        processor = createUpgradeProcessor(socket, inbound);
                        inbound.onUpgradeComplete();
                    }
                } while (state == SocketState.ASYNC_END ||
                        state == SocketState.UPGRADING);

                if (state == SocketState.LONG) {
                    // In the middle of processing a request/response. Keep the
                    // socket associated with the processor. Exact requirements
                    // depend on type of long poll
                    longPoll(socket, processor);
                } else if (state == SocketState.OPEN) {
                    // In keep-alive but between requests. OK to recycle
                    // processor. Continue to poll for the next request.
                    release(socket, processor, false, true);
                } else if (state == SocketState.SENDFILE) {
                    // Sendfile in progress. If it fails, the socket will be
                    // closed. If it works, the socket will be re-added to the
                    // poller
                    release(socket, processor, false, false);
                } else if (state == SocketState.UPGRADED) {
                    // Need to keep the connection associated with the processor
                    longPoll(socket, processor);
                } else {
                    // Connection closed. OK to recycle the processor.
                    if (!(processor instanceof UpgradeProcessor)) {
                        release(socket, processor, true, false);
                    }
                }
                return state;
            } catch(java.net.SocketException e) {
                // SocketExceptions are normal
                getLog().debug(sm.getString(
                        "abstractConnectionHandler.socketexception.debug"), e);
            } catch (java.io.IOException e) {
                // IOExceptions are normal
                getLog().debug(sm.getString(
                        "abstractConnectionHandler.ioexception.debug"), e);
            }
            // Future developers: if you discover any other
            // rare-but-nonfatal exceptions, catch them here, and log as
            // above.
            catch (Throwable e) {
                ExceptionUtils.handleThrowable(e);
                // any other exception or error is odd. Here we log it
                // with "ERROR" level, so it will show up even on
                // less-than-verbose logs.
                getLog().error(
                        sm.getString("abstractConnectionHandler.error"), e);
            }
            // Don't try to add upgrade processors back into the pool
            if (!(processor instanceof UpgradeProcessor)) {
                release(socket, processor, true, false);
            }
            return SocketState.CLOSED;
        }

該方法很長(zhǎng)珊佣,我們簡(jiǎn)略的說一下主要邏輯:

  1. 從 ConcurrentHashMap 中獲取長(zhǎng)連接的封裝了soceket的處理類蹋宦。
  2. 如果沒有,則從循環(huán)使用的 ConcurrentLinkedQueue 隊(duì)列中獲取咒锻。如果還沒有冷冗,則調(diào)用自己的 createProcessor 直接創(chuàng)建一個(gè)。
  3. 調(diào)用子類的 initSsl 方法惑艇,初始化 SSL 屬性蒿辙。如果配置文件沒配置拇泛,則設(shè)置為null。
  4. 根據(jù)不同的屬性調(diào)用不同的方法思灌,優(yōu)先判斷是否異步俺叭,默認(rèn)使用同步,也就是 Http11Processor.process 方法泰偿。
  5. 執(zhí)行結(jié)束后熄守,根據(jù)返回的不同的狀態(tài)調(diào)用不同的方法,比如 longPoll耗跛,release裕照,參數(shù)也不同,默認(rèn)是 release(socket, processor, true, false)调塌,放入ConcurrentLinkedQueue (recycledProcessors)隊(duì)列中晋南。

我們重點(diǎn)關(guān)注 AbstractHttp11Processor.process 方法,該方法是處理socket的主要邏輯烟阐。

4. Http11Processor.process 方法解析

該方法其實(shí)是其父類 AbstractHttp11Processor 的方法搬俊,特別的長(zhǎng),不知道為什么tomcat的大師們?yōu)槭裁床环庋b一下蜒茄,代碼這么長(zhǎng)真的不太好看唉擂。樓主認(rèn)為一個(gè)方法最好不要超過50行。越短越好檀葛。樓主無奈玩祟,只好貼出樓主簡(jiǎn)化的代碼,大家湊合著看屿聋,如果想看詳細(xì)的源碼空扎,可以留言也可以去我的github clone;

    @Override
    public SocketState process(SocketWrapper<S> socketWrapper) throws IOException {
        // Setting up the I/O
        setSocketWrapper(socketWrapper);
        getInputBuffer().init(socketWrapper, endpoint);// 設(shè)置輸入流
        getOutputBuffer().init(socketWrapper, endpoint);// 設(shè)置輸出流

        prepareRequest();// 準(zhǔn)備請(qǐng)求內(nèi)容

        adapter.service(request, response); // 真正處理的方法 CoyoteAdapter

        return SocketState.OPEN;
     }

該方法步驟:

  1. 設(shè)置socket润讥。
  2. 設(shè)置輸入流和輸出流转锈。
  3. 繼續(xù)向下執(zhí)行。執(zhí)行 CoyoteAdapter 的service 方法楚殿。
  4. 返回狀態(tài)(默認(rèn)返回 OPEN)供上層判斷撮慨。

我們重點(diǎn)關(guān)注 CoyoteAdapter 的service 方法;

5. CoyoteAdapter.service 方法解析

該方法同樣很長(zhǎng)脆粥,我們看看該方法的邏輯:

    /**
     * Service method.
     */
    @Override
    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES); // 實(shí)現(xiàn)了 servlet 標(biāo)準(zhǔn)的 Request
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response); // 互相關(guān)聯(lián)
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringEncoding // 解析 uri
                (connector.getURIEncoding());

        }

        if (connector.getXpoweredBy()) { // 網(wǎng)站安全狗IIS
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;

        try {

            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            boolean postParseSuccess = postParseRequest(req, request, res, response); // 解析請(qǐng)求內(nèi)容
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container 調(diào)用 容器
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); // 一個(gè)復(fù)雜的調(diào)用

                if (request.isComet()) {
                    if (!response.isClosed() && !response.isError()) {
                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                            // Invoke a read event right away if there are available bytes
                            if (event(req, res, SocketStatus.OPEN)) {
                                comet = true;
                                res.action(ActionCode.COMET_BEGIN, null);
                            }
                        } else {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        }
                    } else {
                        // Clear the filter chain, as otherwise it will not be reset elsewhere
                        // since this is a Comet request
                        request.setFilterChain(null);
                    }
                }

            }
            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
            if (asyncConImpl != null) {
                async = true;
            } else if (!comet) {
                request.finishRequest();
                response.finishResponse();
                if (postParseSuccess &&
                        request.getMappingData().context != null) {
                    // Log only if processing was invoked.
                    // If postParseRequest() failed, it has already logged it.
                    // If context is null this was the start of a comet request
                    // that failed and has already been logged.
                    ((Context) request.getMappingData().context).logAccess(
                            request, response,
                            System.currentTimeMillis() - req.getStartTime(),
                            false);
                }
                req.action(ActionCode.POST_REQUEST , null);
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            req.getRequestProcessor().setWorkerThreadName(null);
            // Recycle the wrapper request and response
            if (!comet && !async) {
                request.recycle();
                response.recycle();
            } else {
                // Clear converters so that the minimum amount of memory
                // is used by this processor
                request.clearEncoders();
                response.clearEncoders();
            }
        }

    }

我們分析一下該方法的邏輯:

  1. 創(chuàng)建實(shí)現(xiàn) Servlet 標(biāo)準(zhǔn)的 Request 和 Response砌溺。
  2. 將Request 和 Response 互相關(guān)聯(lián)。
  3. 執(zhí)行 postParseRequest 解析請(qǐng)求內(nèi)容变隔。
  4. 執(zhí)行最重要的步驟:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)规伐;
  5. 執(zhí)行一些清理工作。

6. CoyoteAdapter.postParseRequest 方法解析

我們看看他是如何解析請(qǐng)求的內(nèi)容的匣缘,也就是 postParseRequest 方法的實(shí)現(xiàn):

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

        // XXX the processor may have set a correct scheme and port prior to this point,
        // in ajp13 protocols dont make sense to get the port from the connector...
        // otherwise, use connector configuration
        if (! req.scheme().isNull()) {
            // use processor specified scheme to determine secure state
            request.setSecure(req.scheme().equals("https"));
        } else {
            // use connector scheme and secure configuration, (defaults to
            // "http" and false respectively)
            req.scheme().setString(connector.getScheme());
            request.setSecure(connector.getSecure());
        }

        // FIXME: the code below doesnt belongs to here,
        // this is only have sense
        // in Http11, not in ajp13..
        // At this point the Host header has been processed.
        // Override if the proxyPort/proxyHost are set
        String proxyName = connector.getProxyName();
        int proxyPort = connector.getProxyPort();
        if (proxyPort != 0) {
            req.setServerPort(proxyPort);
        }
        if (proxyName != null) {
            req.serverName().setString(proxyName);
        }

        // Copy the raw URI to the decodedURI
        MessageBytes decodedURI = req.decodedURI();
        decodedURI.duplicate(req.requestURI());

        // Parse the path parameters. This will:
        //   - strip out the path parameters
        //   - convert the decodedURI to bytes
        parsePathParameters(req, request);

        // URI decoding
        // %xx decoding of the URL
        try {
            req.getURLDecoder().convert(decodedURI, false);
        } catch (IOException ioe) {
            res.setStatus(400);
            res.setMessage("Invalid URI: " + ioe.getMessage());
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        // Normalization
        if (!normalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }
        // Character decoding
        convertURI(decodedURI, request);
        // Check that the URI is still normalized
        if (!checkNormalize(req.decodedURI())) {
            res.setStatus(400);
            res.setMessage("Invalid URI character encoding");
            connector.getService().getContainer().logAccess(
                    request, response, 0, true);
            return false;
        }

        // Set the remote principal
        String principal = req.getRemoteUser().toString();
        if (principal != null) {
            request.setUserPrincipal(new CoyotePrincipal(principal));
        }

        // Set the authorization type
        String authtype = req.getAuthType().toString();
        if (authtype != null) {
            request.setAuthType(authtype);
        }

        // Request mapping.
        MessageBytes serverName;
        if (connector.getUseIPVHosts()) {
            serverName = req.localName();
            if (serverName.isNull()) {
                // well, they did ask for it
                res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
            }
        } else {
            serverName = req.serverName();
        }
        if (request.isAsyncStarted()) {
            //TODO SERVLET3 - async
            //reset mapping data, should prolly be done elsewhere
            request.getMappingData().recycle();
        }

        boolean mapRequired = true;
        String version = null;

        while (mapRequired) {
            if (version != null) {
                // Once we have a version - that is it
                mapRequired = false;
            }
            // This will map the the latest version by default
            connector.getMapper().map(serverName, decodedURI, version,
                                      request.getMappingData());
            request.setContext((Context) request.getMappingData().context);
            request.setWrapper((Wrapper) request.getMappingData().wrapper);

            // Single contextVersion therefore no possibility of remap
            if (request.getMappingData().contexts == null) {
                mapRequired = false;
            }

            // If there is no context at this point, it is likely no ROOT context
            // has been deployed
            if (request.getContext() == null) {
                res.setStatus(404);
                res.setMessage("Not found");
                // No context, so use host
                Host host = request.getHost();
                // Make sure there is a host (might not be during shutdown)
                if (host != null) {
                    host.logAccess(request, response, 0, true);
                }
                return false;
            }

            // Now we have the context, we can parse the session ID from the URL
            // (if any). Need to do this before we redirect in case we need to
            // include the session id in the redirect
            String sessionID = null;
            if (request.getServletContext().getEffectiveSessionTrackingModes()
                    .contains(SessionTrackingMode.URL)) {

                // Get the session ID if there was one
                sessionID = request.getPathParameter(
                        SessionConfig.getSessionUriParamName(
                                request.getContext()));
                if (sessionID != null) {
                    request.setRequestedSessionId(sessionID);
                    request.setRequestedSessionURL(true);
                }
            }

            // Look for session ID in cookies and SSL session
            parseSessionCookiesId(req, request);
            parseSessionSslId(request);

            sessionID = request.getRequestedSessionId();

            if (mapRequired) {
                if (sessionID == null) {
                    // No session means no possibility of needing to remap
                    mapRequired = false;
                } else {
                    // Find the context associated with the session
                    Object[] objs = request.getMappingData().contexts;
                    for (int i = (objs.length); i > 0; i--) {
                        Context ctxt = (Context) objs[i - 1];
                        if (ctxt.getManager().findSession(sessionID) != null) {
                            // Was the correct context already mapped?
                            if (ctxt.equals(request.getMappingData().context)) {
                                mapRequired = false;
                            } else {
                                // Set version so second time through mapping the
                                // correct context is found
                                version = ctxt.getWebappVersion();
                                // Reset mapping
                                request.getMappingData().recycle();
                                break;
                            }
                        }
                    }
                    if (version == null) {
                        // No matching context found. No need to re-map
                        mapRequired = false;
                    }
                }
            }
            if (!mapRequired && request.getContext().getPaused()) {
                // Found a matching context but it is paused. Mapping data will
                // be wrong since some Wrappers may not be registered at this
                // point.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Should never happen
                }
                // Reset mapping
                request.getMappingData().recycle();
                mapRequired = true;
            }
        }

        // Possible redirect
        MessageBytes redirectPathMB = request.getMappingData().redirectPath;
        if (!redirectPathMB.isNull()) {
            String redirectPath = urlEncoder.encode(redirectPathMB.toString());
            String query = request.getQueryString();
            if (request.isRequestedSessionIdFromURL()) {
                // This is not optimal, but as this is not very common, it
                // shouldn't matter
                redirectPath = redirectPath + ";" +
                        SessionConfig.getSessionUriParamName(
                            request.getContext()) +
                    "=" + request.getRequestedSessionId();
            }
            if (query != null) {
                // This is not optimal, but as this is not very common, it
                // shouldn't matter
                redirectPath = redirectPath + "?" + query;
            }
            response.sendRedirect(redirectPath);
            request.getContext().logAccess(request, response, 0, true);
            return false;
        }

        // Filter trace method
        if (!connector.getAllowTrace()
                && req.method().equalsIgnoreCase("TRACE")) {
            Wrapper wrapper = request.getWrapper();
            String header = null;
            if (wrapper != null) {
                String[] methods = wrapper.getServletMethods();
                if (methods != null) {
                    for (int i=0; i<methods.length; i++) {
                        if ("TRACE".equals(methods[i])) {
                            continue;
                        }
                        if (header == null) {
                            header = methods[i];
                        } else {
                            header += ", " + methods[i];
                        }
                    }
                }
            }
            res.setStatus(405);
            res.addHeader("Allow", header);
            res.setMessage("TRACE method is not allowed");
            request.getContext().logAccess(request, response, 0, true);
            return false;
        }

        return true;
    }
  1. 設(shè)置 請(qǐng)求的消息類型猖闪。
  2. 設(shè)置代理名稱和代理端口(如果配置文件有的話)鲜棠。
  3. 解析URL路徑參數(shù)。
  4. 轉(zhuǎn)換 URI 的編碼萧朝。
  5. 從Connector 容器中的Mapper 中取出對(duì)應(yīng)的 Context 和 Servlet, 設(shè)置 Request 的 WebApp應(yīng)用和Servlet岔留。如果Context不存在夏哭,則返回404检柬。
  6. 解析cookie,解析sessionId竖配,設(shè)置 SessionId何址,
  7. 判斷方法類型是否是 TRACE 類型的方法,如果是进胯,則返回405.不允許該方法進(jìn)入服務(wù)器用爪。

7.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 中的管道與閥門解析

好,解析完 postParseRequest 之后胁镐,我們?cè)倏纯聪旅娴牟襟E::connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)套像,這個(gè)鏈?zhǔn)秸{(diào)用可以說很自信鹦筹,一點(diǎn)不怕 NPE。很明顯,他們很自信销部,因?yàn)樵賳?dòng)過程中就已經(jīng)設(shè)置這些屬性了。而這段代碼也引出了2個(gè)概念吁脱,管道和閥尤溜,getPipeline 獲取管道 Pipeline,getFirst 獲取第一個(gè)閥竭宰。我們先說說什么是管道空郊?什么是閥門?

我們知道切揭,Tomcat 是由容器組成的狞甚,容器從大到小的排列依次是:Server-->Service---->Engine--->Host--->Context--->Wrapper,那么當(dāng)一個(gè)請(qǐng)求過來廓旬,從大容器到小容器哼审,他們一個(gè)一個(gè)的傳遞,像接力比賽嗤谚,如果是我們?cè)O(shè)計(jì)棺蛛,我們可能只需要將讓大容器持有小容器就好了,即能夠傳遞了巩步,但是旁赊,如果在傳遞的過程中我們需要做一些事情呢?比如校驗(yàn)椅野,比如記錄日志终畅,比如記錄時(shí)間籍胯,比如權(quán)限,并且我們要保證不能耦合离福,隨時(shí)可去除一個(gè)功能杖狼。我們?cè)撛趺崔k?相信由經(jīng)驗(yàn)的同學(xué)已經(jīng)想到了妖爷。

那就是使用過濾器模式蝶涩。

眾多的過濾器如何管理?使用管道絮识,將過濾器都放在管道中绿聘,簡(jiǎn)直完美!4紊唷熄攘!

那么Tomcat中 Pipeline 就是剛剛說的管道,過濾器就是閥門 Valve彼念,每個(gè) Pipeline 只持有第一個(gè)閥門挪圾,后面的就不管了,因?yàn)榈谝粋€(gè)會(huì)指向第二個(gè)逐沙,第二個(gè)會(huì)指向第三個(gè)哲思,便于拆卸。

那么現(xiàn)在是時(shí)候看看我們的時(shí)序圖了酱吝,我們先請(qǐng)出一部分:

從圖中我們可以看出:請(qǐng)求從SocketProcessor 進(jìn)來也殖,然后交給 Http11ConnectionHandler,最后交給 CoyoteAdapter务热,開始觸及容器的管道忆嗜。CoyoteAdapter 持有一個(gè) Connector 實(shí)例,我們?cè)俪跏蓟臅r(shí)候已經(jīng)知道了崎岂,Connector 會(huì)獲取他對(duì)應(yīng)的容器(一個(gè)容器對(duì)應(yīng)多個(gè)連接器)StandardService捆毫,StandardService會(huì)獲取他的下級(jí)容器 StandardEngine,這個(gè)時(shí)候冲甘,就該獲取管道了绩卤,管道管理著閥門。閥門接口 Pipeline 只有一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn) StandardPipeline江醇,每個(gè)容器都持有該管道實(shí)例(在初始化的時(shí)候就創(chuàng)建好了)濒憋。管道會(huì)獲取第一個(gè)閥門,如果不存在陶夜,就返回一個(gè)基礎(chǔ)閥門(每個(gè)容器創(chuàng)建的時(shí)候都會(huì)放入一個(gè)標(biāo)準(zhǔn)的對(duì)應(yīng)的容器閥門作為其基礎(chǔ)閥門)凛驮。再調(diào)用閥門的 invoke 方法,該方法在執(zhí)行完自己的邏輯之后条辟,便調(diào)用子容器的管道中的閥門的 invoke 方法黔夭。依次遞歸宏胯。

現(xiàn)在我們圖也看了,原理也說了本姥,現(xiàn)在該看代碼了肩袍,也就是 StandardEnglineValve 閥門的 invoke 方法:

    /**
     * Select the appropriate child Host to process this request,
     * based on the requested server name.  If no matching Host can
     * be found, return an appropriate HTTP error.
     * 
     * 根據(jù)所請(qǐng)求的服務(wù)器名稱選擇適當(dāng)?shù)淖又鳈C(jī)來處理這個(gè)請(qǐng)求。如果找不到匹配的主機(jī)婚惫,則返回一個(gè)適當(dāng)?shù)腍TTP錯(cuò)誤氛赐。
     *
     */
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost", 
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);

    }

該方法很簡(jiǎn)單,校驗(yàn)該Engline 容器是否含有Host容器辰妙,如果不存在鹰祸,返回400錯(cuò)誤甫窟,否則繼續(xù)執(zhí)行 host.getPipeline().getFirst().invoke(request, response)密浑,可以看到 Host 容器先獲取自己的管道,再獲取第一個(gè)閥門粗井,我們?cè)倏纯丛撻y門的 invoke 方法尔破。

8. host.getPipeline().getFirst().invoke 解析

該鏈?zhǔn)秸{(diào)用獲取的不是Basic 閥門,因?yàn)樗O(shè)置了第一個(gè)閥門:

    @Override
    public Valve getFirst() {
        if (first != null) {
            return first;
        }
        return basic;
    }

返回的是 AccessLogValve 實(shí)例浇衬,我們進(jìn)入 AccessLogValve 的 invoke 方法查看:

    @Override
    public void invoke(Request request, Response response) throws IOException,
            ServletException {
        getNext().invoke(request, response);
    }

啥也沒做懒构,只是將請(qǐng)求交給了下一個(gè)閥門,執(zhí)行g(shù)etNext耘擂,該方法是其父類 ValveBase 的方法胆剧。下一個(gè)閥門是誰呢? ErrorReportValve,我們看看該閥門的 invoke 方法:

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

        // Perform the request
        getNext().invoke(request, response);

        if (response.isCommitted()) {
            return;
        }

        Throwable throwable =
                (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

        if (request.isAsyncStarted() && response.getStatus() < 400 &&
                throwable == null) {
            return;
        }

        if (throwable != null) {

            // The response is an error
            response.setError();

            // Reset the response (if possible)
            try {
                response.reset();
            } catch (IllegalStateException e) {
                // Ignore
            }

            response.sendError
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        }

        response.setSuspended(false);

        try {
            report(request, response, throwable);
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }

        if (request.isAsyncStarted()) {
            request.getAsyncContext().complete();
        }
    }

該方法首先執(zhí)行了下個(gè)閥門的 invoke 方法醉冤。然后根據(jù)返回的Request 屬性設(shè)置一些錯(cuò)誤信息秩霍。那么下個(gè)閥門是誰呢?其實(shí)就是基礎(chǔ)閥門了:StandardHostValve蚁阳,該閥門的 invoke 的方法是如何實(shí)現(xiàn)的呢铃绒?

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

        // Select the Context to be used for this Request
        Context context = request.getContext();
        if (context == null) {
            response.sendError
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                 sm.getString("standardHost.noContext"));
            return;
        }

        // Bind the context CL to the current thread
        if( context.getLoader() != null ) {
            // Not started - it should check for availability first
            // This should eventually move to Engine, it's generic.
            if (Globals.IS_SECURITY_ENABLED) {
                PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                        context.getLoader().getClassLoader());
                AccessController.doPrivileged(pa);                
            } else {
                Thread.currentThread().setContextClassLoader
                        (context.getLoader().getClassLoader());
            }
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(context.getPipeline().isAsyncSupported());
        }

        // Don't fire listeners during async processing
        // If a request init listener throws an exception, the request is
        // aborted
        boolean asyncAtStart = request.isAsync(); 
        // An async error page may dispatch to another resource. This flag helps
        // ensure an infinite error handling loop is not entered
        boolean errorAtStart = response.isError();
        if (asyncAtStart || context.fireRequestInitEvent(request)) {

            // Ask this Context to process this request
            try {
                context.getPipeline().getFirst().invoke(request, response);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (errorAtStart) {
                    container.getLogger().error("Exception Processing " +
                            request.getRequestURI(), t);
                } else {
                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                    throwable(request, response, t);
                }
            }
    
            // If the request was async at the start and an error occurred then
            // the async error handling will kick-in and that will fire the
            // request destroyed event *after* the error handling has taken
            // place
            if (!(request.isAsync() || (asyncAtStart &&
                    request.getAttribute(
                            RequestDispatcher.ERROR_EXCEPTION) != null))) {
                // Protect against NPEs if context was destroyed during a
                // long running request.
                if (context.getState().isAvailable()) {
                    if (!errorAtStart) {
                        // Error page processing
                        response.setSuspended(false);
    
                        Throwable t = (Throwable) request.getAttribute(
                                RequestDispatcher.ERROR_EXCEPTION);
    
                        if (t != null) {
                            throwable(request, response, t);
                        } else {
                            status(request, response);
                        }
                    }
    
                    context.fireRequestDestroyEvent(request);
                }
            }
        }

        // Access a session (if present) to update last accessed time, based on a
        // strict interpretation of the specification
        if (ACCESS_SESSION) {
            request.getSession(false);
        }

        // Restore the context classloader
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    StandardHostValve.class.getClassLoader());
            AccessController.doPrivileged(pa);                
        } else {
            Thread.currentThread().setContextClassLoader
                    (StandardHostValve.class.getClassLoader());
        }
    }

首先校驗(yàn)了Request 是否存在 Context,其實(shí)在執(zhí)行 CoyoteAdapter.postParseRequest 方法的時(shí)候就設(shè)置了螺捐,如果Context 不存在颠悬,就返回500,接著還是老套路:context.getPipeline().getFirst().invoke定血,該管道獲取的是基礎(chǔ)閥門:StandardContextValve赔癌,我們還是關(guān)注他的 invoke 方法。

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

        // Disallow any direct access to resources under WEB-INF or META-INF
        MessageBytes requestPathMB = request.getRequestPathMB();
        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/META-INF"))
                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Select the Wrapper to be used for this Request
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Acknowledge the request
        try {
            response.sendAcknowledgement();
        } catch (IOException ioe) {
            container.getLogger().error(sm.getString(
                    "standardContextValve.acknowledgeException"), ioe);
            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
        
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
        }
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

該方法也只是一些校驗(yàn)澜沟,最后卡是調(diào)用 wrapper.getPipeline().getFirst().invoke灾票,獲取到的也是基礎(chǔ)閥門,該閥門是StandardWeapperValve 倔喂,我們看看該方法铝条。該方法很重要靖苇。

9. wrapper.getPipeline().getFirst().invoke 解析

該方法超長(zhǎng):

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

        // Initialize local variables we may need
        boolean unavailable = false;
        Throwable throwable = null;
        // This should be a Request attribute...
        long t1=System.currentTimeMillis();
        requestCount++;
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();
        
        // Check for the application being marked unavailable
        if (!context.getState().isAvailable()) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardContext.isUnavailable"));
            unavailable = true;
        }

        // Check for the servlet being marked unavailable
        if (!unavailable && wrapper.isUnavailable()) {
            container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
                    wrapper.getName()));
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE)) {
                response.setDateHeader("Retry-After", available);
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                        sm.getString("standardWrapper.isUnavailable",
                                wrapper.getName()));
            } else if (available == Long.MAX_VALUE) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        sm.getString("standardWrapper.notFound",
                                wrapper.getName()));
            }
            unavailable = true;
        }

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } catch (UnavailableException e) {
            container.getLogger().error(
                    sm.getString("standardWrapper.allocateException",
                            wrapper.getName()), e);
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE)) {
                response.setDateHeader("Retry-After", available);
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardWrapper.isUnavailable",
                                        wrapper.getName()));
            } else if (available == Long.MAX_VALUE) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                           sm.getString("standardWrapper.notFound",
                                        wrapper.getName()));
            }
        } catch (ServletException e) {
            container.getLogger().error(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), StandardWrapper.getRootCause(e));
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }

        // Identify if the request is Comet related now that the servlet has been allocated
        boolean comet = false;
        if (servlet instanceof CometProcessor && request.getAttribute(
                Globals.COMET_SUPPORTED_ATTR) == Boolean.TRUE) {
            comet = true;
            request.setComet(true);
        }
        
        MessageBytes requestPathMB = request.getRequestPathMB();
        DispatcherType dispatcherType = DispatcherType.REQUEST;
        if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; 
        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
                requestPathMB);
        // Create the filter chain for this request
        ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
        ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);
        
        // Reset comet flag value after creating the filter chain
        request.setComet(false);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            //TODO SERVLET3 - async
                            ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); 
                        } else if (comet) {
                            filterChain.doFilterEvent(request.getEvent());
                            request.setComet(true);
                        } else {
                            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()) {
                        //TODO SERVLET3 - async
                        ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
                    } else if (comet) {
                        request.setComet(true);
                        filterChain.doFilterEvent(request.getEvent());
                    } else {
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
        } catch (ClientAbortException e) {
            throwable = e;
            exception(request, response, e);
        } catch (IOException e) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (UnavailableException e) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            //            throwable = e;
            //            exception(request, response, e);
            wrapper.unavailable(e);
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE)) {
                response.setDateHeader("Retry-After", available);
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardWrapper.isUnavailable",
                                        wrapper.getName()));
            } else if (available == Long.MAX_VALUE) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                            sm.getString("standardWrapper.notFound",
                                        wrapper.getName()));
            }
            // Do not save exception in 'throwable', because we
            // do not want to do exception(request, response, e) processing
        } catch (ServletException e) {
            Throwable rootCause = StandardWrapper.getRootCause(e);
            if (!(rootCause instanceof ClientAbortException)) {
                container.getLogger().error(sm.getString(
                        "standardWrapper.serviceExceptionRoot",
                        wrapper.getName(), context.getName(), e.getMessage()),
                        rootCause);
            }
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceException", wrapper.getName(),
                    context.getName()), e);
            throwable = e;
            exception(request, response, e);
        }

        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            if (request.isComet()) {
                // If this is a Comet request, then the same chain will be used for the
                // processing of all subsequent events.
                filterChain.reuse();
            } else {
                filterChain.release();
            }
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                wrapper.unload();
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.unloadException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }
        long t2=System.currentTimeMillis();

        long time=t2-t1;
        processingTime += time;
        if( time > maxTime) maxTime=time;
        if( time < minTime) minTime=time;

    }

我們分析一下該方法的重要步驟:

  1. 獲取 StandardWrapper(封裝了Servlet) 實(shí)例調(diào)用 allocate 方法獲取 Stack 中的 Servlet 實(shí)例;
  2. 判斷servlet 是否實(shí)現(xiàn)了 CometProcessor 接口班缰,如果實(shí)現(xiàn)了則設(shè)置 request 的comet(Comet:基于 HTTP 長(zhǎng)連接的“服務(wù)器推”技術(shù)) 屬性為 true贤壁。
  3. 獲取 ApplicationFilterFactory 單例(注意:這個(gè)獲取單例的代碼是有線程安全問題的),調(diào)用該單例的 createFilterChain 方法獲取 ApplicationFilterChain 過濾器鏈實(shí)例埠忘。
  4. 執(zhí)行過濾器鏈 filterChain 的 doFilter 方法脾拆。該方法會(huì)循環(huán)執(zhí)行所有的過濾器,最終執(zhí)行 servlet 的 servie 方法莹妒。

我們分析一下 allocate 方法名船。

10. StandardWrapper.allcate 獲取 Servlet 實(shí)例方法解析

源碼:

   @Override
    public Servlet allocate() throws ServletException {

        // If we are currently unloading this servlet, throw an exception
        if (unloading)
            throw new ServletException
              (sm.getString("standardWrapper.unloading", getName()));

        boolean newInstance = false;
        
        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {

            // Load and initialize our instance if necessary
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            if (log.isDebugEnabled())
                                log.debug("Allocating non-STM instance");

                            instance = loadServlet();
                            if (!singleThreadModel) {
                                // For non-STM, increment here to prevent a race
                                // condition with unload. Bug 43683, test case
                                // #3
                                newInstance = true;
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ExceptionUtils.handleThrowable(e);
                            throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }

            if (!instanceInitialized) {
                initServlet(instance);
            }

            if (singleThreadModel) {
                if (newInstance) {
                    // Have to do this outside of the sync above to prevent a
                    // possible deadlock
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                if (log.isTraceEnabled())
                    log.trace("  Returning non-STM instance");
                // For new instances, count will have been incremented at the
                // time of creation
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return (instance);
            }
        }

        synchronized (instancePool) {

            while (countAllocated.get() >= nInstances) {
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            if (log.isTraceEnabled())
                log.trace("  Returning allocated STM instance");
            countAllocated.incrementAndGet();
            return instancePool.pop();

        }

    }

該方法很長(zhǎng),我們來看看該方法步驟: 判斷該類(StandardWrapper)中的 Servlet 實(shí)例是否為null旨怠,默認(rèn)不為null渠驼,該實(shí)例在初始化的時(shí)候就已經(jīng)注入,如果沒有注入鉴腻,則調(diào)用 loadServlet 方法迷扇,反射加載實(shí)例(注意,如果這個(gè)servlet 實(shí)現(xiàn)了 singleThreadModel 接口爽哎,該StandardWrapper 就是多個(gè)servlet 實(shí)例的蜓席,默認(rèn)是單個(gè)實(shí)例,多個(gè)實(shí)例會(huì)放入一個(gè)Stack(這個(gè)棧不是早就不建議使用了嗎) 類型的棧中)课锌。

11. ApplicationFilterFactory 解析(tomcat 7 會(huì)有并發(fā)問題)

獲取創(chuàng)建過濾器鏈工廠的單例厨内,但樓主看了代碼,發(fā)現(xiàn)該代碼一定會(huì)有問題渺贤。

    public static ApplicationFilterFactory getInstance() {
        if (factory == null) {
            factory = new ApplicationFilterFactory();
        }
        return factory;
    }

讀過樓主 深入解析單例模式 的文章應(yīng)該知道雏胃,這種寫法并發(fā)的時(shí)候一定是有問題的,會(huì)創(chuàng)建多個(gè)實(shí)例癣亚。但是樓主下載了最新的 tomcat 源碼丑掺,已經(jīng)解決了該 bug,新的 tomcat 已經(jīng)把 getInstance 去除了述雾,將 createFilterChain 方法改為靜態(tài)方法街州。

獲取過濾器工廠鏈后,創(chuàng)建過濾器鏈實(shí)例玻孟,從該Wrapper 中獲取父容器唆缴,從父容器 StandardContext 中獲取實(shí)例,從該實(shí)例中獲取 filterMaps黍翎,該 filterMaps 在初始化容器時(shí)從web.xml 中創(chuàng)建面徽。

12. 執(zhí)行過濾器鏈的 doFilter 方法

該方法主要執(zhí)行 ApplicationFilterChain.internalDoFilter() 方法,那么 方法 internalDoFilter 內(nèi)部又是如何實(shí)現(xiàn)的呢?下面時(shí)該方法的主要邏輯(主要時(shí)樓主字?jǐn)?shù)受限了):

     // Call the next filter if there is one
       if (pos < n) {
           filter.doFilter(request, response, this);
       }
       servlet.service(request, response);

可以看到趟紊,ApplicationFilterChain 中維護(hù)了2個(gè)變量氮双,當(dāng)前位置 pos 和 過濾器數(shù)量,因此要執(zhí)行完所有的過濾器霎匈,而過濾器最終又會(huì)執(zhí)行 doFilter 方法戴差,就會(huì)又回到該方法,直到執(zhí)行完所有的過濾器铛嘱,最后執(zhí)行 servlet 的 service 方法暖释。到這里,一個(gè)完整的http請(qǐng)求就從socket 到了我們編寫的servlet中了墨吓。

13. 總結(jié)

終于球匕,我們完成了對(duì)tomcat請(qǐng)求過程的剖析,詳細(xì)理解了tomcat是如何處理一個(gè)http請(qǐng)求帖烘,也完了我們最初的計(jì)劃亮曹,下一篇就是總結(jié)我們的前9篇文章,敬請(qǐng)期待蚓让。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乾忱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子历极,更是在濱河造成了極大的恐慌,老刑警劉巖衷佃,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趟卸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡氏义,警方通過查閱死者的電腦和手機(jī)锄列,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惯悠,“玉大人邻邮,你說我怎么就攤上這事】松簦” “怎么了筒严?”我有些...
    開封第一講書人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)情萤。 經(jīng)常有香客問我鸭蛙,道長(zhǎng),這世上最難降的妖魔是什么筋岛? 我笑而不...
    開封第一講書人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任娶视,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肪获。我一直安慰自己寝凌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開白布孝赫。 她就那樣靜靜地躺著硫兰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寒锚。 梳的紋絲不亂的頭發(fā)上劫映,一...
    開封第一講書人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音刹前,去河邊找鬼泳赋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛喇喉,可吹牛的內(nèi)容都是我干的祖今。 我是一名探鬼主播,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼拣技,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼千诬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起膏斤,我...
    開封第一講書人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤徐绑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后莫辨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體傲茄,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年沮榜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盘榨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蟆融,死狀恐怖草巡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情型酥,我是刑警寧澤山憨,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站冕末,受9級(jí)特大地震影響萍歉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜档桃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一枪孩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蔑舞、人聲如沸拒担。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽从撼。三九已至,卻和暖如春钧栖,著一層夾襖步出監(jiān)牢的瞬間低零,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工拯杠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掏婶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓潭陪,卻偏偏與公主長(zhǎng)得像雄妥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子依溯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容