• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Tomcat 7 的一次请求分析(2):Socket 转换成内部请求对象

Tomcat winrains 来源:预流 1年前 (2019-10-29) 49次浏览
先抛开之前所看到的 Tomcat 源码不谈,Tomcat 作为一个用 Java 实现的 Web 服务器,如果让你来实现,那么从何入手?
这里首先需要厘清的是 Web 服务器的概念,谷歌了一下,发现这条解释还算靠谱点,【在网络环境下可以向发出请求的浏览器提供文档的程序】。重点有两条:1.网络环境下,2.能够给出响应。用 Java 写过网络通信程序的都知道,这里必然会用到 Socket 编程。我们自己要实现的服务器程序作为 Socket 编程里的服务端,浏览器作为 Socket 编程里的客户端。
要理解 Tomcat 原理,Socket 编程这块的基本原理必须得了解,google 一把一大堆,这里不再单独做介绍。下面给出一个服务器端最简单的响应客户端请求的伪代码示例:
ServerSocket serverSocket  = new ServerSocket(8080, 1,
        InetAddress.getByName(“localhost”));
Socket socket = null;
InputStream is = null;
OutputStream os = null;
try {
    socket = serverSocket.accept();//1.监听到客户端的连接
    is = socket.getInputStream();
    os = socket.getOutputStream();
    Request request = Util.getRequest(is);//2.从输入流中读取数据,并根据Http协议转换成请求
    Response response = Util.service(request);//服务器内部根据请求信息给出响应信息
    os.writeResponse(response);//3.将响应信息写到输出流
} catch (Exception e) {
    e.printStackTrace();
} finally {//4.关闭输入输出流及连接
    if (is != null) {
        is.close();
    }
    if (os != null) {
        os.close();
    }
    socket.close();
}

浏览器和 Web 服务器的一次交互过程分四步:连接、请求、响应、关闭。前一篇文章分析到的接收器线程,如前文开始截图里的 http-bio-8080-Acceptor-0 ,这个线程的实现类org.apache.tomcat.util.net.JIoEndpoint.Acceptor,源码如下:

// --------------------------------------------------- Acceptor Inner Class
/**
 * The background thread that listens for incoming TCP/IP connections and
 * hands them off to an appropriate processor.
 */
protected class Acceptor extends AbstractEndpoint.Acceptor {
    @Override
    public void run() {
        int errorDelay = 0;
        // Loop until we receive a shutdown command
        while (running) {
            // Loop if endpoint is paused
            while (paused && running) {
                state = AcceptorState.PAUSED;
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
            if (!running) {
                break;
            }
            state = AcceptorState.RUNNING;
            try {
                //if we have reached max connections, wait
                countUpOrAwaitConnection();
                Socket socket = null;
                try {
                    // Accept the next incoming connection from the server
                    // socket
                    socket = serverSocketFactory.acceptSocket(serverSocket);
                } catch (IOException ioe) {
                    countDownConnection();
                    // Introduce delay if necessary
                    errorDelay = handleExceptionWithDelay(errorDelay);
                    // re-throw
                    throw ioe;
                }
                // Successful accept, reset the error delay
                errorDelay = 0;
                // Configure the socket
                if (running && !paused && setSocketOptions(socket)) {
                    // Hand this socket off to an appropriate processor
                    if (!processSocket(socket)) {
                        countDownConnection();
                        // Close socket right away
                        closeSocket(socket);
                    }
                } else {
                    countDownConnection();
                    // Close socket right away
                    closeSocket(socket);
                }
            } catch (IOException x) {
                if (running) {
                    log.error(sm.getString("endpoint.accept.fail"), x);
                }
            } catch (NullPointerException npe) {
                if (running) {
                    log.error(sm.getString("endpoint.accept.fail"), npe);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("endpoint.accept.fail"), t);
            }
        }
        state = AcceptorState.ENDED;
    }
}

第 39 行做的事就是上面伪代码示例里的监听客户端连接,监听到连接后(即浏览器向服务器发起一次请求)在第 53 行由 processSocket 方法来处理这次接收到的 Socket 连接。processSocket 方法源码如下:

protected boolean processSocket(Socket socket) {
    // Process the request from this socket
    try {
        SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
        wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
        // During shutdown, executor may be null - avoid NPE
        if (!running) {
            return false;
        }
        getExecutor().execute(new SocketProcessor(wrapper));
    } catch (RejectedExecutionException x) {
        log.warn("Socket processing request was rejected for:"+socket,x);
        return false;
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // This means we got an OOM or similar creating a thread, or that
        // the pool and its queue are full
        log.error(sm.getString("endpoint.process.fail"), t);
        return false;
    }
    return true;
}

该方法中先把 Socket 包装成 SocketWrapper ,用以内部处理。重点是第 10 行:getExecutor().execute(new SocketProcessor(wrapper))。如果按照上面伪代码中的处理方式,在有并发请求的情况下,一个请求没有处理完成,服务器将无法立即响应另一个请求。这里做了一下改进,即在接收到一次 Socket 连接后另启一个线程处理该连接,使当前线程不阻塞。
下面跟着 SocketProcessor 这个线程来看看,一次 Socket 连接是如何在 Tomcat 7 中被转成内部的 Request 的。看下该线程的 run 方法:

@Override
public void run() {
    boolean launch = false;
    synchronized (socket) {
        try {
            SocketState state = SocketState.OPEN;
            try {
                // SSL handshake
                serverSocketFactory.handshake(socket.getSocket());
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("endpoint.err.handshake"), t);
                }
                // Tell to close the socket
                state = SocketState.CLOSED;
            }
            if ((state != SocketState.CLOSED)) {
                if (status == null) {
                    state = handler.process(socket, SocketStatus.OPEN);
                } 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.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));
                } 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
}

