Tomcat请求响应处理(一)

前言
之前的文章分别从Tomcat的两个部分:ContainerConnector对其组件间关系和生命周期状态的流转进行了分析。两大部分作为Tomcat运行的基石保证了请求响应的高效处理及准确分发。从本篇文章开始,我们进入Tomcat处理请求原理的流程分析,该部分内容准备用两篇文章阐述,本文是其中的前半部分,着重分析Tomcat是如何根据请求生成对应的requestresponse,又如何根据请求参数路径映射到对应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内部类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)首先根据SocketWrapper从类型为ConcurrentHashMap的缓存中查找是否有对应SocketWrapperProcessor,如果从缓存池和可循环利用的recycledProcessors中都没有合适的处理器,就会调用createProcessor()创建,不同的请求方式对应不同的处理器类,BIO方式对应的为Http11Processor

图1. Http11Protocol内部类Http11ConnectionHandler的createProcessor()

图中传了一堆参数构建了Http11Processor实例,其中第一个参数设置了最大Http请求头大小为8M,最后一个参数设置了默认情况下请求体最大大小为20M,倒数第三句代码设置处理最多200个cookie,除此以外CoyoteAdapterJIoEndpoint实例都与Http11Processor建立了关联,在调用Http11Processor构造器时首先调用了其顶层父类的构造器
图2. AbstractProcessor的构造器

图中看到创建了requestresponse对象,并且建立了两者的关联,但是这一对请求响应仅仅是处理过程中第一对请求响应,我称之为第一层次的请求响应,request中使用一个个MessageBytes对象将请求头中各个部分进行分门别类的保存,而requestresponse中各自引用的输入、输出流buffer就作为了socket与对象之间的数据传输的纽带,我们再来看看AbstractProcessor的实现类Http11Processor的构造方法
图3. Http11Processor的构造器

super就是调用父类AbstractProcessor的构造器,InternalInputBuffer就是输入流buffer,对应的InternalOutputBuffer就是输出流buffer,最后一句用于设置输入输出过滤,比如上面说的Http请求体的最大大小就是过滤条件之一。我们回到代码清单2继续看标注(3)处,进入真正处理socket流程,代码实际上调用了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进行关联,然后初始化输入输出流缓冲类,init(SocketWrapper, AbstractEndpoint)实际上是将socket内部的输入输出流赋值给了输入输出缓冲类中的输入输出流。标注(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对于请求中信息的提取基本都是采用如上偏移量加字节数组读取再赋值的方式进行的,虽然看上去很杂乱,但我们仔细想一想请求行的三大组成部分:请求方式;请求路径;请求协议及版本,再看看对应代码的分割就会发现,代码块与请求三大部分近乎一一对应。标注(1)首先排除了一些在请求行之前的空行,标注(2)解析了请求方式,当遍历到的指针pos对应的字节为' '或者\t(三部分之间以空格或制表符隔开),说明请求方式截取结束,将数据块中的请求方式内容赋值给request中的method。标注(3)提取请求URI信息,除了和提取请求方式有着相同的逻辑之外,URI还多了个两个判断:1. 如果遍历到字节为\r或者\n,则请求协议和版本为Http/0.9;2. 如果字节为?说明遍历到了URI后面跟的参数,记录当前位置为questionPos。一切准备就绪才进行URI和queryString的提取赋值。最后一部分标注(4)自然就是对协议及版本号的解析赋值过程,当然如果是HTTP协议的0.9版本,在第三部分会将结束标志位eol置为false,就不用再解析了,其对应字段为request中的protocol
回到代码清单3看标注(3)对请求头进行解析方法parseHeaders(),最终会调用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;

}

请求头由多个请求头域组成,每一个请求头域又分为域名、分隔符:、域值,而代码中的主要结构可以分成三个大while,第一个while的作用上面将的一样,为了滤除空行;第二个while筛选出所有的域名,并为每一个域名调用headers.addValue(byte[], int, int)生成对应的,用于存储域名对应域值的对象MessageBytes headerValue;第三个while用于解析域值,并把该值赋给与域名对应的headerValue,其中又包含两个while,第一个同样是滤去空格或者制表符,第二个读取数据直到头域某一行的末尾,但这时又要分为两种情况:1.该头域的值是单行的,此时读取到行末该行就算解析完毕;2.该头域的值存在多行,如果存在这种情况,那么域值接续行的头字节为空格或者制表符,当检测到这种情况时要继续循环提取内容,并计算好数据块真正的偏移位置
继续代码清单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);
    }
}

预处理的主要作用是在调用Container容器进行真正处理前,先对请求行和请求头中异常的或者无法处理的信息设置对应的错误码,比如对于请求行中的协议来说,Tomcat7能处理的就是Http1.1、1.0和0.9三个版本,当程序发现解析出的协议不在三者之中,就会设置响应码为505。再比如代码中会判断user-agent头域,如果域值符合受限user-agent正则表达式,那么该user-agent就无法正常访问
当经过上述所有步骤,最后会调用代码清单3标注(5)处代码,调用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内部一个8位的数组note[]中取出第一位的对象,强转成Request对象(为什么要这么设计我也不理解)。第一次获取该对象必为null,进入if代码块中由Connector创建出requestresponse,建立两者的双向关联,以及和第一层次请求响应的对应关联,最后将生成的请求响应放入8位的note[]对应位
标注(2)的代码非常重要,该方法内部根据解析的请求信息对应到正确的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;
}

根据请求信息映射对应容器的核心代码在标注(1)处,在Tomcat的生命周期(三)中,我们曾今分析过所有的组件实体和组件间关系都保存在ConnectorMapper中,标注(1)处代码最终会调用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);
    }

}

先简单说一下四个参数的意义,第一第二个很好理解,分别对应请求的host和经过URI解码的URI,第三个参数表示请求对应ContextVersion的版本,最后一个参数是在第二层次request中一个成员变量mappingData,当代码执行完后该对象中会包含本次请求对应的所有容器组件。结合之前Mapper中元素的结构和上述代码,我们将逻辑分成4个部分,分别对应Host映射、Context映射、ContextVersion映射和Wrapper映射,正好对应4个标注
标注(1)首先找到host对应Mapper中的映射mappedHost,如果没有找到对应的映射主机,则使用默认主机。标注(2)从mappedHost中的ContextList,进而得到该对象中的Context[],然后根据参数uri找到对应的Context的下标,如果没有找到默认采用Context[]的首元素。标注(3)根据参数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的生命周期(三)中曾今说过,根据web.xml<servlet-mapping>的子标签<url-pattern>的不同,将Wrapper放在Mapper.ContextVersion中不同的Wrapper[]中,那么自然地,在寻找URI对应的Wrapper时肯定也要根据路径的不同类型从对应的数组中得到正确的Wrapper,上面的代码就做了这件事,这里不再做具体分析了,有兴趣的读者可以按着本文的分析思路深入研读
至此,请求已经“找到了”应该请求的每一个组件,代码清单7中的标注(3)将请求响应对象传给StandardEngineValve.invoke(Request, response),顺着每个组件的“管道”依次处理,具体处理过程请听下回分解

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容