Tomcat請求響應處理(一)

前言
之前的文章分別從Tomcat的兩個部分:ContainerConnector對其組件間關系和生命周期狀態(tài)的流轉(zhuǎn)進行了分析。兩大部分作為Tomcat運行的基石保證了請求響應的高效處理及準確分發(fā)。從本篇文章開始,我們進入Tomcat處理請求原理的流程分析,該部分內(nèi)容準備用兩篇文章闡述,本文是其中的前半部分,著重分析Tomcat是如何根據(jù)請求生成對應的requestresponse,又如何根據(jù)請求參數(shù)路徑映射到對應Context中特定的Servlet

Tomcat的生命周期(三)中說到,對于BIO來說JIoEndpointSocketProcessor封裝了接收并處理Socket的流程,見代碼清單1

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;
                }
                //  (1)
                if ((state != SocketState.CLOSED)) {
                    if (status == null) {
                        state = handler.process(socket, SocketStatus.OPEN_READ);
                    } else {
                        state = handler.process(socket,status);
                    }
                }
                if (state == SocketState.CLOSED) {
                    // Close socket
                    if (log.isTraceEnabled()) {
                        log.trace("Closing socket:"+socket);
                    }
                    countDownConnection();
                    try {
                        socket.getSocket().close();
                    } catch (IOException e) {
                        // Ignore
                    }
                } else if (state == SocketState.OPEN ||
                        state == SocketState.UPGRADING ||
                        state == SocketState.UPGRADING_TOMCAT  ||
                        state == SocketState.UPGRADED){
                    socket.setKeptAlive(true);
                    socket.access();
                    launch = true;
                } else if (state == SocketState.LONG) {
                    socket.access();
                    waitingRequests.add(socket);
                }
            } finally {
                if (launch) {
                    try {
                        getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
                    } 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;
        // Finish up this request
    }

}

在標注(1)中將socket的包裝類SocketWrapper交給成員變量handler進一步處理,該handler是繼承自AbstractProtocol.AbstractConnectionHandlerJIoEndpoint內(nèi)部類Http11ConnectionHandler,但process(SocketWrapper, SocketStatus)依然是其父類的方法,代碼清單2

public SocketState process(SocketWrapper<S> wrapper, SocketStatus status) {
    if (wrapper == null) {
        // Nothing to do. Socket has been closed.
        return SocketState.CLOSED;
    }

    S socket = wrapper.getSocket();
    if (socket == null) {
        // Nothing to do. Socket has been closed.
        return SocketState.CLOSED;
    }
    //    (1)
    Processor<S> processor = connections.get(socket);
    if (status == SocketStatus.DISCONNECT && processor == null) {
        // Nothing to do. Endpoint requested a close and there is no
        // longer a processor associated with this socket.
        return SocketState.CLOSED;
    }

    wrapper.setAsync(false);
    ContainerThreadMarker.markAsContainerThread();

    try {
        if (processor == null) {
            processor = recycledProcessors.poll();
        }
        if (processor == null) {
            //    (2)
            processor = createProcessor();
        }

        initSsl(wrapper, processor);

        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);
                if (state == SocketState.OPEN) {
                    // release() won't get called so in case this request
                    // takes a long time to process, remove the socket from
                    // the waiting requests now else the async timeout will
                    // fire
                    getProtocol().endpoint.removeWaitingRequest(wrapper);
                    // There may be pipe-lined data to read. If the data
                    // isn't processed now, execution will exit this
                    // loop and call release() which will recycle the
                    // processor (and input buffer) deleting any
                    // pipe-lined data. To avoid this, process it now.
                    state = processor.process(wrapper);
                }
            } else if (processor.isComet()) {
                state = processor.event(status);
            } else if (processor.getUpgradeInbound() != null) {
                state = processor.upgradeDispatch();
            } else if (processor.isUpgrade()) {
                state = processor.upgradeDispatch(status);
            } else {
                //    (3)
                state = processor.process(wrapper);
            }

            if (state != SocketState.CLOSED && processor.isAsync()) {
                state = processor.asyncPostProcess();
            }

            if (state == SocketState.UPGRADING) {
                // Get the HTTP upgrade handler
                HttpUpgradeHandler httpUpgradeHandler =
                        processor.getHttpUpgradeHandler();
                // Release the Http11 processor to be re-used
                release(wrapper, processor, false, false);
                // Create the upgrade processor
                processor = createUpgradeProcessor(
                        wrapper, httpUpgradeHandler);
                // Mark the connection as upgraded
                wrapper.setUpgraded(true);
                // Associate with the processor with the connection
                connections.put(socket, processor);
                // Initialise the upgrade handler (which may trigger
                // some IO using the new protocol which is why the lines
                // above are necessary)
                // This cast should be safe. If it fails the error
                // handling for the surrounding try/catch will deal with
                // it.
                httpUpgradeHandler.init((WebConnection) processor);
            } else if (state == SocketState.UPGRADING_TOMCAT) {
                // Get the UpgradeInbound handler
                org.apache.coyote.http11.upgrade.UpgradeInbound inbound =
                        processor.getUpgradeInbound();
                // Release the Http11 processor to be re-used
                release(wrapper, processor, false, false);
                // Create the light-weight upgrade processor
                processor = createUpgradeProcessor(wrapper, inbound);
                inbound.onUpgradeComplete();
            }
            if (getLog().isDebugEnabled()) {
                getLog().debug("Socket: [" + wrapper +
                        "], Status in: [" + status +
                        "], State out: [" + state + "]");
            }
        } while (state == SocketState.ASYNC_END ||
                state == SocketState.UPGRADING ||
                state == SocketState.UPGRADING_TOMCAT);

        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
            connections.put(socket, processor);
            longPoll(wrapper, processor);
        } else if (state == SocketState.OPEN) {
            // In keep-alive but between requests. OK to recycle
            // processor. Continue to poll for the next request.
            connections.remove(socket);
            release(wrapper, 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
            connections.remove(socket);
            release(wrapper, processor, false, false);
        } else if (state == SocketState.UPGRADED) {
            // Need to keep the connection associated with the processor
            connections.put(socket, processor);
            // Don't add sockets back to the poller if this was a
            // non-blocking write otherwise the poller may trigger
            // multiple read events which may lead to thread starvation
            // in the connector. The write() method will add this socket
            // to the poller if necessary.
            if (status != SocketStatus.OPEN_WRITE) {
                longPoll(wrapper, processor);
            }
        } else {
            // Connection closed. OK to recycle the processor. Upgrade
            // processors are not recycled.
            connections.remove(socket);
            if (processor.isUpgrade()) {
                processor.getHttpUpgradeHandler().destroy();
            } else if (processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor) {
                // NO-OP
            } else {
                release(wrapper, 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);
    }
    // Make sure socket/processor is removed from the list of current
    // connections
    connections.remove(socket);
    // Don't try to add upgrade processors back into the pool
    if (!(processor instanceof org.apache.coyote.http11.upgrade.UpgradeProcessor)
            && !processor.isUpgrade()) {
        release(wrapper, processor, true, false);
    }
    return SocketState.CLOSED;
}

標注(1)首先根據(jù)SocketWrapper從類型為ConcurrentHashMap的緩存中查找是否有對應SocketWrapperProcessor,如果從緩存池和可循環(huán)利用的recycledProcessors中都沒有合適的處理器,就會調(diào)用createProcessor()創(chuàng)建,不同的請求方式對應不同的處理器類,BIO方式對應的為Http11Processor

圖1. Http11Protocol內(nèi)部類Http11ConnectionHandler的createProcessor()

圖中傳了一堆參數(shù)構建了Http11Processor實例,其中第一個參數(shù)設置了最大Http請求頭大小為8M,最后一個參數(shù)設置了默認情況下請求體最大大小為20M,倒數(shù)第三句代碼設置處理最多200個cookie,除此以外CoyoteAdapterJIoEndpoint實例都與Http11Processor建立了關聯(lián),在調(diào)用Http11Processor構造器時首先調(diào)用了其頂層父類的構造器
圖2. AbstractProcessor的構造器

圖中看到創(chuàng)建了requestresponse對象,并且建立了兩者的關聯(lián),但是這一對請求響應僅僅是處理過程中第一對請求響應,我稱之為第一層次的請求響應,request中使用一個個MessageBytes對象將請求頭中各個部分進行分門別類的保存,而requestresponse中各自引用的輸入、輸出流buffer就作為了socket與對象之間的數(shù)據(jù)傳輸?shù)募~帶,我們再來看看AbstractProcessor的實現(xiàn)類Http11Processor的構造方法
圖3. Http11Processor的構造器