默认情况下会走到第 22 行,调用 handler 对象的 process 方法,这里 handler 对象实际上是 Http11ConnectionHandler 类的实例,该对象的初始化过程是在 org.apache.coyote.http11.Http11Protocol对象的构造方法中:

public Http11Protocol() {
    endpoint = new JIoEndpoint();
    cHandler = new Http11ConnectionHandler(this);
    ((JIoEndpoint) endpoint).setHandler(cHandler);
    setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
    setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}

所以需要到org.apache.coyote.http11.Http11Protocol类的静态内部类 Http11ConnectionHandler 中找到 process 方法的定义,但当前定义里面没有,这个方法是在其父类org.apache.coyote.AbstractProtocol.AbstractConnectionHandler中定义的:

public SocketState process(SocketWrapper<S> socket,
        SocketStatus status) {
    Processor<S> processor = connections.remove(socket.getSocket());
    if (status == SocketStatus.DISCONNECT && processor == null) {
        //nothing more to be done endpoint requested a close
        //and there are no object associated with this connection
        return SocketState.CLOSED;
    }
    socket.setAsync(false);
    try {
        if (processor == null) {
            processor = recycledProcessors.poll();
        }
        if (processor == null) {
            processor = createProcessor();
        }
        initSsl(socket, 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);
            } else if (processor.isComet()) {
                state = processor.event(status);
            } else if (processor.isUpgrade()) {
                state = processor.upgradeDispatch();
            } else {
                state = processor.process(socket);
            }
            if (state != SocketState.CLOSED && processor.isAsync()) {
                state = processor.asyncPostProcess();
            }
            if (state == SocketState.UPGRADING) {
                // Get the UpgradeInbound handler
                UpgradeInbound inbound = processor.getUpgradeInbound();
                // Release the Http11 processor to be re-used
                release(socket, processor, false, false);
                // Create the light-weight upgrade processor
                processor = createUpgradeProcessor(socket, inbound);
                inbound.onUpgradeComplete();
            }
        } while (state == SocketState.ASYNC_END ||
                state == SocketState.UPGRADING);
        if (state == SocketState.LONG) {
            // In the middle of processing a request/response. Keep the
            // socket associated with the processor. Exact requirements
            // depend on type of long poll
            longPoll(socket, processor);
        } else if (state == SocketState.OPEN) {
            // In keep-alive but between requests. OK to recycle
            // processor. Continue to poll for the next request.
            release(socket, processor, false, true);
        } else if (state == SocketState.SENDFILE) {
            // Sendfile in progress. If it fails, the socket will be
            // closed. If it works, the socket will be re-added to the
            // poller
            release(socket, processor, false, false);
        } else if (state == SocketState.UPGRADED) {
            // Need to keep the connection associated with the processor
            longPoll(socket, processor);
        } else {
            // Connection closed. OK to recycle the processor.
            if (!(processor instanceof UpgradeProcessor)) {
                release(socket, processor, true, false);
            }
        }
        return state;
    } catch(java.net.SocketException e) {
        // SocketExceptions are normal
        getLog().debug(sm.getString(
                "abstractConnectionHandler.socketexception.debug"), e);
    } catch (java.io.IOException e) {
        // IOExceptions are normal
        getLog().debug(sm.getString(
                "abstractConnectionHandler.ioexception.debug"), e);
    }
    // Future developers: if you discover any other
    // rare-but-nonfatal exceptions, catch them here, and log as
    // above.
    catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        // any other exception or error is odd. Here we log it
        // with "ERROR" level, so it will show up even on
        // less-than-verbose logs.
        getLog().error(
                sm.getString("abstractConnectionHandler.error"), e);
    }
    // Don't try to add upgrade processors back into the pool
    if (!(processor instanceof UpgradeProcessor)) {
        release(socket, processor, true, false);
    }
    return SocketState.CLOSED;
}

