`
Tyrion
  • 浏览: 257792 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Tomcat7中一次请求处理的前世今生(四)Tomcat7阀机制原理

阅读更多

通过前面的三篇文章看到了一次客户端连接在Tomcat内部被转换成了请求对象(org.apache.catalina.connector.Request类的实例),并在该请求对象内部将与本次请求相关的Host、Context、Wrapper对象的引用。本文主要分析该请求对象在容器内部流转的经过。

再来看一下Tomcat7内部的组件结构图:


其实这张图已经给出了答案,在Connector接收到一次连接并转化成请求(Request)后,会将请求传递到Engine的管道(Pipeline)的阀(ValveA)中。请求在Engine的管道中最终会传递到Engine Valve这个阀中。接着请求会从Engine Valve传递到一个Host的管道中,在该管道中最后传递到Host Valve这个阀里。接着从Host Valve传递到一个Context的管道中,在该管道中最后传递到Context Valve中。接下来请求会传递到Wrapper C内的管道所包含的阀Wrapper Valve中,在这里会经过一个过滤器链(Filter Chain),最终送到一个Servlet中。

 

如果你不了解上面这段文字描述中所谓的管道(Pipeline)和阀(Valve)的概念,别急,下面会讲到这个。先从源码层面看下这段文字描述的经过。上一篇文里提到的org.apache.catalina.connector.CoyoteAdapter类的service方法:

    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());

        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;

        try {

            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            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
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

                if (request.isComet()) {
                    if (!response.isClosed() && !response.isError()) {
                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                            // Invoke a read event right away if there are available bytes
                            if (event(req, res, SocketStatus.OPEN)) {
                                comet = true;
                                res.action(ActionCode.COMET_BEGIN, null);
                            }
                        } else {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        }
                    } else {
                        // Clear the filter chain, as otherwise it will not be reset elsewhere
                        // since this is a Comet request
                        request.setFilterChain(null);
                    }
                }

            }
            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
            if (asyncConImpl != null) {
                async = true;
            } else if (!comet) {
                request.finishRequest();
                response.finishResponse();
                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.
                    ((Context) request.getMappingData().context).logAccess(
                            request, response,
                            System.currentTimeMillis() - req.getStartTime(),
                            false);
                }
                req.action(ActionCode.POST_REQUEST , null);
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            req.getRequestProcessor().setWorkerThreadName(null);
            // Recycle the wrapper request and response
            if (!comet && !async) {
                request.recycle();
                response.recycle();
            } else {
                // Clear converters so that the minimum amount of memory
                // is used by this processor
                request.clearEncoders();
                response.clearEncoders();
            }
        }

    }

前一篇文章主要分析了第42行的代码,通过postParseRequest方法的调用请求对象内保存了关于本次请求的具体要执行的Host、Context、Wrapper组件的引用。

看下第47行:

                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

虽然只有一行,但调用了一堆方法,这里对这些方法逐个分析一下:

connector.getService()获取的是当前connector关联的Service组件,默认情况下获得的就是org.apache.catalina.core.StandardService的对象。其getContainer方法获得的是org.apache.catalina.core.StandardEngine的对象,这段的由来在前面讲Digester的解析文章时,createStartDigester方法中的这段代码:

        digester.addRuleSet(new EngineRuleSet("Server/Service/"));

在EngineRuleSet类的addRuleInstances方法中的这一段代码:

    public void addRuleInstances(Digester digester) {
        
        digester.addObjectCreate(prefix + "Engine",
                                 "org.apache.catalina.core.StandardEngine",
                                 "className");
        digester.addSetProperties(prefix + "Engine");
        digester.addRule(prefix + "Engine",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.EngineConfig",
                          "engineConfigClass"));
        digester.addSetNext(prefix + "Engine",
                            "setContainer",
                            "org.apache.catalina.Container");

结合上一段代码可以看出Tomcat启动时,如果碰到server.xml里的Server/Service/Engine节点,先实例化一个org.apache.catalina.core.StandardEngine对象,在第11到13行,会以StandardEngine对象为入参调用org.apache.catalina.core.StandardService的setContainer方法。

 

所以上面connector.getService().getContainer()方法得到的实际上是StandardEngine对象。紧接着的getPipeline方法返回的是StandardEngine类的父类org.apache.catalina.core.ContainerBase类的成员变量pipeline,看下该类中这个变量的声明代码:

    /**
     * The Pipeline object with which this Container is associated.
     */
    protected Pipeline pipeline = new StandardPipeline(this);

所以connector.getService().getContainer().getPipeline()方法返回的是org.apache.catalina.core.StandardPipeline类的对象,该对象就是本文开头部分提到的管道(Pipeline)。

 

下面讲一下Tomcat7中的管道和阀的概念和实现:

所有的管道类都会实现org.apache.catalina.Pipeline这个接口,看下这个接口中定义的方法:


Tomat7中一个管道包含多个阀(Valve),这些阀共分为两类,一类叫基础阀(通过getBasic、setBasic方法调用),一类是普通阀(通过addValve、removeValve调用)。管道都是包含在一个容器当中,所以API里还有getContainer和setContainer方法。一个管道一般有一个基础阀(通过setBasic添加),可以有0到多个普通阀(通过addValve添加)。

所有的阀类都会实现org.apache.catalina.Valve这个接口,看下这个接口中定义的方法:


重点关注setNext、getNext、invoke这三个方法,通过setNext设置该阀的下一阀,通过getNext返回该阀的下一个阀的引用,invoke方法则执行该阀内部自定义的请求处理代码。

Tomcat7里Pipeline的默认实现类是org.apache.catalina.core.StandardPipeline,其内部有三个成员变量:basic、first、container。

    /**
     * The basic Valve (if any) associated with this Pipeline.
     */
    protected Valve basic = null;

    /**
     * The Container with which this Pipeline is associated.
     */
    protected Container container = null;

    /**
     * The first valve associated with this Pipeline.
     */
    protected Valve first = null;

看下该类的addValve方法:

    public void addValve(Valve valve) {
    
        // Validate that we can add this Valve
        if (valve instanceof Contained)
            ((Contained) valve).setContainer(this.container);

        // Start the new component if necessary
        if (getState().isAvailable()) {
            if (valve instanceof Lifecycle) {
                try {
                    ((Lifecycle) valve).start();
                } catch (LifecycleException e) {
                    log.error("StandardPipeline.addValve: start: ", e);
                }
            }
        }

        // Add this Valve to the set associated with this Pipeline
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }
        
        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }

在第18到32行,每次给管道添加一个普通阀的时候如果管道内原来没有普通阀则将新添加的阀作为该管道的成员变量first的引用,如果管道内已有普通阀,则把新加的阀加到所有普通阀链条末端,并且将该阀的下一个阀的引用设置为管道的基础阀。这样管道内的阀结构如下图所示:

 

即Pipeline内部维护first和basic两个阀,其它相关阀通过getNext来获取。

看下getFirst方法的实现:

    public Valve getFirst() {
        if (first != null) {
            return first;
        }
        
        return basic;
    }

如果管道中有普通阀则返回普通阀链条最开始的那个,否则就返回基础阀。

 

在Tomcat7中所有作为普通阀的类的invoke方法实现中都会有这段代码: 

getNext().invoke(request, response);

通过这种机制来保证调用管道最开头一端的阀的invoke方法,最终会执行完该管道相关的所有阀的invoke方法,并且最后执行的必定是该管道基础阀的invoke方法。

 

再回到connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)这段代码的解释,这里将会执行StandardEngine类的管道中的所有阀(包括普通阀和基础阀)的invoke方法,并且最后会执行基础阀的invoke方法

Tomcat7在默认情况下Engine节点没有普通阀,如果想要添加普通阀的话,可以通过在server.xml文件的engine节点下添加Valve节点,参加该文件中的普通阀配置的示例:

<Valve className="org.apache.catalina.authenticator.SingleSignOn" />

那么就来看看StandardEngine类的管道中的基础阀的代码实现。先看下该基础阀设置的代码,在org.apache.catalina.core.StandardEngine对象的构造函数中:

    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

第4行即设置基础阀。所以connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)会执行到org.apache.catalina.core.StandardEngineValve类的invoke方法:

    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);

    }

第5行,从请求对象中取出该请求关联的Host(默认情况下是org.apache.catalina.core.StandardHost对象),请求是如何找到关联的Host的请看前一篇文章。经过上述代码分析应该可以看出第18行会执行StandardHost对象的管道内所有的阀的invoke方法。

 

看下StandardHost的构造方法的实现:

    public StandardHost() {

        super();
        pipeline.setBasic(new StandardHostValve());

    }

所以看下org.apache.catalina.core.StandardHostValve类的invoke方法:

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

第41行,会调用该请求相关的Context的管道内所有的阀的invoke方法,默认情况下Context是org.apache.catalina.core.StandardContext类的对象,其构造方法中设置了管道的基础阀:

    public StandardContext() {

        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        // Set defaults
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            // Strict servlet compliance requires all extension mapped servlets
            // to be checked against welcome files
            resourceOnlyServlets.add("jsp");
        }
    }

看下其基础阀的invoke方法代码:

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

最后的第35行,从请求中取出关联的wrapper对象后调用其管道内所有阀的invoke方法。wrapper对象默认是org.apache.catalina.core.StandardWrapper类的实例,同样是在该类的构造方法中设置的基础阀:

    public StandardWrapper() {

        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();

    }

有兴趣可以看下基础阀org.apache.catalina.core.StandardWrapperValve的invoke方法,在这里最终会调用请求的url所匹配的Servlet相关过滤器(filter)的doFilter方法及该Servlet的service方法(这段实现都是在过滤器链ApplicationFilterChain类的doFilter方法中),这里不再贴出代码分析。

 

这里可以看出容器内的Engine、Host、Context、Wrapper容器组件的实现的共通点:

1.这些组件内部都有一个成员变量pipeline,因为它们都是从org.apache.catalina.core.ContainerBase类继承来的,pipeline就定义在这个类中。所以每一个容器内部都关联了一个管道。

2.都是在类的构造方法中设置管道内的基础阀。

3.所有的基础阀的实现最后都会调用其下一级容器(直接从请求中获取下一级容器对象的引用,在前一篇文章的分析中已经设置了与该请求相关的各级具体组件的引用)的getPipeline().getFirst().invoke()方法,直到Wrapper组件。因为Wrapper是对一个Servlet的包装,所以它的基础阀内部调用的过滤器链的doFilter方法和Servlet的service方法。

 

正是通过这种管道和阀的机制及上述的3点前提,使得请求可以从连接器内一步一步流转到具体Servlet的service方法中。这样,关于本系列文章《Tomcat7中一次请求处理的前世今生》介绍完毕,从中可以看出在浏览器发出一次Socket连接请求之后Tomcat容器内运转处理的大致流程。

  • 大小: 76.4 KB
  • 大小: 20 KB
  • 大小: 18.7 KB
  • 大小: 11.3 KB
  • 大小: 44.6 KB
3
1
分享到:
评论
3 楼 wgsddfe 2015-02-09  
顶一个,楼主写的真好
2 楼 Tyrion 2013-11-03  
flywangfei 写道
StandardPipeline 怎么初始化的

在所有容器组件的父类org.apache.catalina.core.ContainerBase类里面声明即初始化了啊:

    /**
     * The Pipeline object with which this Container is associated.
     */
    protected Pipeline pipeline = new StandardPipeline(this);
1 楼 flywangfei 2013-11-03  
StandardPipeline 怎么初始化的

相关推荐

Global site tag (gtag.js) - Google Analytics