super就是調(diào)用父類AbstractProcessor的構造器,InternalInputBuffer就是輸入流buffer,對應的InternalOutputBuffer就是輸出流buffer,最后一句用于設置輸入輸出過濾,比如上面說的Http請求體的最大大小就是過濾條件之一。我們回到代碼清單2繼續(xù)看標注(3)處,進入真正處理socket流程,代碼實際上調(diào)用了AbstractHttp11Processor.process(SocketWrapper)代碼清單3

public SocketState process(SocketWrapper<S> socketWrapper)
    throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

    // Setting up the I/O
    //    (1)
    setSocketWrapper(socketWrapper);
    getInputBuffer().init(socketWrapper, endpoint);
    getOutputBuffer().init(socketWrapper, endpoint);

    // Flags
    keepAlive = true;
    comet = false;
    openSocket = false;
    sendfileInProgress = false;
    readComplete = true;
    if (endpoint.getUsePolling()) {
        keptAlive = false;
    } else {
        keptAlive = socketWrapper.isKeptAlive();
    }

    if (disableKeepAlive()) {
        socketWrapper.setKeepAliveLeft(0);
    }

    while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
            upgradeInbound == null &&
            httpUpgradeHandler == null && !endpoint.isPaused()) {

        // Parsing the request header
        try {
            setRequestLineReadTimeout();
            //    (2)
            if (!getInputBuffer().parseRequestLine(keptAlive)) {
                if (handleIncompleteRequestLineRead()) {
                    break;
                }
            }

            if (endpoint.isPaused()) {
                // 503 - Service unavailable
                response.setStatus(503);
                setErrorState(ErrorState.CLOSE_CLEAN, null);
            } else {
                keptAlive = true;
                // Set this every time in case limit has been changed via JMX
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                request.getCookies().setLimit(getMaxCookieCount());
                // Currently only NIO will ever return false here
                //    (3)
                if (!getInputBuffer().parseHeaders()) {
                    // We've read part of the request, don't recycle it
                    // instead associate it with the socket
                    openSocket = true;
                    readComplete = false;
                    break;
                }
                if (!disableUploadTimeout) {
                    setSocketTimeout(connectionUploadTimeout);
                }
            }
        } catch (IOException e) {
            if (getLog().isDebugEnabled()) {
                getLog().debug(
                        sm.getString("http11processor.header.parse"), e);
            }
            setErrorState(ErrorState.CLOSE_NOW, e);
            break;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            UserDataHelper.Mode logMode = userDataHelper.getNextMode();
            if (logMode != null) {
                String message = sm.getString(
                        "http11processor.header.parse");
                switch (logMode) {
                    case INFO_THEN_DEBUG:
                        message += sm.getString(
                                "http11processor.fallToDebug");
                        //$FALL-THROUGH$
                    case INFO:
                        getLog().info(message, t);
                        break;
                    case DEBUG:
                        getLog().debug(message, t);
                }
            }
            // 400 - Bad Request
            response.setStatus(400);
            setErrorState(ErrorState.CLOSE_CLEAN, t);
            getAdapter().log(request, response, 0);
        }

        if (!getErrorState().isError()) {
            // Setting up filters, and parse some request headers
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                //    (4)
                prepareRequest();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (getLog().isDebugEnabled()) {
                    getLog().debug(sm.getString(
                            "http11processor.request.prepare"), t);
                }
                // 500 - Internal Server Error
                response.setStatus(500);
                setErrorState(ErrorState.CLOSE_CLEAN, t);
                getAdapter().log(request, response, 0);
            }
        }

        if (maxKeepAliveRequests == 1) {
            keepAlive = false;
        } else if (maxKeepAliveRequests > 0 &&
                socketWrapper.decrementKeepAlive() <= 0) {
            keepAlive = false;
        }

        // Process the request in the adapter
        if (!getErrorState().isError()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                //    (5)
                adapter.service(request, response);
                // Handle when the response was committed before a serious
                // error occurred.  Throwing a ServletException should both
                // set the status to 500 and set the errorException.
                // If we fail here, then the response is likely already
                // committed, so we can't try and set headers.
                if(keepAlive && !getErrorState().isError() && (
                        response.getErrorException() != null ||
                                (!isAsync() &&
                                statusDropsConnection(response.getStatus())))) {
                    setErrorState(ErrorState.CLOSE_CLEAN, null);
                }
                setCometTimeouts(socketWrapper);
            } catch (InterruptedIOException e) {
                setErrorState(ErrorState.CLOSE_NOW, e);
            } catch (HeadersTooLargeException e) {
                getLog().error(sm.getString("http11processor.request.process"), e);
                // The response should not have been committed but check it
                // anyway to be safe
                if (response.isCommitted()) {
                    setErrorState(ErrorState.CLOSE_NOW, e);
                } else {
                    response.reset();
                    response.setStatus(500);
                    setErrorState(ErrorState.CLOSE_CLEAN, e);
                    response.setHeader("Connection", "close"); // TODO: Remove
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                getLog().error(sm.getString("http11processor.request.process"), t);
                // 500 - Internal Server Error
                response.setStatus(500);
                setErrorState(ErrorState.CLOSE_CLEAN, t);
                getAdapter().log(request, response, 0);
            }
        }

        // Finish the handling of the request
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);

        if (!isAsync() && !comet) {
            if (getErrorState().isError()) {
                // If we know we are closing the connection, don't drain
                // input. This way uploading a 100GB file doesn't tie up the
                // thread if the servlet has rejected it.
                getInputBuffer().setSwallowInput(false);
            } else {
                // Need to check this again here in case the response was
                // committed before the error that requires the connection
                // to be closed occurred.
                checkExpectationAndResponseStatus();
            }
            endRequest();
        }

        rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);

        // If there was an error, make sure the request is counted as
        // and error, and update the statistics counter
        if (getErrorState().isError()) {
            response.setStatus(500);
        }

        request.updateCounters();

        if (!isAsync() && !comet || getErrorState().isError()) {
            if (getErrorState().isIoAllowed()) {
                getInputBuffer().nextRequest();
                getOutputBuffer().nextRequest();
            }
        }

        if (!disableUploadTimeout) {
            if(endpoint.getSoTimeout() > 0) {
                setSocketTimeout(endpoint.getSoTimeout());
            } else {
                setSocketTimeout(0);
            }
        }

        rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);

        if (breakKeepAliveLoop(socketWrapper)) {
            break;
        }
    }

    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);

    if (getErrorState().isError() || endpoint.isPaused()) {
        return SocketState.CLOSED;
    } else if (isAsync() || comet) {
        return SocketState.LONG;
    } else if (isUpgrade()) {
        return SocketState.UPGRADING;
    } else if (getUpgradeInbound() != null) {
        return SocketState.UPGRADING_TOMCAT;
    } else {
        if (sendfileInProgress) {
            return SocketState.SENDFILE;
        } else {
            if (openSocket) {
                if (readComplete) {
                    return SocketState.OPEN;
                } else {
                    return SocketState.LONG;
                }
            } else {
                return SocketState.CLOSED;
            }
        }
    }
}