重点在第 38 行,调用 processor 的 process 方法处理 socket 。而 processor 对象在第 18 行通过 createProcessor 方法创建出来的,createProcessor 方法在当前类里面是抽象方法,默认情况下调用的具体实现类在上面提到的 Http11ConnectionHandler 类中:

@Override
protected Http11Processor createProcessor() {
    Http11Processor processor = new Http11Processor(
            proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
            proto.getMaxTrailerSize());
    processor.setAdapter(proto.adapter);
    processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
    processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
    processor.setConnectionUploadTimeout(
            proto.getConnectionUploadTimeout());
    processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
    processor.setCompressionMinSize(proto.getCompressionMinSize());
    processor.setCompression(proto.getCompression());
    processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
    processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
    processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
    processor.setSocketBuffer(proto.getSocketBuffer());
    processor.setMaxSavePostSize(proto.getMaxSavePostSize());
    processor.setServer(proto.getServer());
    processor.setDisableKeepAlivePercentage(
            proto.getDisableKeepAlivePercentage());
    register(processor);
    return processor;
}

此时的 processor 对象是 Http11Processor 类的实例,再看上一段提到的 processor.process 方法,最终会执行到 Http11Processor 类(因为该类中没有定义 process 方法)的父类org.apache.coyote.http11.AbstractHttp11Processor中的 process 方法。
为了方便理解,下面的时序图列出从 Acceptor 线程的 run 方法到 AbstractHttp11Processor 类的 process 方法的关键方法调用过程:

接下来分析 org.apache.coyote.http11.AbstractHttp11Processor 类的 process 方法:

@Override
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
    setSocketWrapper(socketWrapper);
    getInputBuffer().init(socketWrapper, endpoint);
    getOutputBuffer().init(socketWrapper, endpoint);
    // Flags
    error = false;
    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 (!error && keepAlive && !comet && !isAsync() &&
            upgradeInbound == null && !endpoint.isPaused()) {
        // Parsing the request header
        try {
            setRequestLineReadTimeout();
            if (!getInputBuffer().parseRequestLine(keptAlive)) {
                if (handleIncompleteRequestLineRead()) {
                    break;
                }
            }
            if (endpoint.isPaused()) {
                // 503 - Service unavailable
                response.setStatus(503);
                error = true;
            } else {
                // Make sure that connectors that are non-blocking during
                // header processing (NIO) only set the start time the first
                // time a request is processed.
                if (request.getStartTime() < 0) {
                    request.setStartTime(System.currentTimeMillis());
                }
                keptAlive = true;
                // Set this every time in case limit has been changed via JMX
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                // Currently only NIO will ever return false here
                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);
            }
            error = true;
            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);
                        break;
                    case DEBUG:
                        getLog().debug(message);
                }
            }
            // 400 - Bad Request
            response.setStatus(400);
            adapter.log(request, response, 0);
            error = true;
        }
        if (!error) {
            // Setting up filters, and parse some request headers
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                prepareRequest();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (getLog().isDebugEnabled()) {
                    getLog().debug(sm.getString(
                            "http11processor.request.prepare"), t);
                }
                // 400 - Internal Server Error
                response.setStatus(400);
                adapter.log(request, response, 0);
                error = true;
            }
        }
        if (maxKeepAliveRequests == 1) {
            keepAlive = false;
        } else if (maxKeepAliveRequests > 0 &&
                socketWrapper.decrementKeepAlive() <= 0) {
            keepAlive = false;
        }
        // Process the request in the adapter
        if (!error) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                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 && !error) { // Avoid checking twice.
                    error = response.getErrorException() != null ||
                            (!isAsync() &&
                            statusDropsConnection(response.getStatus()));
                }
                setCometTimeouts(socketWrapper);
            } catch (InterruptedIOException e) {
                error = true;
            } catch (HeadersTooLargeException e) {
                error = true;
                // The response should not have been committed but check it
                // anyway to be safe
                if (!response.isCommitted()) {
                    response.reset();
                    response.setStatus(500);
                    response.setHeader("Connection", "close");
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                getLog().error(sm.getString(
                        "http11processor.request.process"), t);
                // 500 - Internal Server Error
                response.setStatus(500);
                adapter.log(request, response, 0);
                error = true;
            }
        }
        // Finish the handling of the request
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
        if (!isAsync() && !comet) {
            if (error) {
                // 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);
            }
            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 (error) {
            response.setStatus(500);
        }
        request.updateCounters();
        if (!isAsync() && !comet || error) {
            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 (error || endpoint.isPaused()) {
        return SocketState.CLOSED;
    } else if (isAsync() || comet) {
        return SocketState.LONG;
    } else if (isUpgrade()) {
        return SocketState.UPGRADING;
    } else {
        if (sendfileInProgress) {
            return SocketState.SENDFILE;
        } else {
            if (openSocket) {
                if (readComplete) {
                    return SocketState.OPEN;
                } else {
                    return SocketState.LONG;
                }
            } else {
                return SocketState.CLOSED;
            }
        }
    }
}

从这个方法中可以清晰的看出解析请求的过程:第 7 到 10 行从 Socket 中获取输入输出流,第 32 到 97 行解析请求行和请求头,第 99 到 115 行校验和解析请求头中的属性,第 125 到 160 行调用适配器的 service 方法,第 172 行请求处理结束。
上面就是根据 Http 协议解析请求的总体流程。要理解上面提到的请求行、请求头等术语,需要熟悉 Http 协议,这里简单介绍下 Http 协议中的标准请求信息数据的格式:
请求信息包括以下三条

  • 请求行(request line)

例如GET /images/logo.gif HTTP/1.1,表示从/images目录下请求logo.gif这个文件。

  • 请求头(request header),空行

例如Accept-Language: en

  • 其他消息体

请求行和标题必须以<CR><LF>作为结尾。空行内必须只有<CR><LF>而无其他空格。在 HTTP/1.1 协议中,所有的请求头,除 Host 外,都是可选的。
请求行、请求头数据的格式具体看 Http 协议中的描述。所以在从输入流中读取到字节流数据之后必须按照请求行、请求头、消息体的顺序来解析。
这里以请求行数据的解析为例,在 Http 协议中该行内容格式为:
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
即请求类型、要访问的资源( URI )以及使用的HTTP版本,中间以特殊字符空格来分隔,以\r\n字符结尾。
在上面列出的 AbstractHttp11Processor 类的 process 代码中的第 36 行,会调用抽象方法 getInputBuffer() ,当前该抽象方法的具体实现在子类org.apache.coyote.http11.Http11Processor中,该方法返回的是该类的实例变量 inputBuffer :

protected AbstractInputBuffer<Socket> getInputBuffer() {
    return inputBuffer;
}

该实例变量在 Http11Processor 的构造方法中会被初始化:

public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
        int maxTrailerSize) {
    super(endpoint);
    inputBuffer = new InternalInputBuffer(request, headerBufferSize);
    request.setInputBuffer(inputBuffer);
    outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
    response.setOutputBuffer(outputBuffer);
    initializeFilters(maxTrailerSize);
}

