【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第三篇)

2024-10-08 11:04:59 浏览数 (1)

前言

承接上文容器处理机制,当 postParseRequest方法返回真值时,容器将继续处理请求。在 service 方法中,通过 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 这行代码,容器将请求传递至管道的第一步,开启后续的处理流程。

  • Connector 调用 getService() 方法返回 StandardService 对象,
  • StandardService 接着调用 getContainer() 方法返回 StandardEngine 对象,
  • 最终 StandardEngine 调用 getPipeline() 方法返回与其关联的 StandardPipeline 对象。

Engine 处理请求

回顾前文,StandardEngine 的构造函数中,为其关联的 Pipeline 添加了名为 StandardEngineValve 的基本阀,代码如下:

代码语言:javascript复制
public StandardEngine() {
    super();
    pipeline.setBasic(new StandardEngineValve());
    try {
        setJvmRoute(System.getProperty("jvmRoute"));
    } catch(Exception ex) {
        log.warn(sm.getString("standardEngine.jvmRouteFail"));
    }
}

接下来,我们深入探究 StandardEngineValveinvoke() 方法。该方法主要负责选择合适的 Host,并调用该 Host 所关联的 Pipeline 中第一个 Valveinvoke() 方法,将请求传递至下一处理阶段。

代码语言:javascript复制
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Select the Host to be used for this Request
    Host host = request.getHost();
    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
    host.getPipeline().getFirst().invoke(request, response);
}

StandardEngineValveinvoke() 方法逻辑清晰,首先检查当前 Engine 容器是否包含 Host 容器,若不存在,则返回 400 错误。若存在,则继续执行 host.getPipeline().getFirst().invoke(request, response)。可以看到,Host 容器先获取其关联的 Pipeline,再获取 Pipeline 中的第一个 Valve,并调用该 Valveinvoke() 方法,将请求传递至下一个处理阶段。

Host 处理请求

分析 Host 时,我们从其构造函数入手,该方法主要负责设置基础阀门。

代码语言:javascript复制
public StandardHost() {
    super();
    pipeline.setBasic(new StandardHostValve());
}

StandardPipeline 调用 getFirst 方法获取第一个 Valve 来处理请求。由于基本阀通常是最后一个添加的 Valve,因此最终请求会由基本阀进行处理。

StandardHostPipeline 中必然包含 ErrorReportValveStandardHostValve 两个 ValveErrorReportValve 主要负责检测 HTTP 请求过程中是否出现过异常,如果有异常,则直接拼装 HTML 页面,并输出到客户端。

接下来,我们仔细观察 ErrorReportValveinvoke() 方法:

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

ErrorReportValveinvoke() 方法首先执行下一个 Valveinvoke() 方法,然后根据返回的 Request 属性设置一些错误信息。那么下一个 Valve 是谁呢?实际上就是基本阀门 StandardHostValve

接下来,我们深入分析 StandardHostValveinvoke() 方法是如何实现的:

代码语言:javascript复制
@Override
public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // Select the Context to be used for this Request
    Context context = request.getContext();
    if (context == null) {
        response.sendError
            (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
             sm.getString("standardHost.noContext"));
        return;
    }

    // Bind the context CL to the current thread
    if( context.getLoader() != null ) {
        // Not started - it should check for availability first
        // This should eventually move to Engine, it's generic.
        if (Globals.IS_SECURITY_ENABLED) {
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    context.getLoader().getClassLoader());
            AccessController.doPrivileged(pa);
        } else {
            Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
        }
    }
    if (request.isAsyncSupported()) {
        request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }

    // Don't fire listeners during async processing
    // If a request init listener throws an exception, the request is
    // aborted
    boolean asyncAtStart = request.isAsync();
    // An async error page may dispatch to another resource. This flag helps
    // ensure an infinite error handling loop is not entered
    boolean errorAtStart = response.isError();
    if (asyncAtStart || context.fireRequestInitEvent(request)) {

        // Ask this Context to process this request
        try {
            context.getPipeline().getFirst().invoke(request, response);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            if (errorAtStart) {
                container.getLogger().error("Exception Processing "  
                        request.getRequestURI(), t);
            } else {
                request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                throwable(request, response, t);
            }
        }

        // If the request was async at the start and an error occurred then
        // the async error handling will kick-in and that will fire the
        // request destroyed event *after* the error handling has taken
        // place
        if (!(request.isAsync() || (asyncAtStart &&
                request.getAttribute(
                        RequestDispatcher.ERROR_EXCEPTION) != null))) {
            // Protect against NPEs if context was destroyed during a
            // long running request.
            if (context.getState().isAvailable()) {
                if (!errorAtStart) {
                    // Error page processing
                    response.setSuspended(false);

                    Throwable t = (Throwable) request.getAttribute(
                            RequestDispatcher.ERROR_EXCEPTION);

                    if (t != null) {
                        throwable(request, response, t);
                    } else {
                        status(request, response);
                    }
                }

                context.fireRequestDestroyEvent(request);
            }
        }
    }

    // Access a session (if present) to update last accessed time, based on a
    // strict interpretation of the specification
    if (ACCESS_SESSION) {
        request.getSession(false);
    }

    // Restore the context classloader
    if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                StandardHostValve.class.getClassLoader());
        AccessController.doPrivileged(pa);
    } else {
        Thread.currentThread().setContextClassLoader
                (StandardHostValve.class.getClassLoader());
    }
}

StandardHostValveinvoke() 方法首先校验 Request 是否存在 Context,这个 Context 在执行 CoyoteAdapter.postParseRequest 方法时就已经设置好了。如果 Context 不存在,则返回 500 错误。接下来,依然是熟悉的流程:context.getPipeline().getFirst().invoke,该管道获取的是基础阀门 StandardContextValve,我们继续关注它的 invoke() 方法。

Context 处理请求

接着,Context 会去处理请求,同样地,StandardContextValveinvoke() 方法会被调用:

代码语言:javascript复制
@Override
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();
    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);
        return;
    }

    // Select the Wrapper to be used for this Request
    Wrapper wrapper = request.getWrapper();
    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);
}

Wrapper 处理请求

Wrapper 是一个 Servlet 的包装,我们先来了解一下它的构造方法。其主要作用是设置基础阀门 StandardWrapperValve

接下来,我们来分析 StandardWrapperValveinvoke() 方法。