標注(1)處首先將SocketWrapper與當前processor進行關聯(lián),然后初始化輸入輸出流緩沖類,init(SocketWrapper, AbstractEndpoint)實際上是將socket內(nèi)部的輸入輸出流賦值給了輸入輸出緩沖類中的輸入輸出流。標注(2)對請求頭進行解析,代碼清單4

public boolean parseRequestLine(boolean useAvailableDataOnly)

    throws IOException {

    int start = 0;

    //
    // Skipping blank lines
    //
    //    (1)
    byte chr = 0;
    do {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }
        // Set the start time once we start reading data (even if it is
        // just skipping blank lines)
        if (request.getStartTime() < 0) {
            request.setStartTime(System.currentTimeMillis());
        }
        chr = buf[pos++];
    } while ((chr == Constants.CR) || (chr == Constants.LF));

    pos--;

    // Mark the current buffer position
    start = pos;

    //
    // Reading the method name
    // Method name is a token
    //
    //    (2)
    boolean space = false;

    while (!space) {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        // Spec says method name is a token followed by a single SP but
        // also be tolerant of multiple SP and/or HT.
        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
            space = true;
            request.method().setBytes(buf, start, pos - start);
        } else if (!HttpParser.isToken(buf[pos])) {
            throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
        }

        pos++;

    }

    // Spec says single SP but also be tolerant of multiple SP and/or HT
    while (space) {
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }
        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
            pos++;
        } else {
            space = false;
        }
    }

    // Mark the current buffer position
    start = pos;
    int end = 0;
    int questionPos = -1;

    //
    // Reading the URI
    //
    //    (3)
    boolean eol = false;

    while (!space) {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        // Spec says single SP but it also says be tolerant of HT
        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
            space = true;
            end = pos;
        } else if ((buf[pos] == Constants.CR)
                   || (buf[pos] == Constants.LF)) {
            // HTTP/0.9 style request
            eol = true;
            space = true;
            end = pos;
        } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {
            questionPos = pos;
        } else if (HttpParser.isNotRequestTarget(buf[pos])) {
            throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
        }

        pos++;

    }

    request.unparsedURI().setBytes(buf, start, end - start);
    if (questionPos >= 0) {
        request.queryString().setBytes(buf, questionPos + 1,
                                       end - questionPos - 1);
        request.requestURI().setBytes(buf, start, questionPos - start);
    } else {
        request.requestURI().setBytes(buf, start, end - start);
    }

    // Spec says single SP but also says be tolerant of multiple SP and/or HT
    while (space) {
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }
        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
            pos++;
        } else {
            space = false;
        }
    }

    // Mark the current buffer position
    start = pos;
    end = 0;

    //
    // Reading the protocol
    // Protocol is always "HTTP/" DIGIT "." DIGIT
    //    (4)
    while (!eol) {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        if (buf[pos] == Constants.CR) {
            end = pos;
        } else if (buf[pos] == Constants.LF) {
            if (end == 0)
                end = pos;
            eol = true;
        } else if (!HttpParser.isHttpProtocol(buf[pos])) {
            throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
        }

        pos++;

    }

    if ((end - start) > 0) {
        request.protocol().setBytes(buf, start, end - start);
    } else {
        request.protocol().setString("");
    }

    return true;

}

