美文网首页Java学习之路
Tomcat 源码分析 一次完整请求 (基于8.0.5)

Tomcat 源码分析 一次完整请求 (基于8.0.5)

作者: 爱吃鱼的KK | 来源:发表于2017-07-31 22:14 被阅读286次
    1. Tomcat 一次完整请求猜想

    在进行分析之前, 我们先自己猜想一下, Tomcat 处理一个请求一共完成哪些步骤:

    (1) Acceptor 接受请求
    (2) 交给工作线程池来处理请求
    (3) 封装请求成 Request, Response 对象, 向后端容器进行传递
    (4) 经由后端容器的一个一个 Valve 处理
    (5) 在 StandardWrapperValve 根据URi, 组装 ApplicationFilterChain 来处理请求
    (6) 请求经过 Servlet 里面的逻辑处理
    (7) 请求处理的结果经过 Response 对象刷到 浏览器上
    
    2. Tomcat Acceptor处理请求

    Acceptor是一个专门处理请求连接得线程, 他主要做了以下步骤:

    1. 在没有网络 IO 数据时, 该线程会一直 在 serverSocketFactory.acceptSocket 上阻塞
    2. 如果有请求, 则首先通过 countUpOrAwaitConnection 来获取请求处理得许可(用的是LimitLatch)
    3. 若获取 LimitLatch 成功, 则首先将 socket 数据设置 Connector 配置的一些属性, 
    4. 封装成 SocketProcessor 交由工作线程池来处理
    

    见代码:

    try {
        socket = serverSocketFactory.acceptSocket(serverSocket);    // 1. 获取链接请求
    } catch (IOException ioe) {
        countDownConnection();
    }
    
    if (running && !paused && setSocketOptions(socket)) {           // 2. setSocketOptions 是设置socket属性的方法 
        // Hand this socket off to an appropriate processor
        if (!processSocket(socket)) {                               // 3. 在processSocket里面封装一个 SocketProcessor交给线程池处理
            countDownConnection();
            closeSocket(socket);
        }
    }
    

    从上面得代码中获知: Connector 是通过 LimitLatch 来控制连接处理数, 所以 BIO 情况下的 tomcat 并发请求数 = backlog + LimitLatch.maxConnection, 还有另外一个: socket常见的属性:

    1. soLinger: 是在调用 socket.close() 时阻塞一会, 因为底层的数据 可能还没有发出去
    2. soTimeOut: 这个参数是在 ServerSocket.accept() 时阻塞的时间, 若超过这个时间还没有客户端连接上来, 则直接报出 SocketTimeoutException 异常, 当 ServerSocket 还是存活着的
       参考地址 https://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html#setSoTimeout-int-
       与之对应的是 客户端的 socket, 这时 soTimeOut 影响的是 inputStream.read() 的超时时间
       参考地址 https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html#setSoTimeout-int-
    3. tcpNoDelay: Nagle's 算法主要是减少小数据包在网络上的流动,当数据包小于 limit (usually MSS), 将会等待前面发送的数据包返回  ACK(这意味着在底层积累数据包, 等到数据包变大了再发送出去)
       而 TcpNoDelay 主要是禁止 Nagle 算法
       参考地址
       https://stackoverflow.com/questions/3761276/when-should-i-use-tcp-nodelay-and-when-tcp-cork
       http://ccr.sigcomm.org/archive/2001/jan01/ccr-200101-mogul.pdf
    
    3. Tomcat SocketProcessor

    SocketProcessor里面就开始进行正真的请求处理, 主要步骤:

    1. 处理SSL握手
    2. 直接调用 Http11Protocol$Http11ConnectionHandler.process()来 处理请求
    

    在Http11Protocol$Http11ConnectionHandler.process()里面最奇怪的是请求的处理是在一个loop里面, 这是为什么呢? 主要是因为 http 特性里面 keepAlive 的存在, 即一个 socket 的存在能处理多次 http 请求(PS: 那多少次呢, 是否有超时时间限制, 这些参数具体看配置了,看 XXXProtocol.createProcessor 方法里面 )
    下面我们再来看看传说中的Http11Processor

    protected Http11Processor createProcessor() {                          // 构建 Http11Processor
        Http11Processor processor = new Http11Processor(
                proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint, // 1. http header 的最大尺寸
                proto.getMaxTrailerSize(),proto.getMaxExtensionSize());
        processor.setAdapter(proto.getAdapter());
        processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());// 2. 默认的 KeepAlive 情况下, 每个 Socket 处理的最多的 请求次数
        processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());        // 3. 开启 KeepAlive 的 Timeout
        processor.setConnectionUploadTimeout(
                proto.getConnectionUploadTimeout());                       // 4. http 当遇到文件上传时的 默认超时时间 (300 * 1000)
        processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
        processor.setCompressionMinSize(proto.getCompressionMinSize());    // 5. 当 http 请求的 body size超过这个值时, 通过 gzip 进行压缩
        processor.setCompression(proto.getCompression());                  // 6. http 请求是否开启 compression 处理
        processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
        processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());// 7. http body里面的内容是 "text/html,text/xml,text/plain" 才会进行 压缩处理
        processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
        processor.setSocketBuffer(proto.getSocketBuffer());                // 8. socket 的 buffer, 默认 9000
        processor.setMaxSavePostSize(proto.getMaxSavePostSize());          // 9. 最大的 Post 处理尺寸的大小 4 * 1000
        processor.setServer(proto.getServer());
        processor.setDisableKeepAlivePercentage(
                proto.getDisableKeepAlivePercentage());                    // 10. 这是一个阀值, 当工作线程池超过 disableKeepAlivePercentage() 默认时, 就会 disable 的功能, 在 AbstractHttp11Processor中的 process
        register(processor);                                               // 11. 将 Http11Processor 注册到 JMX 里面
        return processor;
    }
    
    4. Tomcat AbstractHttp11Processor

    主逻辑:

    1. 只解析 Http 请求头中的 方法, URI, 协议(PS: 这里是直接整块读取 Header 里面的数据, 最大大小是 8M, 见 Http11Processor 的构造函数)
    2. 设置最大的 Header 大小
    3. 解析 HTTP 请求的包问头 headers
    4. 根据请求来设置其对应的 InputFilter
    5. 调用 CoyoteAdapter 的 service 方法来处理请求
    

    见代码:

    if (!getInputBuffer().parseRequestLine(keptAlive)) {             // 只解析 Http 请求头中的 方法, URI, 协议(PS: 这里是直接整块读取 Header 里面的数据, 最大大小是 8M, 见 Http11Processor 的构造函数)
        if (handleIncompleteRequestLineRead()) {
            break;
        }
    }
    request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount()); // 设置最大的 Header 大小
    // Currently only NIO will ever return false here
    if (!getInputBuffer().parseHeaders()) {                          // 解析 HTTP 请求的包问头 headers
        // We've read part of the request, don't recycle it
        // instead associate it with the socket
        openSocket = true;
        readComplete = false;
        break;
    }
    
    
    /**
     * Parse the HTTP headers.
     * 读取请求头
     */
    @Override
    public boolean parseHeaders()
        throws IOException {
        if (!parsingHeader) {
            throw new IllegalStateException(
                    sm.getString("iib.parseheaders.ise.error"));
        }
    
        while (parseHeader()) {             // 这里其实就是在 解析 Http header 里面的键值对 (loop 一次, 读取一行数据, 处理逻辑还是比较简单)
            // Loop until we run out of headers
        }
    
        parsingHeader = false;
        end = pos;
        return true;
    }
    
    if (!error) {
        // Setting up filters, and parse some request headers
        rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
        try {
            prepareRequest();                       // 根据请求来设置其对应的 InputFilter
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            if (getLog().isDebugEnabled()) {
                getLog().debug(sm.getString(
                        "http11processor.request.prepare"), t);
            }
            // 400 - Internal Server Error
            response.setStatus(400);
            getAdapter().log(request, response, 0);
            error = true;
        }
    }
    // 调用 CoyoteAdapter 的 service 方法, 传入 org.apache.coyote.Request对象及
    // org.apache.coyoteResponse 对象
    getAdapter().service(request, response);
    
    5. Tomcat CoyoteAdapter

    适配器模式: 主要将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题
    对于适配器模式, 一般的书本都是这样定义的, 现在我们看看Tomcat里面是在哪种情况下运用的: 将网络框架的封装类 Request 转换成 Servlet容器能处理的Request, 见代码(connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);), 主要还是在 CoyoteAdapter.service()里面

    主要步骤:

    1. 通过 Connector 创建 org.apache.catalina.connector.Request对象,org.apache.catalina.connectorResponse 对象 这里的 Request, Response 实现了 ServletRequest/ServletResponse, 并且在内部属性中拥有 org.apache.coyote.Request/Response
    2. 下面的 postParseRequest 是用来处理请求映射 (获取 host, context, wrapper, URI 后面的参数的解析, sessionId )
    3. 开始调用 Tomcat 的容器, 首先调用 StandardEngine 容器中的管道PipeLine 中的第一个 Valve, 传入 connector.Request 与 connector.Response 来处理所有逻辑
    4. 通过request.finishRequest 与 response.finishResponse(刷OutputBuffer中的数据到浏览器) 来完成整个请求
    

    见下面的代码:

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);
    
    if (request == null) {
                                                // 1. 通过 Connector 创建 org.apache.catalina.connector.Request对象,org.apache.catalina.connectorResponse 对象 这里的 Request, Response 实现了 ServletRequest/ServletResponse, 并且在内部属性中拥有 org.apache.coyote.Request/Response
        request = connector.createRequest();    // 2. 这里的 Request 或实现 ServletRequest接口, 并且会传递到下游容器中 (PS: 在创建 Connector 时会构建一个 CoyoteAdapter(Connector))
                                                // 3. 设置 org.apache.coyote.Request 对象
        request.setCoyoteRequest(req);
                                                // 4. 通过 Connector 创建 org.apache.catalina.connectorResponse 对象
        response = connector.createResponse();
                                                // 5. 设置 org.apache.coyote.Response 对象
        response.setCoyoteResponse(res);
        // Link objects
                                                // 6. 把 Resquest 及 Response 对象相互关联起来
        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 // 7. 获取 URI 的编码格式
            (connector.getURIEncoding());
    
    }
    
    req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());// 8. 在 RequestInfo 里面设置对应的 ThreadName (PS: 可以看到 这里 ThreadLocal 只有对对应 get, 但没有对应的 remove, 为什么呢?  因为这里存储 Tomcat 工作线程池的领地,  工作线程池 never stop, 除非停止 Tomcat, 并且这里缓存的数据的大小也是非常小的, 也不会与其他的 WebappClassLoader/WebappClassLoader 任何挂钩)
                                                                     // 9. 下面的 postParseRequest 是用来处理请求映射 (获取 host, context, wrapper, URI 后面的参数的解析, sessionId )
    boolean postParseSuccess = postParseRequest(req, request, res, response);
    if (postParseSuccess) {
        //check valves if we support async
        request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
        // Calling the container
                                                                    // 10. 开始调用 Tomcat 的容器, 首先调用 StandardEngine 容器中的管道PipeLine 中的第一个 Valve, 传入 connector.Request 与 connector.Response
        connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
    }
    
                                                            // 11. 完成此次请求
    request.finishRequest();
                                                            // 12. 完成此次请求, 并提交响应信息
    response.finishResponse();                              // 13. 跟过源码的兄弟会发现, 这里有点绕, 主要是将 org.apache.catalina.connector.Response对应的 OutputBuffer 中的数据 刷到 org.apache.coyote.Response 对应的 InternalOutputBuffer 中, 并且最终调用 socket对应的 outputStream 将数据刷出去( 这里会组装 Http Response 中的 header 与 body 里面的数据, 并且刷到远端 )
    if (postParseSuccess &&
            request.getMappingData().context != null) {
        // Log only if processing was invoked.
        // If postParseRequest() failed, it has already logged it.
        // If context is null this was the start of a comet request
        // that failed and has already been logged.
        request.getMappingData().context.logAccess(
                request, response,
                System.currentTimeMillis() - req.getStartTime(),
                false);
    }
    

    上述方法中比较重要的就是postParseRequest方法, 它主要完成:

    1. 将URI里面的请求参数解析到 request.pathParameters 里面
    2. Mapper.map 方法按照请求路径进行匹配, Mapper是路由程序, 主要根据 URI 中的信息, 匹配对应的 StandardHost, StandardContext, StandardWrapper
    3. 根据默认的session追踪机制(defaultSessionTrackingModes)来尝试获取一下 sessionID(依次从 URI, Cookie, SSL中)
    
    6. Tomcat StandardEngine

    程序从CoyoteAdapter中找到对应的StandardEngine的Pipeline里面的第一个Valve来进行处理, 下面我们来看 StandardEngineValve
    见代码:

    /*
     * StandardEngine 容器默认配置了 StandardEngineValve 阀门, 它主要做负责选择响应的 Host 去处理请求
     */
    @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Select the Host to be used for this Request
                                                    // 1. 得到此次请求对应的 StandardHost 容器
        Host host = request.getHost();              // 2. 通过 Mapper 模块是就已经获取 对应的 StandardHost
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost",
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }
        // Ask this Host to process this request
                                                    // 3. 调用 StandardHost 容器中管道 Pipeline 中的第一个 Valve
        host.getPipeline().getFirst().invoke(request, response);  
    
    }
    

    从代码中我们得知: 它主要做负责选择响应的 Host 去处理请求

    7. Tomcat StandardHost

    StandardHost的Pipeline里面一定有 ErrorReportValve, 与 StandardHostValve两个Valve
    ErrorReportValve主要是检测 Http 请求过程中是否出现过什么异常, 有异常的话, 直接拼装 html 页面, 输出到客户端
    见代码:

    public void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Perform the request
        getNext().invoke(request, response);        // 1. 先将 请求转发给下一个 Valve
        if (response.isCommitted()) {               // 2. 这里的 isCommitted 表明, 请求是正常处理结束
            return;
        }
        Throwable throwable =                       // 3. 判断请求过程中是否有异常发生
                (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        if (request.isAsyncStarted() && ((response.getStatus() < 400 &&
                throwable == null) || request.isAsyncDispatching())) {
            return;
        }
        if (throwable != null) {
            // The response is an error
            response.setError();
            // Reset the response (if possible)
            try {
                response.reset();                  // 4. 重置 response 里面的数据(此时 Response 里面可能有些数据)
            } catch (IllegalStateException e) {
                // Ignore
            }
            response.sendError                     // 5. 这就是我们常看到的 500 错误码
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        response.setSuspended(false);
        try {
            report(request, response, throwable); // 6. 这里就是将 异常的堆栈信息组合成 html 页面, 输出到前台                                          
        } catch (Throwable tt) {
            ExceptionUtils.handleThrowable(tt);
        }
        if (request.isAsyncStarted()) {          // 7. 若是异步请求的话, 设置对应的 complete (对应的是 异步 Servlet)                                              
            request.getAsyncContext().complete();
        }
    }
    

    上面逻辑比较简单, 看代码就行; 至于 StandardHostValve, 其主要根据请求的信息将其路由到对应的 StandardContext (PS: 其中比较重要的是, 在每次进行操作前后, 需要更改对应线程的 ContextClassloader 为 StandardContext 中的 WebappClassloader)

    8. Tomcat StandardContext

    StandardContext中的Valve也主要 根据 Request 里面的信息, 将请求路由到对应的 wrapper 中, 见代码

    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Disallow any direct access to resources under WEB-INF or META-INF
        MessageBytes requestPathMB = request.getRequestPathMB();       // 1. 对于 WEB-INF 与 META-INF 目录禁止访问的控制
        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/META-INF"))
                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);      // 2. 返回 HTTP 状态码 404
            return;
        }
        // Select the Wrapper to be used for this Request
        Wrapper wrapper = request.getWrapper();                        // 3. 得到此请求对应的 StandardWrapper 容器 (这个是在路由模块获取到的)
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
    
        // Acknowledge the request
        try {
            response.sendAcknowledgement();
        } catch (IOException ioe) {
            container.getLogger().error(sm.getString(
                    "standardContextValve.acknowledgeException"), ioe);
            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
    
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
        }
        wrapper.getPipeline().getFirst().invoke(request, response);    // 4. 调用 StandardWrapper 容器中管道 Pipeline 中的第一个 Valve 的 invoke() 方法
    } 
    
    9. Tomcat StandardWrapper

    在StandardWrapper中的Valve比较重要, 见代码

    public final void invoke(Request request, Response response) throws IOException, ServletException {
    
        // Initialize local variables we may need
        boolean unavailable = false;
        Throwable throwable = null;
        // This should be a Request attribute...
        long t1=System.currentTimeMillis();                        // 1. 开始记录请求处理时间
        requestCount.incrementAndGet();                            // 2. 增加请求次数
        // 得到 StandardWrapper 容器
        StandardWrapper wrapper = (StandardWrapper) getContainer();// 3. 每个请求都会对应相应的 StandardWrapper 及 StandardWrapperValve 对象
        // 此次请求对应的 servlet
        Servlet servlet = null;
        Context context = (Context) wrapper.getParent();           // 4. 得到此次请求的 StandardContext 对象
    
        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) { // 判断 Servlet 是否存在
                // 从 StandardWrapper 容器获取一个 Servlet 对象, Servlet对象的创建及初始化init 都在这里执行
                /**
                 * Servlet 的分配操作,
                 * 在 !SingleThreadModel 模式下, 多线程共享一个 Servlet
                 * 在  SingleThreadModel 模式下, Servlet 放到一个共享的对象池里面(池里面最多放 20 个 Servlet)
                 */
                servlet = wrapper.allocate();                     // 6. 进行 servlet 的分配
            }
        } catch (UnavailableException e) {}
    
        MessageBytes requestPathMB = request.getRequestPathMB();  // 7. 获取 请求的 Path
        DispatcherType dispatcherType = DispatcherType.REQUEST;
        if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
                requestPathMB);
    
        // Create the filter chain for this request
        ApplicationFilterFactory factory =                        // 8. 创建 ApplicationFilterFactory 对象
            ApplicationFilterFactory.getInstance();
    
        // 创建 ApplicationFilterChain
        ApplicationFilterChain filterChain =                      // 9. 创建此次请求的 ApplicationFilterChain 对象, 包装了所有请求的 Servlet 对象及一些拦截的过滤器 Filter 对象
            factory.createFilterChain(request, wrapper, servlet);
    
        // 调用 ApplicationFilterChain的 doFilter 方法
        // 传入 org.apache.catalina.connector.RequestFacade 及
        // org.apache.catalina.connector.ResponseFacade 对象, 开始进行请求处理
        filterChain.doFilter(request.getRequest(),                // 10.  执行 filterChain 链, 在链的末尾就是 servlet
                response.getResponse());
    
        // Release the filter chain (if any) for this request
        if (filterChain != null) {
            if (request.isComet()) {
                // If this is a Comet request, then the same chain will be used for the
                // processing of all subsequent events.
                filterChain.reuse();
            } else {
                filterChain.release();                            // 11. 释放 对应的 ApplicationFilterChain里面的资源
            }
        }
    
        / Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);                      // 12. 执行 完后把 Servlet 实例回收到 Servlet 实例池
            }
        } catch (Throwable e) {
            ExceptionUtils.handleThrowable(e);
            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }
    
        long t2=System.currentTimeMillis();
        long time=t2-t1;
        processingTime += time;                                  // 13. 这里的 processingTime 就是请求的处理时间
        if( time > maxTime) maxTime=time;
        if( time < minTime) minTime=time;
    
    }
    

    上面的代码中比较重要的是

    1. 进行 servlet 的分配
    2. 创建此次请求的 ApplicationFilterChain 对象, 包装了所有请求的 Servlet 对象及一些拦截的过滤器 Filter 对象
    3. 通过执行 filterChain 链, 在链的末尾就是 servlet
    

    首先看Servlet的分配

    public Servlet allocate() throws ServletException {
        // If we are currently unloading this servlet, throw an exception
        if (unloading)
            throw new ServletException
              (sm.getString("standardWrapper.unloading", getName()));
        boolean newInstance = false;
        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {               // 1. 是否是 单个线程单个实例 的模型(PS: 这里的 Servlet 若是已经实例化了, 则说明 在 load-on-startup 时已经实例化了)
            /**
             * 若 instance 存在的话, 说明要么
             * 1. 在 web.xml 中的 load-on-startup 已经实例化过
             * 2. 要么这个实例化的流程已经走过了一遍
             */
            // Load and initialize our instance if necessary
            if (instance == null) {             // 2. double check lock 通过 double check lock 来实例化
                synchronized (this) {
                    if (instance == null) {     // 3. 说明 Servlet 没有实例化过
                        try {
                            if (log.isDebugEnabled())
                                log.debug("Allocating non-STM instance");
    
                            instance = loadServlet();// 4. 通过 InstanceManager 来实例化
                            if (!singleThreadModel) {
                                // For non-STM, increment here to prevent a race
                                // condition with unload. Bug 43683, test case
                                // #3
                                newInstance = true;
                                countAllocated.incrementAndGet();
                            }
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            ExceptionUtils.handleThrowable(e);
                            throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }
            if (!instanceInitialized) {              // 5. 调用 Servlet 的初始化方法 init
                initServlet(instance);
            }
    
            if (singleThreadModel) {                 // 6. 若是单例模式, 就直接放到  instancePool 里面
                if (newInstance) {
                    // Have to do this outside of the sync above to prevent a
                    // possible deadlock
                    synchronized (instancePool) {
                        instancePool.push(instance);
                        nInstances++;
                    }
                }
            } else {
                if (log.isTraceEnabled())
                    log.trace("  Returning non-STM instance");
                // For new instances, count will have been incremented at the
                // time of creation
                if (!newInstance) {
                    countAllocated.incrementAndGet();
                }
                return (instance);
            }
        }
        synchronized (instancePool) {
            while (countAllocated.get() >= nInstances) { // 7. 下面的这段代码可以用 Semaphore 之类的来进行取代 (个人还是比较喜欢 JUC 里面的代码)
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {         // 8. 这里的 maxInstances = 20 表示在 一个线程一个 Servlet 的模式下, 对象池中最多只能有 20 个Servlet
                    try {
                        instancePool.push(loadServlet());// 9. 将加载好的 Servlet 放入对象池里面
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();            // 10. 在 Servlet.deallocate 中会进行 instancePool.notify 通知唤醒
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }
            }
            if (log.isTraceEnabled())
                log.trace("  Returning allocated STM instance");
            countAllocated.incrementAndGet();
            return instancePool.pop();
        }
    }
    

    Servlet 的分配操作主要分为下面两种模式
    在 !SingleThreadModel 模式下, 多线程共享一个 Servlet
    在 SingleThreadModel 模式下, Servlet 放到一个共享的对象池里面(池里面最多放 20 个 Servlet, 这里只同一个Servlet Class 的实例)
    见 InstanceManager 来分配 Servlet, 其中最有感觉的是 Servlet 的Annotation属性的注入(PS: 这些属性的解析获取是在 ContextConfig.applicationAnnotationsConfig() 里面进行设置)

    // 通过 InstanceManager 来对 Servlet 实例进行创建
    InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
    try {
        // servlet 的实例化不是通过 class.forname 来生成的
        // 下面的方法就会涉及到 @inject 如何注入, 并且在其执行时候, 是怎么进行查找的
        servlet = (Servlet) instanceManager.newInstance(servletClass);
    } catch (ClassCastException e) {}
    
    @Override
    public Object newInstance(String className) throws IllegalAccessException,
            InvocationTargetException, NamingException, InstantiationException,
            ClassNotFoundException {
        Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
        return newInstance(clazz.newInstance(), clazz);
    }
    
    private Object newInstance(Object instance, Class<?> clazz)
            throws IllegalAccessException, InvocationTargetException, NamingException {
        if (!ignoreAnnotations) { // 找到当前的 inject 点, 从 injectionMap 中查找出当前 Servlet 的 Inject集合
            /**
             * 主要有
             * @Resource
             * @WebServiceRef
             * @PersistenceContext
             * @PersistenceUnit
             * @PostConstruct
             * @PreDestory
             */
            /**
             * 前面定义的应用拳击的注入集合 injectionMap, 是基于所有应用的, 而这里是基于特定的 Servlet 的
             * , 所以需要从 injectMap 中 get到自己的 Servlet 的inject集合
             */
            Map<String, String> injections = assembleInjectionsFromClassHierarchy(clazz);                       // 从 Servlet 类的继承树中收集 Servlet的注解
            /**
             * 在前面我们得到 injectionMap 集合, 这个集合的 value 不是引用的本身, 而是
             *  jndiName, 之所以没有将这些引用直接实例化是因为 对于这些引用非常占用内存, 并且初始化的时间非常长
             *  我们是否想过一个好的办法, 假设每一次都引用, 是否将这些都缓存起来, 第一次虽然费点劲, 初始化时间长, 而
             *  下一次就直接可以跳过初始化这一步, 具体操作在 populateAnnotationsCache
             *  Tomcat 将每一个 Annotation 条目都做成了 AnnotationCacheEntry, 这一步主要是将这些
             *  映射关系建立起来, 并没有直接把引用创建出来, 直接赋值到 AnnotationCacheEntry 中,
             *  操作已经在 processAnnotations 完成
             */
            populateAnnotationsCache(clazz, injections); // 将jndi的引用实例化为 annotationCache引用集合, 并进行缓存起来
    
            /**
             * 将引用存入 AnnotationCacheEntry 中去
             * 通过 tomcat 自身的 JNDI 系统进行查询, 如果是方法的化, 再进行
             *  method.invoke, 如果是 field 的话, 直接返回 filed 即可,
             *  当这一步操作完以后, AnnotationCacheEntry 就缓存完毕, 下一次再请求 Servlet的话
             *  实例化就不需要这些步骤的
             *  目前 Servlet 都是单实例多线程的
             */
            processAnnotations(instance, injections);    // 根据注解说明, 调用 Servlet 的方法, 进行设置名称上下文的资源
    
            /**
             * 对于 PostConstruct 的方法的回调,这个是为了 JAVA EE 规范的 Common Annotation 规范
             * 整体的思路也是查询方法, 然后进行回调注入的方法
             */
            postConstruct(instance, clazz); // 实例化 Object // 设置 @PostConstruct/@PreDestory 类型的资源依赖
        }
        return instance;
    }
    

    至于 ApplicationFilterChain 的组装, 在创建 ApplicationFilterChain 的过程中, 会遍历 filterMaps, 将符合 URL 请求的 Filter 加入到 filterChain 里面(代码比较简单, 就不贴出来了)

    ApplicationFilterChain.internalDoFilter里面是一步一步的执行Filter, 若有一个Filter执行失败, 则在第一个判断里面return, 若都成功, 则向下执行Servlet

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {                                              // 1. 如果还有 过滤器Filter, 则执行Filter
            ApplicationFilterConfig filterConfig = filters[pos++];  // 2. 得到 过滤器 Filter
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();                  // 3. 这里的 getFilter在没有初始化Filter时, 会通过instanceManager来实现加载Filter(并且初始化Filter)
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);
    
                // 执行过滤器 Filter的 doFilter(Request, Response, FilterChain) 方法     // 一个小建议 在 Fiter 下面加一个 FilterBase, 做些基础工作
                    filter.doFilter(request, response, this);       // 4. 这里的 filter 的执行 有点递归的感觉, 只是通过 pos 来控制从 filterChain 里面拿出那个 filter 来进行操作 (所以在 Filter 里面所调用 return, 则会终止 Filter 的调用, 而下面的 Servlet.service 更本就没有调用到)
            }catch(Exception e){}
    
            return;                                                 // 5. 这里return 表明没有所有 Filter 都执行成功
        }     
    
        servlet.service(request, response);                         // 6. 过滤器 Filter 全部执行完, 最终调用 servlet 的 service(request, response) 方法完成Web 应用的业务逻辑               
    } 
    
    10. Tomcat MyHttpServlet

    然而程序到这里还没有完成, 我们先来看一下一个 HttpServletdemo程序

    public class MyHttpServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/plain");            // 1. 请求返回的数据的格式
            PrintWriter out = resp.getWriter();           // 2. 获取 new CoyoteWriter(outputBuffer) (PS: outputBuffer是Response里面的内部类, 存储着要写回浏览器的数据)
            Object object = req.getParameter("name");     // 3. 在第一次获取参数时, http body 里面的数据
            HttpSession httpSession = req.getSession();   // 4. 在第一次获取 Session 时会初始化构建Session(PS: 也就是说, 只有在程序里面getSession时才会创建Session)
            httpSession.setAttribute("name", "xjk");      // 5. 在Session里面设置对应 KV 数据
            out.print("OK");                              // 6. 将数据写回 Response 的OutputBuffer里面(PS: 只有在Response commit 时才正真的写数据到浏览器里)
        }
    }
    

    看到第二步的PrintWriter, 这个类其实是通过 Response里面的outputBuffer构建出的CoyoteWriter(PS: outputBuffer默认最大存储是 8M, 里面存储的是写到浏览器的数据)
    req.getParameter("name") 这个看似简单的方法, 其实里面很有搞头
    这里的 req 其实是 org.apache.catalina.connector.Request, 在调用方法的第一次会进行请求参数的第一次解析(调用parseParameters来解析body的数据), 对于这个方法, 其实就是解析URI, body的参数(其间会根据contentType分别处理), 见下面的代码

    /**
     * Parse request parameters.
     */
    protected void parseParameters() {
    
        parametersParsed = true;
    
        Parameters parameters = coyoteRequest.getParameters();
        boolean success = false;
        try {
            // Set this every time in case limit has been changed via JMX
            parameters.setLimit(getConnector().getMaxParameterCount());         // 1. 设置http 请求的最大参数个数 (KV 对)
    
            // getCharacterEncoding() may have been overridden to search for
            // hidden form field containing request encoding
            String enc = getCharacterEncoding();                                // 2. 这里是从 org.apache.coyote.Request 里面拿取对应的编码模式
    
            boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); // 3. 获取解析 URI 的编码
            if (enc != null) {
                parameters.setEncoding(enc);
                if (useBodyEncodingForURI) {
                    parameters.setQueryStringEncoding(enc);
                }
            } else {
                parameters.setEncoding                                          // 4. 若未获取编码格式, 则直接使用 "ISO-8859-1" 这种编码格式
                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
                if (useBodyEncodingForURI) {
                    parameters.setQueryStringEncoding
                        (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
                }
            }
    
            parameters.handleQueryParameters();
    
            if (usingInputStream || usingReader) {
                success = true;
                return;
            }
    
            if( !getConnector().isParseBodyMethod(getMethod()) ) {             // 5. 判断这种请求方法类型是否需要解析 http 的 body
                success = true;
                return;
            }
    
            String contentType = getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(';');
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }
    
            if ("multipart/form-data".equals(contentType)) {                    // 6. 若http是文件上传, 则解析上传的文件, 直接对 http 请求的数据进行 part 解析
                parseParts(false);
                success = true;
                return;
            }
    
            if (!("application/x-www-form-urlencoded".equals(contentType))) {
                success = true;
                return;
            }
    
            int len = getContentLength();                                       // 7. 获取请求数据长度大小
    
            if (len > 0) {
                int maxPostSize = connector.getMaxPostSize();                   // 8. 若 http 发来的数据大于 connector 能接收的极限, 则 不进行处理请求
                if ((maxPostSize > 0) && (len > maxPostSize)) {
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.postTooLarge"));
                    }
                    checkSwallowInput();
                    return;
                }
                byte[] formData = null;
                if (len < CACHED_POST_LEN) { // 这里默认是 8M
                    if (postData == null) {
                        postData = new byte[CACHED_POST_LEN];
                    }
                    formData = postData;
                } else {
                    formData = new byte[len];
                }
                try {
                    if (readPostBody(formData, len) != len) {                   // 9. 读取 body 里面的数据
                        return;
                    }
                } catch (IOException e) {
                    // Client disconnect
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                parameters.processParameters(formData, 0, len);                 // 10. 解析处理 body 里面的数据
            } else if ("chunked".equalsIgnoreCase(                              // 11. 若 header 里面的 transfer-encoding 是 chunked
                    coyoteRequest.getHeader("transfer-encoding"))) {
                byte[] formData = null;
                try {
                    formData = readChunkedPostBody();                           // 12. 这里就是进行 form 表单提交时, 默认的解析 body 里面数据的入口
                } catch (IOException e) {
                    // Client disconnect or chunkedPostTooLarge error
                    if (context.getLogger().isDebugEnabled()) {
                        context.getLogger().debug(
                                sm.getString("coyoteRequest.parseParameters"), e);
                    }
                    return;
                }
                if (formData != null) {
                    parameters.processParameters(formData, 0, formData.length);
                }
            }
            success = true;
        } finally {
            if (!success) {
                parameters.setParseFailed(true);
            }
        }
    }
    

    接着我们再看看 req.getSession(), 见详情

    protected Session doGetSession(boolean create) {        // create: 是否创建 StandardSession
    
        // There cannot be a session if no context has been assigned yet
        if (context == null) {
            return (null);                                  // 1. 检验 StandardContext
        }
    
        // Return the current session if it exists and is valid
        if ((session != null) && !session.isValid()) {      // 2. 校验 Session 的有效性
            session = null;
        }
        if (session != null) {
            return (session);
        }
    
        // Return the requested session if it exists and is valid
        Manager manager = null;
        if (context != null) {
            manager = context.getManager();
        }
        if (manager == null)
         {
            return (null);      // Sessions are not supported
        }
        if (requestedSessionId != null) {
            /**
             * 通过 StandardContext 拿到对应的StandardManager, 查找缓存中是否有对应的客户端传递过来的 sessionId
             * 如果有的话, 那么直接 session.access (计数器 + 1), 然后返回
             */
            try {                                            // 3. 通过 managerBase.sessions 获取 Session
                session = manager.findSession(requestedSessionId);  // 4. 通过客户端的 sessionId 从 managerBase.sessions 来获取 Session 对象
            } catch (IOException e) {
                session = null;
            }
            if ((session != null) && !session.isValid()) {   // 5. 判断 session 是否有效
                session = null;
            }
            if (session != null) {
                session.access();                            // 6. session access +1
                return (session);
            }
        }
    
        // Create a new session if requested and the response is not committed
        if (!create) {
            return (null);                                   // 7. 根据标识是否创建 StandardSession ( false 直接返回)
        }
        if ((context != null) && (response != null) &&
            context.getServletContext().getEffectiveSessionTrackingModes().
                    contains(SessionTrackingMode.COOKIE) &&
            response.getResponse().isCommitted()) {
            throw new IllegalStateException
              (sm.getString("coyoteRequest.sessionCreateCommitted"));
        }
    
        // Attempt to reuse session id if one was submitted in a cookie
        // Do not reuse the session id if it is from a URL, to prevent possible
        // phishing attacks
        // Use the SSL session ID if one is present.
        if (("/".equals(context.getSessionCookiePath())      // 8. 到这里其实是没有找到 session, 直接创建 Session 出来
                && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
            session = manager.createSession(getRequestedSessionId()); // 9. 从客户端读取 sessionID
        } else {
            session = manager.createSession(null);
        }
    
        // Creating a new session cookie based on that session
        if ((session != null) && (getContext() != null)
               && getContext().getServletContext().
                       getEffectiveSessionTrackingModes().contains(
                               SessionTrackingMode.COOKIE)) {
            Cookie cookie =
                ApplicationSessionCookieConfig.createSessionCookie( // 10. 根据 sessionId 来创建一个 Cookie
                        context, session.getIdInternal(), isSecure());
    
            response.addSessionCookieInternal(cookie);              // 11. 最后在响应体中写入 cookie
        }
    
        if (session == null) {
            return null;
        }
    
        session.access();                                           // 12. session access 计数器 + 1
        return session;
    }
    
    public Session createSession(String sessionId) {
    
        if ((maxActiveSessions >= 0) &&
                (getActiveSessions() >= maxActiveSessions)) {       // 1. 判断 单节点的 Session 个数是否超过限制
            rejectedSessions++;
            throw new TooManyActiveSessionsException(
                    sm.getString("managerBase.createSession.ise"),
                    maxActiveSessions);
        }
    
        // Recycle or create a Session instance
        // 创建一个 空的 session
        Session session = createEmptySession();                     // 2. 创建 Session
    
        // Initialize the properties of the new session and return it
        // 初始化空 session 的属性
        session.setNew(true);
        session.setValid(true);
        session.setCreationTime(System.currentTimeMillis());
        session.setMaxInactiveInterval(this.maxInactiveInterval);   // 3. StandardSession 最大的默认 Session 激活时间
        String id = sessionId;
        if (id == null) {
            id = generateSessionId();                               // 4. 生成 sessionId (这里通过随机数来生成)
        }
        session.setId(id);
        sessionCounter++;
    
        SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
        synchronized (sessionCreationTiming) {
            sessionCreationTiming.add(timing);                      // 5. 每次创建 Session 都会创建一个 SessionTiming, 并且 push 到 链表 sessionCreationTiming 的最后
            sessionCreationTiming.poll();                           // 6. 并且将 链表 最前面的节点删除
        }
        return (session);
    }
    
    10. Tomcat 回写数据

    Tomcat 会写数据是通过 org.apache.catalina.connectorResponse.finishResponse方法, 这里的逻辑比较多: 涉及两层封装, 写数据时的 commit与Flush 都是通过回调 Http11Processor.InternalOutputBuffer 来完成的; 先看一下整个流程的主要成员:

    参与角色
    org.apache.catalina.connector.Response                           : 这个 Servlet 容器中封装的 Response 对象
    org.apache.catalina.connector.Response.OutputBuffer              : Response 对象中的输出缓冲
    org.apache.catalina.connector.Response.OutputBuffer.CharChunk    : Response 对象中输出缓冲下的字符缓冲区域
    org.apache.catalina.connector.Response.OutputBuffer.ByteChunk    : Response 对象中输出缓冲下的字节缓冲区域
    org.apache.coyote.Response                                       : Tomcat 网络框架封装的 Response
    org.apache.coyote.http11;Http11Processor                         : Tomcat 工作线程池执行的任务
    org.apache.coyote.http11;InternalOutputBuffer                    : InternalOutputBuffer 通常存在于 Http11Processor 里面, 主要是做最终数据的缓存与刷数据到远端
    org.apache.coyote.http11;InternalOutputBuffer.socketBuffer       : 用于存储刷新到远端数据的缓存(这里buffer里面存储的是 header + body 的所有数据)
    org.apache.coyote.http11;InternalOutputBuffer.outputStream       : outputStream 是从代表 客户端的 socket 中拿出来的, 最后也是通过这个 Stream 刷数据到远端
    

    先看一张UML 图吧

    Response.finishResponse.png

    额, 好像都点复杂, 没事, 我们一步一步来;
    看代码: org.apache.catalina.connector.OutputBuffer.close() 方法

    public void close()
        throws IOException {
    
        if (closed) {
            return;
        }
        if (suspended) {
            return;
        }
    
        // If there are chars, flush all of them to the byte buffer now as bytes are used to
        // calculate the content-length (if everything fits into the byte buffer, of course).
        if (cb.getLength() > 0) {                   // 1. 这里 cb 里面的数据就是 Response.getPrintWriter.write("OK") 写到 CharChunk 里面
            cb.flushBuffer();                       // 2. 将 CharChunk 里面的数据刷到 OutputBuffer中 (PS: 这里就是将 CharChunk 里面的数据刷到 ByteChunk 里面), 期间转化会经过字符转化器
        }
    
        if ((!coyoteResponse.isCommitted())         // 3. 这里的 coyoteResponse 是 coyote 的 response
            && (coyoteResponse.getContentLengthLong() == -1)) {
            // If this didn't cause a commit of the response, the final content
            // length can be calculated
            if (!coyoteResponse.isCommitted()) {    // 4. 设置 Http header 里面 content-length 的长度 (也就是你在 HttpServlet 里面调用CoyoteWriter.print/write 写入的数据的大小)
                coyoteResponse.setContentLength(bb.getLength());
            }
        }
                                                    // 5. 下面的 doFlush 其实就是将 org.apache.catalina.connector.Response.OutputBuffer 中的 CharChunk, ByteChunk 的数据(body中的数据, 也就是一开始在 MyHttpServlet中 PrintWriter.write("OK")的数据刷到 Http11Processor.InternalOutputBuffer里面, 当然里面还涉及到 header 中的数据
        if (coyoteResponse.getStatus() ==
                HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
            doFlush(true);
        } else {
            doFlush(false);
        }
        closed = true;
    
        // The request should have been completely read by the time the response
        // is closed. Further reads of the input a) are pointless and b) really
        // confuse AJP (bug 50189) so close the input buffer to prevent them.
        Request req = (Request) coyoteResponse.getRequest().getNote(
                CoyoteAdapter.ADAPTER_NOTES);
        req.inputBuffer.close();
    
        coyoteResponse.finish();                   // 6. 这边才是正真写出数据的地方 注意这里是 org.apache.coyote.Response, 将 InternalOutputBuffer 中的 socketBuffer 刷到浏览器中
    }
    

    其主要做了下面几步:

    1. 将 org.apache.catalina.connector.Response.CharChunk 里面的数据刷到 org.apache.catalina.connector.Response.ByteChunk 里面
    2. 将 org.apache.catalina.connector.Response.OutputBuffer 中的 CharChunk, ByteChunk 的数据(body中的数据, 也就是一开始在 MyHttpServlet中 PrintWriter.write("OK")的数据刷到 Http11Processor.InternalOutputBuffer里面, 当然里面还涉及到 header 中的数据
    3. 将 InternalOutputBuffer 中的 socketBuffer 刷到浏览器中
    

    其中比较重要的就是 doFlush方法了, 它主要做了下面几步:

    1. 调用 Http11Processor.prepareResponse将 header里面的数据刷到 Http11Processor.InternalOutputBuffer里面(先到 headerbuffer, 后到 socketBuffer)
    2. 通过 org.apache.catalina.connector.Response.OutputBuffer.ByteBuffer.flushBuffer() 将数据刷到 Http11Processor.InternalOutputBuffer.socketBuffer 里面
    

    见代码:

    protected void doFlush(boolean realFlush)
        throws IOException {
    
        if (suspended) {
            return;
        }
    
        try {                                             // 前置, 这里做的就两部 1. 调用 Http11Processor.prepareResponse将 header里面的数据刷到 Http11Processor.InternalOutputBuffer里面(先到 headerbuffer, 后到 socketBuffer), 2. 通过 org.apache.catalina.connector.Response.OutputBuffer.ByteBuffer.flushBuffer() 将数据刷到 Http11Processor.InternalOutputBuffer.socketBuffer 里面
            doFlush = true;
            if (initial) {                               // 1. 回调 Http11Processor  (将 header 中要写的数据 commit 到 Http11Processor.InternalOutputBuffer 里面)
                coyoteResponse.sendHeaders();            // 2. coyoteResponse 就是 org.apache.coyote.Response (将 Http header 里面的信息 刷到 headBuffer 中, 然后刷到 socketBuffer 中, 这里的 headBuffer 与 sendBuffer 都是在 InternalOutputBuffer 中)
                initial = false;
            }
            if (cb.getLength() > 0) {
                cb.flushBuffer();
            }
            if (bb.getLength() > 0) {                    // 3. 这里的 bb(ByteChunk) 存储的是 http 请求的 body 里面的数据
                bb.flushBuffer();                        // 4. bb(ByteChunk) 将自己的数据刷到 org.apache.catalina.connector.OutputBuffer 的 outputChunk, 然后再调用 coyoteResponse.doWrite 刷到 InternalOutputBuffer.socketBuffer 里面(这一步经过很多步)
            }
        } finally {
            doFlush = false;
        }
    
        if (realFlush) {
            coyoteResponse.action(ActionCode.CLIENT_FLUSH,
                                  coyoteResponse);
            // If some exception occurred earlier, or if some IOE occurred
            // here, notify the servlet with an IOE
            if (coyoteResponse.isExceptionPresent()) {
                throw new ClientAbortException
                    (coyoteResponse.getErrorException());
            }
        }
    }
    

    flush其中第一步就是 coyoteResponse.sendHeaders, 这个方法会触发 Http11Processor的action方法, 然后就将Header里面的数据刷到 SocketBuffer里面

    prepareResponse();              // 将 Http header 里面的 请求结果状态, header头部的信息刷到 InternalOutput.headerbuffer 里面, 并且根据头部的信息选择合适的 OutputFilter
    getOutputBuffer().commit();     // 将 headerbuffer 里面的数据刷到 socketBuffer (socketBuffer 是一个 ByteChunk)
    

    最后就是通过 coyoteResponse.finish() 来触发Http11Processor的action动作, 它其实就是通过 socketBuffer.flushBuffer 来刷数据到远端(浏览器)

    11. 总结

    整个"一次请求"的过程涉及

    1. 网络框架层(coyote)
    2. 路由层(Mapper)
    3. 容器链路层(Engine, Host, Context, Wrapper, ApplicationFilterChain, Servlet)
    4. 数据回写(各中 OutputBuffer, InternalOutputBuffer, PS: 里面有些许操作是通过回调)
    

    对于我们个人来说, 我们可以仿照Tomcat的架构层级而设计一个网络框架(PS: 其实完全可以在现在代码的基础上添加一个协议, 做成 一个IM服务也很容易)

    参考:
    Tomcat源码阅读之底层 IO 封装(1)InternalNioInputBuffer的分析
    Tomcat 5 源码分析
    Tomcat 7.0 源码分析——请求原理分析
    Tomcat7 源码解析
    Tomcat源码分析(二)------ 一次完整请求的里里外外

    相关文章

      网友评论

        本文标题:Tomcat 源码分析 一次完整请求 (基于8.0.5)

        本文链接:https://www.haomeiwen.com/subject/vscqlxtx.html