Tomcat請求響應(yīng)處理(一)

前言
之前的文章分別從Tomcat的兩個(gè)部分:ContainerConnector對其組件間關(guān)系和生命周期狀態(tài)的流轉(zhuǎn)進(jìn)行了分析。兩大部分作為Tomcat運(yùn)行的基石保證了請求響應(yīng)的高效處理及準(zhǔn)確分發(fā)。從本篇文章開始,我們進(jìn)入Tomcat處理請求原理的流程分析奸汇,該部分內(nèi)容準(zhǔn)備用兩篇文章闡述拆挥,本文是其中的前半部分,著重分析Tomcat是如何根據(jù)請求生成對應(yīng)的requestresponse送膳,又如何根據(jù)請求參數(shù)路徑映射到對應(yīng)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
    }

}

在標(biāo)注(1)中將socket的包裝類SocketWrapper交給成員變量handler進(jìn)一步處理,該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;
}

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

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

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

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

super就是調(diào)用父類AbstractProcessor的構(gòu)造器肥卡,InternalInputBuffer就是輸入流buffer,對應(yīng)的InternalOutputBuffer就是輸出流buffer事镣,最后一句用于設(shè)置輸入輸出過濾步鉴,比如上面說的Http請求體的最大大小就是過濾條件之一。我們回到代碼清單2繼續(xù)看標(biāo)注(3)處璃哟,進(jìn)入真正處理socket流程氛琢,代碼實(shí)際上調(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;
            }
        }
    }
}

標(biāo)注(1)處首先將SocketWrapper與當(dāng)前processor進(jìn)行關(guān)聯(lián)随闪,然后初始化輸入輸出流緩沖類阳似,init(SocketWrapper, AbstractEndpoint)實(shí)際上是將socket內(nèi)部的輸入輸出流賦值給了輸入輸出緩沖類中的輸入輸出流。標(biāo)注(2)對請求頭進(jìn)行解析铐伴,代碼清單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ù)組讀取再賦值的方式進(jìn)行的撮奏,雖然看上去很雜亂,但我們仔細(xì)想一想請求行的三大組成部分:請求方式当宴;請求路徑畜吊;請求協(xié)議及版本,再看看對應(yīng)代碼的分割就會(huì)發(fā)現(xiàn)即供,代碼塊與請求三大部分近乎一一對應(yīng)定拟。標(biāo)注(1)首先排除了一些在請求行之前的空行,標(biāo)注(2)解析了請求方式逗嫡,當(dāng)遍歷到的指針pos對應(yīng)的字節(jié)為' '或者\t(三部分之間以空格或制表符隔開)青自,說明請求方式截取結(jié)束,將數(shù)據(jù)塊中的請求方式內(nèi)容賦值給request中的method驱证。標(biāo)注(3)提取請求URI信息延窜,除了和提取請求方式有著相同的邏輯之外,URI還多了個(gè)兩個(gè)判斷:1. 如果遍歷到字節(jié)為\r或者\n抹锄,則請求協(xié)議和版本為Http/0.9;2. 如果字節(jié)為?說明遍歷到了URI后面跟的參數(shù)逆瑞,記錄當(dāng)前位置為questionPos。一切準(zhǔn)備就緒才進(jìn)行URI和queryString的提取賦值伙单。最后一部分標(biāo)注(4)自然就是對協(xié)議及版本號的解析賦值過程获高,當(dāng)然如果是HTTP協(xié)議的0.9版本,在第三部分會(huì)將結(jié)束標(biāo)志位eol置為false吻育,就不用再解析了念秧,其對應(yīng)字段為request中的protocol
回到代碼清單3看標(biāo)注(3)對請求頭進(jìn)行解析方法parseHeaders(),最終會(huì)調(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;

}

請求頭由多個(gè)請求頭域組成摊趾,每一個(gè)請求頭域又分為域名币狠、分隔符:、域值砾层,而代碼中的主要結(jié)構(gòu)可以分成三個(gè)大while漩绵,第一個(gè)while的作用上面將的一樣,為了濾除空行肛炮;第二個(gè)while篩選出所有的域名止吐,并為每一個(gè)域名調(diào)用headers.addValue(byte[], int, int)生成對應(yīng)的,用于存儲(chǔ)域名對應(yīng)域值的對象MessageBytes headerValue铸董;第三個(gè)while用于解析域值祟印,并把該值賦給與域名對應(yīng)的headerValue,其中又包含兩個(gè)while粟害,第一個(gè)同樣是濾去空格或者制表符蕴忆,第二個(gè)讀取數(shù)據(jù)直到頭域某一行的末尾,但這時(shí)又要分為兩種情況:1.該頭域的值是單行的悲幅,此時(shí)讀取到行末該行就算解析完畢套鹅;2.該頭域的值存在多行,如果存在這種情況汰具,那么域值接續(xù)行的頭字節(jié)為空格或者制表符卓鹿,當(dāng)檢測到這種情況時(shí)要繼續(xù)循環(huán)提取內(nèi)容,并計(jì)算好數(shù)據(jù)塊真正的偏移位置
繼續(xù)代碼清單3留荔,標(biāo)注(4)對上面解析到的請求行和請求頭信息進(jìn)行預(yù)處理吟孙,代碼清單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);
    }
}