Tomcat對于請求中信息的提取基本都是采用如上偏移量加字節(jié)數(shù)組讀取再賦值的方式進行的,雖然看上去很雜亂,但我們仔細想一想請求行的三大組成部分:請求方式;請求路徑;請求協(xié)議及版本,再看看對應代碼的分割就會發(fā)現(xiàn),代碼塊與請求三大部分近乎一一對應。標注(1)首先排除了一些在請求行之前的空行,標注(2)解析了請求方式,當遍歷到的指針pos對應的字節(jié)為' '或者\t(三部分之間以空格或制表符隔開),說明請求方式截取結(jié)束,將數(shù)據(jù)塊中的請求方式內(nèi)容賦值給request中的method。標注(3)提取請求URI信息,除了和提取請求方式有著相同的邏輯之外,URI還多了個兩個判斷:1. 如果遍歷到字節(jié)為\r或者\n,則請求協(xié)議和版本為Http/0.9;2. 如果字節(jié)為?說明遍歷到了URI后面跟的參數(shù),記錄當前位置為questionPos。一切準備就緒才進行URI和queryString的提取賦值。最后一部分標注(4)自然就是對協(xié)議及版本號的解析賦值過程,當然如果是HTTP協(xié)議的0.9版本,在第三部分會將結(jié)束標志位eol置為false,就不用再解析了,其對應字段為request中的protocol
回到代碼清單3看標注(3)對請求頭進行解析方法parseHeaders(),最終會調(diào)用InternalInputBufferparseHeader()代碼清單5

private boolean parseHeader()
    throws IOException {

    //
    // Check for blank line
    //

    byte chr = 0;
    while (true) {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        chr = buf[pos];

        if (chr == Constants.CR) {
            // Skip
        } else if (chr == Constants.LF) {
            pos++;
            return false;
        } else {
            break;
        }

        pos++;

    }

    // Mark the current buffer position
    int start = pos;

    //
    // Reading the header name
    // Header name is always US-ASCII
    //

    boolean colon = false;
    MessageBytes headerValue = null;

    while (!colon) {

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        if (buf[pos] == Constants.COLON) {
            colon = true;
            headerValue = headers.addValue(buf, start, pos - start);
        } else if (!HttpParser.isToken(buf[pos])) {
            // If a non-token header is detected, skip the line and
            // ignore the header
            skipLine(start);
            return true;
        }

        chr = buf[pos];
        if ((chr >= Constants.A) && (chr <= Constants.Z)) {
            buf[pos] = (byte) (chr - Constants.LC_OFFSET);
        }

        pos++;

    }

    // Mark the current buffer position
    start = pos;
    int realPos = pos;

    //
    // Reading the header value (which can be spanned over multiple lines)
    //

    boolean eol = false;
    boolean validLine = true;

    while (validLine) {

        boolean space = true;

        // Skipping spaces
        while (space) {

            // Read new bytes if needed
            if (pos >= lastValid) {
                if (!fill())
                    throw new EOFException(sm.getString("iib.eof.error"));
            }

            if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
                pos++;
            } else {
                space = false;
            }

        }

        int lastSignificantChar = realPos;

        // Reading bytes until the end of the line
        while (!eol) {

            // Read new bytes if needed
            if (pos >= lastValid) {
                if (!fill())
                    throw new EOFException(sm.getString("iib.eof.error"));
            }

            if (buf[pos] == Constants.CR) {
                // Skip
            } else if (buf[pos] == Constants.LF) {
                eol = true;
            } else if (buf[pos] == Constants.SP) {
                buf[realPos] = buf[pos];
                realPos++;
            } else {
                buf[realPos] = buf[pos];
                realPos++;
                lastSignificantChar = realPos;
            }

            pos++;

        }

        realPos = lastSignificantChar;

        // Checking the first character of the new line. If the character
        // is a LWS, then it's a multiline header

        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }

        chr = buf[pos];
        if ((chr != Constants.SP) && (chr != Constants.HT)) {
            validLine = false;
        } else {
            eol = false;
            // Copying one extra space in the buffer (since there must
            // be at least one space inserted between the lines)
            buf[realPos] = chr;
            realPos++;
        }

    }

    // Set the header value
    headerValue.setBytes(buf, start, realPos - start);

    return true;

}