所以 AbstractHttp11Processor 类的 process 方法的 36 行 getInputBuffer().parseRequestLine() 将会调用org.apache.coyote.http11.InternalInputBuffer类中的 parseRequestLine 方法:

public boolean parseRequestLine(boolean useAvailableDataOnly)
    throws IOException {
    int start = 0;
    //
    // Skipping blank lines
    //
    byte chr = 0;
    do {
        // Read new bytes if needed
        if (pos >= lastValid) {
            if (!fill())
                throw new EOFException(sm.getString("iib.eof.error"));
        }
        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 always US-ASCII
    //
    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 no CR or LF in method name
        if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
            throw new IllegalArgumentException(
                    sm.getString("iib.invalidmethod"));
        }
        // Spec says single SP but it also says be tolerant of HT
        if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
            space = true;
            request.method().setBytes(buf, start, pos - start);
        }
        pos++;
    }
    // Spec says single SP but also says be tolerant of multiple 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
    //
    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;
        }
        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 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 US-ASCII
    //
    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;
        }
        pos++;
    }
    if ((end - start) > 0) {
        request.protocol().setBytes(buf, start, end - start);
    } else {
        request.protocol().setString("");
    }
    return true;
}

先看这个方法中第 16 行,调用了当前类的 fill 方法:

protected boolean fill() throws IOException {
    return fill(true);
}

里面调用了重载方法 fill :

protected boolean fill(boolean block) throws IOException {
    int nRead = 0;
    if (parsingHeader) {
        if (lastValid == buf.length) {
            throw new IllegalArgumentException
                (sm.getString("iib.requestheadertoolarge.error"));
        }
        nRead = inputStream.read(buf, pos, buf.length - lastValid);
        if (nRead > 0) {
            lastValid = pos + nRead;
        }
    } else {
        if (buf.length - end < 4500) {
            // In this case, the request header was really large, so we allocate a
            // brand new one; the old one will get GCed when subsequent requests
            // clear all references
            buf = new byte[buf.length];
            end = 0;
        }
        pos = end;
        lastValid = pos;
        nRead = inputStream.read(buf, pos, buf.length - lastValid);
        if (nRead > 0) {
            lastValid = pos + nRead;
        }
    }
    return (nRead > 0);
}

在这里可以看到从输入流中读取数据到缓冲区 buf 。按照上面列出的请求行数据格式,从字符流中将会按顺序得到请求的类型( method )、请求的 URI 和 Http 版本。具体实现流程如下:
org.apache.coyote.http11.InternalInputBuffer类中的 parseRequestLine 方法,第 34 到 57 行根据请求头协议的格式,从中取出表示请求方法的字节数据并设置到内置实例变量 request 。第 60 到 72 行解析 method 和 uri 之间的空格字节 SP ,第 83 到 119 行读取表示请求的 URI 的字节数据并放到 request 变量中。第 122 到 133 行解析 uri 和 http 协议版本之间的空格字节 SP ,第 144 到第 168 行读取表示请求的 Http 协议版本的字节数据并放到 request 变量中。
以上是根据 Http 协议解析请求行( request line )的代码实现部分,解析请求头的部分见 InternalInputBuffer 类的 parseHeader 方法,不再赘述。
至此可以看到在 Tomcat 中如何从一次 Socket 连接中取出请求的数据,将这些原始的字符流数据转换成初步可以理解的 Tomcat 内置对象org.apache.coyote.Request的。

作者:预流

来源:https://juejin.im/post/5a72c045f265da3e4c081fb0


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (1)