預(yù)處理的主要作用是在調(diào)用Container容器進(jìn)行真正處理前,先對請求行和請求頭中異常的或者無法處理的信息設(shè)置對應(yīng)的錯(cuò)誤碼聚蝶,比如對于請求行中的協(xié)議來說杰妓,Tomcat7能處理的就是Http1.1、1.0和0.9三個(gè)版本碘勉,當(dāng)程序發(fā)現(xiàn)解析出的協(xié)議不在三者之中巷挥,就會(huì)設(shè)置響應(yīng)碼為505。再比如代碼中會(huì)判斷user-agent頭域验靡,如果域值符合受限user-agent正則表達(dá)式倍宾,那么該user-agent就無法正常訪問
當(dāng)經(jīng)過上述所有步驟,最后會(huì)調(diào)用代碼清單3標(biāo)注(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是第一層次的請求響應(yīng),并不是我們通常應(yīng)用層接觸的請求響應(yīng)辞州,而在service方法中生成了“真正”意義上的請求響應(yīng)怔锌,我稱之為第二層次的請求響應(yīng)。標(biāo)注(1)處代碼首先從org.apache.coyote包下的request內(nèi)部一個(gè)8位的數(shù)組note[]中取出第一位的對象,強(qiáng)轉(zhuǎn)成Request對象(為什么要這么設(shè)計(jì)我也不理解)产禾。第一次獲取該對象必為null,進(jìn)入if代碼塊中由Connector創(chuàng)建出requestresponse牵啦,建立兩者的雙向關(guān)聯(lián)亚情,以及和第一層次請求響應(yīng)的對應(yīng)關(guān)聯(lián),最后將生成的請求響應(yīng)放入8位的note[]對應(yīng)位
標(biāo)注(2)的代碼非常重要哈雏,該方法內(nèi)部根據(jù)解析的請求信息對應(yīng)到正確的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ù)請求信息映射對應(yīng)容器的核心代碼在標(biāo)注(1)處,在Tomcat的生命周期(三)中裳瘪,我們曾今分析過所有的組件實(shí)體和組件間關(guān)系都保存在ConnectorMapper中土浸,標(biāo)注(1)處代碼最終會(huì)調(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);
    }

}

先簡單說一下四個(gè)參數(shù)的意義彭羹,第一第二個(gè)很好理解黄伊,分別對應(yīng)請求的host和經(jīng)過URI解碼的URI,第三個(gè)參數(shù)表示請求對應(yīng)ContextVersion的版本派殷,最后一個(gè)參數(shù)是在第二層次request中一個(gè)成員變量mappingData还最,當(dāng)代碼執(zhí)行完后該對象中會(huì)包含本次請求對應(yīng)的所有容器組件。結(jié)合之前Mapper中元素的結(jié)構(gòu)和上述代碼毡惜,我們將邏輯分成4個(gè)部分拓轻,分別對應(yīng)Host映射、Context映射经伙、ContextVersion映射和Wrapper映射扶叉,正好對應(yīng)4個(gè)標(biāo)注
標(biāo)注(1)首先找到host對應(yīng)Mapper中的映射mappedHost,如果沒有找到對應(yīng)的映射主機(jī)帕膜,則使用默認(rèn)主機(jī)枣氧。標(biāo)注(2)從mappedHost中的ContextList,進(jìn)而得到該對象中的Context[]泳叠,然后根據(jù)參數(shù)uri找到對應(yīng)的Context的下標(biāo)作瞄,如果沒有找到默認(rèn)采用Context[]的首元素。標(biāo)注(3)根據(jù)參數(shù)version從上一步定位的Context.ContextVersion[]中再定位對應(yīng)的ContextVersion危纫,并將ContextVersion賦值給mappingData中對應(yīng)的變量宗挥,代碼清單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>的子標(biāo)簽<url-pattern>的不同种蝶,將Wrapper放在Mapper.ContextVersion中不同的Wrapper[]中契耿,那么自然地,在尋找URI對應(yīng)的Wrapper時(shí)肯定也要根據(jù)路徑的不同類型從對應(yīng)的數(shù)組中得到正確的Wrapper螃征,上面的代碼就做了這件事搪桂,這里不再做具體分析了,有興趣的讀者可以按著本文的分析思路深入研讀
至此,請求已經(jīng)“找到了”應(yīng)該請求的每一個(gè)組件踢械,代碼清單7中的標(biāo)注(3)將請求響應(yīng)對象傳給StandardEngineValve.invoke(Request, response)酗电,順著每個(gè)組件的“管道”依次處理,具體處理過程請聽下回分解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末内列,一起剝皮案震驚了整個(gè)濱河市撵术,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌话瞧,老刑警劉巖嫩与,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異交排,居然都是意外死亡划滋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門埃篓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來处坪,“玉大人,你說我怎么就攤上這事架专〉巨保” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵胶征,是天一觀的道長塞椎。 經(jīng)常有香客問我,道長睛低,這世上最難降的妖魔是什么案狠? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮钱雷,結(jié)果婚禮上骂铁,老公的妹妹穿的比我還像新娘。我一直安慰自己罩抗,他們只是感情好拉庵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著套蒂,像睡著了一般钞支。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上操刀,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天烁挟,我揣著相機(jī)與錄音,去河邊找鬼骨坑。 笑死撼嗓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播且警,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼粉捻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斑芜?” 一聲冷哼從身側(cè)響起杀迹,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎押搪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浅碾,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡大州,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垂谢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厦画。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖滥朱,靈堂內(nèi)的尸體忽然破棺而出根暑,到底是詐尸還是另有隱情,我是刑警寧澤徙邻,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布排嫌,位于F島的核電站,受9級特大地震影響缰犁,放射性物質(zhì)發(fā)生泄漏淳地。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一帅容、第九天 我趴在偏房一處隱蔽的房頂上張望颇象。 院中可真熱鬧,春花似錦并徘、人聲如沸遣钳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕴茴。三九已至,卻和暖如春姐直,著一層夾襖步出監(jiān)牢的瞬間荐开,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工简肴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晃听,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像能扒,于是被迫代替她去往敵國和親佣渴。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

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