請求頭由多個請求頭域組成,每一個請求頭域又分為域名、分隔符:、域值,而代碼中的主要結(jié)構可以分成三個大while,第一個while的作用上面將的一樣,為了濾除空行;第二個while篩選出所有的域名,并為每一個域名調(diào)用headers.addValue(byte[], int, int)生成對應的,用于存儲域名對應域值的對象MessageBytes headerValue;第三個while用于解析域值,并把該值賦給與域名對應的headerValue,其中又包含兩個while,第一個同樣是濾去空格或者制表符,第二個讀取數(shù)據(jù)直到頭域某一行的末尾,但這時又要分為兩種情況:1.該頭域的值是單行的,此時讀取到行末該行就算解析完畢;2.該頭域的值存在多行,如果存在這種情況,那么域值接續(xù)行的頭字節(jié)為空格或者制表符,當檢測到這種情況時要繼續(xù)循環(huán)提取內(nèi)容,并計算好數(shù)據(jù)塊真正的偏移位置
繼續(xù)代碼清單3,標注(4)對上面解析到的請求行和請求頭信息進行預處理,代碼清單6

protected void prepareRequest() {

    http11 = true;
    http09 = false;
    contentDelimitation = false;
    expectation = false;

    prepareRequestInternal();

    if (endpoint.isSSLEnabled()) {
        request.scheme().setString("https");
    }
    MessageBytes protocolMB = request.protocol();
    if (protocolMB.equals(Constants.HTTP_11)) {
        http11 = true;
        protocolMB.setString(Constants.HTTP_11);
    } else if (protocolMB.equals(Constants.HTTP_10)) {
        http11 = false;
        keepAlive = false;
        protocolMB.setString(Constants.HTTP_10);
    } else if (protocolMB.equals("")) {
        // HTTP/0.9
        http09 = true;
        http11 = false;
        keepAlive = false;
    } else {
        // Unsupported protocol
        http11 = false;
        // Send 505; Unsupported HTTP version
        response.setStatus(505);
        setErrorState(ErrorState.CLOSE_CLEAN, null);
        if (getLog().isDebugEnabled()) {
            getLog().debug(sm.getString("http11processor.request.prepare")+
                      " Unsupported HTTP version \""+protocolMB+"\"");
        }
    }

    MessageBytes methodMB = request.method();
    if (methodMB.equals(Constants.GET)) {
        methodMB.setString(Constants.GET);
    } else if (methodMB.equals(Constants.POST)) {
        methodMB.setString(Constants.POST);
    }

    MimeHeaders headers = request.getMimeHeaders();

    // Check connection header
    MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);
    if (connectionValueMB != null) {
        ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
        if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
            keepAlive = false;
        } else if (findBytes(connectionValueBC,
                             Constants.KEEPALIVE_BYTES) != -1) {
            keepAlive = true;
        }
    }

    MessageBytes expectMB = null;
    if (http11) {
        expectMB = headers.getValue("expect");
    }
    if (expectMB != null) {
        if (expectMB.indexOfIgnoreCase("100-continue", 0) != -1) {
            getInputBuffer().setSwallowInput(false);
            expectation = true;
        } else {
            response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
            setErrorState(ErrorState.CLOSE_CLEAN, null);
        }
    }

    // Check user-agent header
    if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
        MessageBytes userAgentValueMB = headers.getValue("user-agent");
        // Check in the restricted list, and adjust the http11
        // and keepAlive flags accordingly
        if(userAgentValueMB != null) {
            String userAgentValue = userAgentValueMB.toString();
            if (restrictedUserAgents.matcher(userAgentValue).matches()) {
                http11 = false;
                keepAlive = false;
            }
        }
    }

    // Check for a full URI (including protocol://host:port/)
    ByteChunk uriBC = request.requestURI().getByteChunk();
    if (uriBC.startsWithIgnoreCase("http", 0)) {

        int pos = uriBC.indexOf("://", 0, 3, 4);
        int uriBCStart = uriBC.getStart();
        int slashPos = -1;
        if (pos != -1) {
            byte[] uriB = uriBC.getBytes();
            slashPos = uriBC.indexOf('/', pos + 3);
            if (slashPos == -1) {
                slashPos = uriBC.getLength();
                // Set URI as "/"
                request.requestURI().setBytes
                    (uriB, uriBCStart + pos + 1, 1);
            } else {
                request.requestURI().setBytes
                    (uriB, uriBCStart + slashPos,
                     uriBC.getLength() - slashPos);
            }
            MessageBytes hostMB = headers.setValue("host");
            hostMB.setBytes(uriB, uriBCStart + pos + 3,
                            slashPos - pos - 3);
        }

    }

    // Input filter setup
    InputFilter[] inputFilters = getInputBuffer().getFilters();

    // Parse transfer-encoding header
    MessageBytes transferEncodingValueMB = null;
    if (http11) {
        transferEncodingValueMB = headers.getValue("transfer-encoding");
    }
    if (transferEncodingValueMB != null) {
        String transferEncodingValue = transferEncodingValueMB.toString();
        // Parse the comma separated list. "identity" codings are ignored
        int startPos = 0;
        int commaPos = transferEncodingValue.indexOf(',');
        String encodingName = null;
        while (commaPos != -1) {
            encodingName = transferEncodingValue.substring(startPos, commaPos);
            addInputFilter(inputFilters, encodingName);
            startPos = commaPos + 1;
            commaPos = transferEncodingValue.indexOf(',', startPos);
        }
        encodingName = transferEncodingValue.substring(startPos);
        addInputFilter(inputFilters, encodingName);
    }

    // Parse content-length header
    long contentLength = request.getContentLengthLong();
    if (contentLength >= 0) {
        if (contentDelimitation) {
            // contentDelimitation being true at this point indicates that
            // chunked encoding is being used but chunked encoding should
            // not be used with a content length. RFC 2616, section 4.4,
            // bullet 3 states Content-Length must be ignored in this case -
            // so remove it.
            headers.removeHeader("content-length");
            request.setContentLength(-1);
        } else {
            getInputBuffer().addActiveFilter
                    (inputFilters[Constants.IDENTITY_FILTER]);
            contentDelimitation = true;
        }
    }

    MessageBytes valueMB = headers.getValue("host");

    // Check host header
    if (http11 && (valueMB == null)) {
        // 400 - Bad request
        response.setStatus(400);
        setErrorState(ErrorState.CLOSE_CLEAN, null);
        if (getLog().isDebugEnabled()) {
            getLog().debug(sm.getString("http11processor.request.prepare")+
                      " host header missing");
        }
    }

    parseHost(valueMB);

    if (!contentDelimitation) {
        // If there's no content length
        // (broken HTTP/1.0 or HTTP/1.1), assume
        // the client is not broken and didn't send a body
        getInputBuffer().addActiveFilter
                (inputFilters[Constants.VOID_FILTER]);
        contentDelimitation = true;
    }

    // Advertise sendfile support through a request attribute
    if (endpoint.getUseSendfile()) {
        request.setAttribute(
                org.apache.coyote.Constants.SENDFILE_SUPPORTED_ATTR,
                Boolean.TRUE);
    }

    // Advertise comet support through a request attribute
    if (endpoint.getUseComet()) {
        request.setAttribute(
                org.apache.coyote.Constants.COMET_SUPPORTED_ATTR,
                Boolean.TRUE);
    }
    // Advertise comet timeout support
    if (endpoint.getUseCometTimeout()) {
        request.setAttribute(
                org.apache.coyote.Constants.COMET_TIMEOUT_SUPPORTED_ATTR,
                Boolean.TRUE);
    }
    if (getErrorState().isError()) {
        adapter.log(request, response, 0);
    }
}

