首页 > 其他 > 详细

深入理解Tomcat系列之四:Engine和Host容器

时间:2016-03-28 00:19:38      阅读:152      评论:0      收藏:0      [点我收藏+]

前言

终于到Container容器了,上面说到Connector把封装了Request对象以及Response对象的Socket传递给了Container容器,那么在Contianer容器中又是怎么样的处理流程呢?在说Container容器之前,有必要对Container容器有一个简单的了解,Container容器是子容器的父接口,所有的子容器都必须实现这个接口,在Tomcat中Container容器的设计是典型的责任链设计模式,其有四个子容器:Engine、Host、Context和Wrapper。这四个容器之间是父子关系,Engine容器包含Host,Host包含Context,Context包含Wrapper。

我们在web项目中的一个Servlet类对应一个Wrapper,多个Servlet就对应多个Wrapper,当有多个Wrapper的时候就需要一个容器来管理这些Wrapper了,这就是Context容器了,Context容器对应一个工程,所以我们新部署一个工程到Tomcat中就会新创建一个Context容器。Container容器的处理过程也比较复杂,下面是一个大概的流程:

技术分享

上面出现了Pipeline与Valve,这两个对象可以分别理解为管道与管道中闸门,当收到从Connector的请求后,这个请求要通过一个个管道以及管道中一个个的闸门,只有全部通过才能最终被具体的Servlet处理。要注意的是,每一个容器都有自己的管道和闸门,这些管道与闸门都是由容器自身老控制的,所以我们可以看到注入StandardEngineValve等类了。

下面就从Container容器的四个子容器入手,分析每一个容器是怎么样处理的:

Engine容器
Engine容器包含Host容器,根据文章第一部分的架构图,可以知道其管理的容器是Host,Engine是一个接口,其标准实现类是StandardEngine,下面是其类结构图:

技术分享
技术分享

注意其中的addChild方法,其类型是Container,但是其实际管理的就是Host容器。Engine容器处理请求的流程可以简化如下:

技术分享

在刚开始的流程图中调用了StandardEngineValve的invoke方法,这个方法的具体实现如何呢?

代码清单4-1:

/**
 * Select the appropriate child Host to process this request,
 * based on the requested server name.  If no matching Host can
 * be found, return an appropriate HTTP error.
 *
 * @param request Request to be processed
 * @param response Response to be produced
 *
 * @exception IOException if an input/output error occurred
 * @exception ServletException if a servlet error occurred
 */
@Override
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);
}

可以看到这个方法的任务就是选择可用的Host容器处理当前的请求,选择Host容器后,就调用其invoke方法,所以具体的处理就转移到了Host容器。

Host容器
Host容器是Engine容器的子容器,上面也说到Host是受Engine容器管理的,就是指一个虚拟主机,比如我们在访问具体jsp页面URL中localhost就是一个虚拟主机,其作用是运行多个应用,并对这些应用进行管理,其子容器是Context,而且一个主机还保存了主机的相关信息。Host的标准实现类是StandardHost,其闸门实现是StandardHostValve,下面是StandardHost与StandardHostValve的类结构图:

技术分享
技术分享

Host容器的处理流程可以简化如下:

技术分享

接着我们回到Engine容器的invoke方法,下面是host.getPipeline().getFirst().invoke(request, response)的方法源码:

代码清单4-2:

@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());
    }
}

其处理过程可以总结如下:
1. 为特定的请求URL选择一个Context容器
2. 把Context容器绑定到线程中
3. 判断是否是一个异步请求
4. 让Context去处理这个请求
5. Context执行invoke方法,进入管道中,由StandardContextValve(是ContextValve的标准实现类)处理

深入理解Tomcat系列之四:Engine和Host容器

原文:http://blog.csdn.net/u011116672/article/details/50994015

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!