代码语言:javascript复制
@Override
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();
    requestCount.incrementAndGet();
    StandardWrapper wrapper = (StandardWrapper) getContainer();
    Servlet servlet = null;
    Context context = (Context) wrapper.getParent();

    // Check for the application being marked unavailable
    if (!context.getState().isAvailable()) {
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardContext.isUnavailable"));
        unavailable = true;
    }

    // Check for the servlet being marked unavailable
    if (!unavailable && wrapper.isUnavailable()) {
        container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
                wrapper.getName()));
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    sm.getString("standardWrapper.isUnavailable",
                            wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                    sm.getString("standardWrapper.notFound",
                            wrapper.getName()));
        }
        unavailable = true;
    }

    // Allocate a servlet instance to process this request
    try {
        // 关键点1:这儿调用Wrapper的allocate()方法分配一个Servlet实例
        if (!unavailable) {
            servlet = wrapper.allocate();
        }
    } catch (UnavailableException e) {
        container.getLogger().error(
                sm.getString("standardWrapper.allocateException",
                        wrapper.getName()), e);
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardWrapper.isUnavailable",
                                    wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                       sm.getString("standardWrapper.notFound",
                                    wrapper.getName()));
        }
    } catch (ServletException e) {
        container.getLogger().error(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), StandardWrapper.getRootCause(e));
        throwable = e;
        exception(request, response, e);
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.allocateException",
                         wrapper.getName()), e);
        throwable = e;
        exception(request, response, e);
        servlet = null;
    }

    MessageBytes requestPathMB = request.getRequestPathMB();
    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
    // 关键点2,创建过滤器链,类似于Pipeline的功能
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    // Call the filter chain for this request
    // NOTE: This also calls the servlet's service() method
    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 关键点3,调用过滤器链的doFilter,最终会调用到Servlet的service方法
                        filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                    }
                } finally {
                    String log = SystemLogHandler.stopCapture();
                    if (log != null && log.length() > 0) {
                        context.getLogger().info(log);
                    }
                }
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else {
                    // 关键点3,调用过滤器链的doFilter,最终会调用到Servlet的service方法
                    filterChain.doFilter
                        (request.getRequest(), response.getResponse());
                }
            }

        }
    } catch (ClientAbortException e) {
        throwable = e;
        exception(request, response, e);
    } catch (IOException e) {
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        throwable = e;
        exception(request, response, e);
    } catch (UnavailableException e) {
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        //            throwable = e;
        //            exception(request, response, e);
        wrapper.unavailable(e);
        long available = wrapper.getAvailable();
        if ((available > 0L) && (available < Long.MAX_VALUE)) {
            response.setDateHeader("Retry-After", available);
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                       sm.getString("standardWrapper.isUnavailable",
                                    wrapper.getName()));
        } else if (available == Long.MAX_VALUE) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND,
                        sm.getString("standardWrapper.notFound",
                                    wrapper.getName()));
        }
        // Do not save exception in 'throwable', because we
        // do not want to do exception(request, response, e) processing
    } catch (ServletException e) {
        Throwable rootCause = StandardWrapper.getRootCause(e);
        if (!(rootCause instanceof ClientAbortException)) {
            container.getLogger().error(sm.getString(
                    "standardWrapper.serviceExceptionRoot",
                    wrapper.getName(), context.getName(), e.getMessage()),
                    rootCause);
        }
        throwable = e;
        exception(request, response, e);
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString(
                "standardWrapper.serviceException", wrapper.getName(),
                context.getName()), e);
        throwable = e;
        exception(request, response, e);
    }

    // Release the filter chain (if any) for this request
    // 关键点4,释放掉过滤器链及其相关资源
    if (filterChain != null) {
        filterChain.release();
    }

    // 关键点5,释放掉Servlet及相关资源
    // Deallocate the allocated servlet instance
    try {
        if (servlet != null) {
            wrapper.deallocate(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);
        }
    }

    // If this servlet has been marked permanently unavailable,
    // unload it and release this instance
    // 关键点6,如果servlet被标记为永远不可达,则需要卸载掉它,并释放这个servlet实例
    try {
        if ((servlet != null) &&
            (wrapper.getAvailable() == Long.MAX_VALUE)) {
            wrapper.unload();
        }
    } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.unloadException",
                         wrapper.getName()), e);
        if (throwable == null) {
            throwable = e;
            exception(request, response, e);
        }
    }
    long t2=System.currentTimeMillis();

    long time=t2-t1;
    processingTime  = time;
    if( time > maxTime) maxTime=time;
    if( time < minTime) minTime=time;
}

通过阅读源码,我们发现了一些关键点,如下所示,后面我们会逐一分析这些关键点相关的源码:

  1. 调用 Wrapper 的 allocate() 方法分配一个 Servlet 实例
  2. 创建过滤器链,类似于 Pipeline 的功能
  3. 调用过滤器链的 doFilter,最终会调用到 Servlet 的 service 方法
  4. 释放掉过滤器链及其相关资源
  5. 释放掉 Servlet 及相关资源
  6. 如果 Servlet 被标记为永远不可达,则需要卸载掉它,并释放这个 Servlet 实例

由于篇幅问题,我们将在下篇中深入解读关键代码。 感兴趣的欢迎点赞关注哟。

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

0 人点赞