預處理的主要作用是在調(diào)用Container容器進行真正處理前,先對請求行和請求頭中異常的或者無法處理的信息設置對應的錯誤碼,比如對于請求行中的協(xié)議來說,Tomcat7能處理的就是Http1.1、1.0和0.9三個版本,當程序發(fā)現(xiàn)解析出的協(xié)議不在三者之中,就會設置響應碼為505。再比如代碼中會判斷user-agent頭域,如果域值符合受限user-agent正則表達式,那么該user-agent就無法正常訪問
當經(jīng)過上述所有步驟,最后會調(diào)用代碼清單3標注(5)處代碼,調(diào)用CoyoteAdapter.service(org.apache.coyote.Request, org.apache.coyote.Response)代碼清單7

public void service(org.apache.coyote.Request req,
                    org.apache.coyote.Response res)
    throws Exception {
    //    (1)
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    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);
        response.setRequest(request);

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

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

    }

    if (connector.getXpoweredBy()) {
        response.addHeader("X-Powered-By", POWERED_BY);
    }

    boolean comet = false;
    boolean async = false;
    boolean postParseSuccess = false;

    try {
        // Parse and set Catalina and configuration specific
        // request parameters
        req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
        //    (2)
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            //check valves if we support async
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());

            // Calling the container
            //    (3)
            connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

            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_READ)) {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        } else {
                            return;
                        }
                    } 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);
                }
            }

        }
        if (request.isAsync()) {
            async = true;
            Throwable throwable =
                    (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

            // If an async request was started, is not going to end once
            // this container thread finishes and an error occurred, trigger
            // the async error process
            if (!request.isAsyncCompleting() && throwable != null) {
                request.getAsyncContextInternal().setErrorState(throwable, true);
            }
        } else if (!comet) {
            try {
                request.finishRequest();
                response.finishResponse();
            } finally {
                if (postParseSuccess) {
                    // 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);
        AtomicBoolean error = new AtomicBoolean(false);
        res.action(ActionCode.IS_ERROR, error);

        // Recycle the wrapper request and response
        if (!comet && !async || error.get()) {
            request.recycle();
            response.recycle();
        } else {
            // Clear converters so that the minimum amount of memory
            // is used by this processor
            request.clearEncoders();
            response.clearEncoders();
        }
    }
}

上面曾今說過org.apache.coyote.Requestorg.apache.coyote.Response是第一層次的請求響應,并不是我們通常應用層接觸的請求響應,而在service方法中生成了“真正”意義上的請求響應,我稱之為第二層次的請求響應。標注(1)處代碼首先從org.apache.coyote包下的request內(nèi)部一個8位的數(shù)組note[]中取出第一位的對象,強轉(zhuǎn)成Request對象(為什么要這么設計我也不理解)。第一次獲取該對象必為null,進入if代碼塊中由Connector創(chuàng)建出requestresponse,建立兩者的雙向關聯(lián),以及和第一層次請求響應的對應關聯(lián),最后將生成的請求響應放入8位的note[]對應位
標注(2)的代碼非常重要,該方法內(nèi)部根據(jù)解析的請求信息對應到正確的ContextWrapper代碼清單8

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;
    }

    // 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();
    }

    // Version for the second mapping loop and
    // Context that we expect to get for that version
    String version = null;
    Context versionContext = null;
    boolean mapRequired = true;

    while (mapRequired) {
        // This will map the the latest version by default
        //    (1)
        connector.getMapper().map(serverName, decodedURI, version,
                                  request.getMappingData());

        request.setContext((Context) request.getMappingData().context);
        request.setWrapper((Wrapper) request.getMappingData().wrapper);

        // 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;
        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();

        mapRequired = false;
        if (version != null && request.getContext() == versionContext) {
            // We got the version that we asked for. That is it.
        } else {
            version = null;
            versionContext = null;

            Object[] contexts = request.getMappingData().contexts;
            // Single contextVersion means no need to remap
            // No session ID means no possibility of remap
            if (contexts != null && sessionID != null) {
                // Find the context associated with the session
                for (int i = (contexts.length); i > 0; i--) {
                    Context ctxt = (Context) contexts[i - 1];
                    if (ctxt.getManager().findSession(sessionID) != null) {
                        // We found a context. Is it the one that has
                        // already been mapped?
                        if (!ctxt.equals(request.getMappingData().context)) {
                            // Set version so second time through mapping
                            // the correct context is found
                            version = ctxt.getWebappVersion();
                            versionContext = ctxt;
                            // Reset mapping
                            request.getMappingData().recycle();
                            mapRequired = true;
                            // Recycle session info in case the correct
                            // context is configured with different settings
                            request.recycleSessionInfo();
                        }
                        break;
                    }
                }
            }
        }

        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(), "UTF-8");
        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;
    }

    doConnectorAuthenticationAuthorization(req, request);

    return true;
}

根據(jù)請求信息映射對應容器的核心代碼在標注(1)處,在Tomcat的生命周期(三)中,我們曾今分析過所有的組件實體和組件間關系都保存在ConnectorMapper中,標注(1)處代碼最終會調(diào)用Mapper.internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData),如代碼清單9

private final void internalMap(CharChunk host, CharChunk uri,
        String version, MappingData mappingData) throws Exception {

    if (mappingData.host != null) {
        // The legacy code (dating down at least to Tomcat 4.1) just
        // skipped all mapping work in this case. That behaviour has a risk
        // of returning an inconsistent result.
        // I do not see a valid use case for it.
        throw new AssertionError();
    }

    uri.setLimit(-1);

    // Virtual host mapping
    //    (1)
    Host[] hosts = this.hosts;
    Host mappedHost = exactFindIgnoreCase(hosts, host);
    if (mappedHost == null) {
        if (defaultHostName == null) {
            return;
        }
        mappedHost = exactFind(hosts, defaultHostName);
        if (mappedHost == null) {
            return;
        }
    }
    mappingData.host = mappedHost.object;

    // Context mapping
    //    (2)
    ContextList contextList = mappedHost.contextList;
    Context[] contexts = contextList.contexts;
    int nesting = contextList.nesting;

    int pos = find(contexts, uri);
    if (pos == -1) {
        return;
    }

    int lastSlash = -1;
    int uriEnd = uri.getEnd();
    int length = -1;
    boolean found = false;
    Context context = null;
    while (pos >= 0) {
        context = contexts[pos];
        if (uri.startsWith(context.name)) {
            length = context.name.length();
            if (uri.getLength() == length) {
                found = true;
                break;
            } else if (uri.startsWithIgnoreCase("/", length)) {
                found = true;
                break;
            }
        }
        if (lastSlash == -1) {
            lastSlash = nthSlash(uri, nesting + 1);
        } else {
            lastSlash = lastSlash(uri);
        }
        uri.setEnd(lastSlash);
        pos = find(contexts, uri);
    }
    uri.setEnd(uriEnd);

    if (!found) {
        if (contexts[0].name.equals("")) {
            context = contexts[0];
        } else {
            context = null;
        }
    }
    if (context == null) {
        return;
    }

    mappingData.contextPath.setString(context.name);
    //    (3)
    ContextVersion contextVersion = null;
    ContextVersion[] contextVersions = context.versions;
    final int versionCount = contextVersions.length;
    if (versionCount > 1) {
        Object[] contextObjects = new Object[contextVersions.length];
        for (int i = 0; i < contextObjects.length; i++) {
            contextObjects[i] = contextVersions[i].object;
        }
        mappingData.contexts = contextObjects;
        if (version != null) {
            contextVersion = exactFind(contextVersions, version);
        }
    }
    if (contextVersion == null) {
        // Return the latest version
        // The versions array is known to contain at least one element
        contextVersion = contextVersions[versionCount - 1];
    }

    mappingData.context = contextVersion.object;
    mappingData.contextSlashCount = contextVersion.slashCount;

    // Wrapper mapping
    if (!contextVersion.isPaused()) {
        //    (4)
        internalMapWrapper(contextVersion, uri, mappingData);
    }

}

先簡單說一下四個參數(shù)的意義,第一第二個很好理解,分別對應請求的host和經(jīng)過URI解碼的URI,第三個參數(shù)表示請求對應ContextVersion的版本,最后一個參數(shù)是在第二層次request中一個成員變量mappingData,當代碼執(zhí)行完后該對象中會包含本次請求對應的所有容器組件。結(jié)合之前Mapper中元素的結(jié)構和上述代碼,我們將邏輯分成4個部分,分別對應Host映射、Context映射、ContextVersion映射和Wrapper映射,正好對應4個標注
標注(1)首先找到host對應Mapper中的映射mappedHost,如果沒有找到對應的映射主機,則使用默認主機。標注(2)從mappedHost中的ContextList,進而得到該對象中的Context[],然后根據(jù)參數(shù)uri找到對應的Context的下標,如果沒有找到默認采用Context[]的首元素。標注(3)根據(jù)參數(shù)version從上一步定位的Context.ContextVersion[]中再定位對應的ContextVersion,并將ContextVersion賦值給mappingData中對應的變量,代碼清單10

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

    int pathOffset = path.getOffset();
    int pathEnd = path.getEnd();
    boolean noServletPath = false;

    int length = contextVersion.path.length();
    if (length == (pathEnd - pathOffset)) {
        noServletPath = true;
    }
    int servletPath = pathOffset + length;
    path.setOffset(servletPath);

    // Rule 1 -- Exact Match
    Wrapper[] exactWrappers = contextVersion.exactWrappers;
    internalMapExactWrapper(exactWrappers, path, mappingData);

    // Rule 2 -- Prefix Match
    boolean checkJspWelcomeFiles = false;
    Wrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
    if (mappingData.wrapper == null) {
        internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
                                   path, mappingData);
        if (mappingData.wrapper != null && mappingData.jspWildCard) {
            char[] buf = path.getBuffer();
            if (buf[pathEnd - 1] == '/') {
                /*
                 * Path ending in '/' was mapped to JSP servlet based on
                 * wildcard match (e.g., as specified in url-pattern of a
                 * jsp-property-group.
                 * Force the context's welcome files, which are interpreted
                 * as JSP files (since they match the url-pattern), to be
                 * considered. See Bugzilla 27664.
                 */
                mappingData.wrapper = null;
                checkJspWelcomeFiles = true;
            } else {
                // See Bugzilla 27704
                mappingData.wrapperPath.setChars(buf, path.getStart(),
                                                 path.getLength());
                mappingData.pathInfo.recycle();
            }
        }
    }

    if(mappingData.wrapper == null && noServletPath &&
            contextVersion.mapperContextRootRedirectEnabled) {
        // The path is empty, redirect to "/"
        path.append('/');
        pathEnd = path.getEnd();
        mappingData.redirectPath.setChars
            (path.getBuffer(), pathOffset, pathEnd - pathOffset);
        path.setEnd(pathEnd - 1);
        return;
    }

    // Rule 3 -- Extension Match
    Wrapper[] extensionWrappers = contextVersion.extensionWrappers;
    if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
        internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                true);
    }

    // Rule 4 -- Welcome resources processing for servlets
    if (mappingData.wrapper == null) {
        boolean checkWelcomeFiles = checkJspWelcomeFiles;
        if (!checkWelcomeFiles) {
            char[] buf = path.getBuffer();
            checkWelcomeFiles = (buf[pathEnd - 1] == '/');
        }
        if (checkWelcomeFiles) {
            for (int i = 0; (i < contextVersion.welcomeResources.length)
                     && (mappingData.wrapper == null); i++) {
                path.setOffset(pathOffset);
                path.setEnd(pathEnd);
                path.append(contextVersion.welcomeResources[i], 0,
                        contextVersion.welcomeResources[i].length());
                path.setOffset(servletPath);

                // Rule 4a -- Welcome resources processing for exact macth
                internalMapExactWrapper(exactWrappers, path, mappingData);

                // Rule 4b -- Welcome resources processing for prefix match
                if (mappingData.wrapper == null) {
                    internalMapWildcardWrapper
                        (wildcardWrappers, contextVersion.nesting,
                         path, mappingData);
                }

                // Rule 4c -- Welcome resources processing
                //            for physical folder
                if (mappingData.wrapper == null
                    && contextVersion.resources != null) {
                    Object file = null;
                    String pathStr = path.toString();
                    try {
                        file = contextVersion.resources.lookup(pathStr);
                    } catch(NamingException nex) {
                        // Swallow not found, since this is normal
                    }
                    if (file != null && !(file instanceof DirContext) ) {
                        internalMapExtensionWrapper(extensionWrappers, path,
                                                    mappingData, true);
                        if (mappingData.wrapper == null
                            && contextVersion.defaultWrapper != null) {
                            mappingData.wrapper =
                                contextVersion.defaultWrapper.object;
                            mappingData.requestPath.setChars
                                (path.getBuffer(), path.getStart(),
                                 path.getLength());
                            mappingData.wrapperPath.setChars
                                (path.getBuffer(), path.getStart(),
                                 path.getLength());
                            mappingData.requestPath.setString(pathStr);
                            mappingData.wrapperPath.setString(pathStr);
                        }
                    }
                }
            }

            path.setOffset(servletPath);
            path.setEnd(pathEnd);
        }

    }

    /* welcome file processing - take 2
     * Now that we have looked for welcome files with a physical
     * backing, now look for an extension mapping listed
     * but may not have a physical backing to it. This is for
     * the case of index.jsf, index.do, etc.
     * A watered down version of rule 4
     */
    if (mappingData.wrapper == null) {
        boolean checkWelcomeFiles = checkJspWelcomeFiles;
        if (!checkWelcomeFiles) {
            char[] buf = path.getBuffer();
            checkWelcomeFiles = (buf[pathEnd - 1] == '/');
        }
        if (checkWelcomeFiles) {
            for (int i = 0; (i < contextVersion.welcomeResources.length)
                     && (mappingData.wrapper == null); i++) {
                path.setOffset(pathOffset);
                path.setEnd(pathEnd);
                path.append(contextVersion.welcomeResources[i], 0,
                            contextVersion.welcomeResources[i].length());
                path.setOffset(servletPath);
                internalMapExtensionWrapper(extensionWrappers, path,
                                            mappingData, false);
            }

            path.setOffset(servletPath);
            path.setEnd(pathEnd);
        }
    }


    // Rule 7 -- Default servlet
    if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
        if (contextVersion.defaultWrapper != null) {
            mappingData.wrapper = contextVersion.defaultWrapper.object;
            mappingData.requestPath.setChars
                (path.getBuffer(), path.getStart(), path.getLength());
            mappingData.wrapperPath.setChars
                (path.getBuffer(), path.getStart(), path.getLength());
        }
        // Redirection to a folder
        char[] buf = path.getBuffer();
        if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
            Object file = null;
            String pathStr = path.toString();
            try {
                if (pathStr.length() == 0) {
                    file = contextVersion.resources.lookup("/");
                } else {
                    file = contextVersion.resources.lookup(pathStr);
                }
            } catch(NamingException nex) {
                // Swallow, since someone else handles the 404
            }
            if (file != null && file instanceof DirContext &&
                    contextVersion.mapperDirectoryRedirectEnabled) {
                // Note: this mutates the path: do not do any processing
                // after this (since we set the redirectPath, there
                // shouldn't be any)
                path.setOffset(pathOffset);
                path.append('/');
                mappingData.redirectPath.setChars
                    (path.getBuffer(), path.getStart(), path.getLength());
            } else {
                mappingData.requestPath.setString(pathStr);
                mappingData.wrapperPath.setString(pathStr);
            }
        }
    }

    path.setOffset(pathOffset);
    path.setEnd(pathEnd);
}

Tomcat的生命周期(三)中曾今說過,根據(jù)web.xml<servlet-mapping>的子標簽<url-pattern>的不同,將Wrapper放在Mapper.ContextVersion中不同的Wrapper[]中,那么自然地,在尋找URI對應的Wrapper時肯定也要根據(jù)路徑的不同類型從對應的數(shù)組中得到正確的Wrapper,上面的代碼就做了這件事,這里不再做具體分析了,有興趣的讀者可以按著本文的分析思路深入研讀
至此,請求已經(jīng)“找到了”應該請求的每一個組件,代碼清單7中的標注(3)將請求響應對象傳給StandardEngineValve.invoke(Request, response),順著每個組件的“管道”依次處理,具體處理過程請聽下回分解

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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