Through this a series of former three parts saw a client connection within the Tomcat was transformed into the Request object (org. Apache. Catalina. The Request instance of a class), Inside the request object are references to the Host, Context, and Wrapper objects associated with the request. This paper mainly analyzes the flow of the request object inside the container.

Take a look at the internal components of Tomcat 7:

If you are not familiar with the concepts of pipelines and valves described in the above paragraph, don’t worry, it will be covered below. Take a look at this paragraph of text description from the source level first. Mentioned above org. Apache. Catalina. The CoyoteAdapter class methods of the service:

     1	    public void service(org.apache.coyote.Request req,
     2	                        org.apache.coyote.Response res)
     3	        throws Exception {
     4	
     5	        Request request = (Request) req.getNote(ADAPTER_NOTES);
     6	        Response response = (Response) res.getNote(ADAPTER_NOTES);
     7	
     8	        if(request == null) { 9 10 // Create objects 11 request = connector.createRequest(); 12 request.setCoyoteRequest(req); 13 response = connector.createResponse(); 14 response.setCoyoteResponse(res); 15 16 // Link objects 17 request.setResponse(response); 18 response.setRequest(request); 19 20 // Set as notes 21 req.setNote(ADAPTER_NOTES, request); 22 res.setNote(ADAPTER_NOTES, response); 23 24 // Set query string encoding 25 req.getParameters().setQueryStringEncoding 26 (connector.getURIEncoding()); 27 28} 29 30if (connector.getXpoweredBy()) {
    31	            response.addHeader("X-Powered-By", POWERED_BY);
    32	        }
    33	
    34	        boolean comet = false;
    35	        boolean async = false;
    36	
    37	        try {
    38	
    39	            // Parse and set Catalina and configuration specific
    40	            // request parameters
    41	            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
    42	            boolean postParseSuccess = postParseRequest(req, request, res, response);
    43	            if (postParseSuccess) {
    44	                //check valves ifwe support async 45 request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported()); 46 // Calling the container 47 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);  48 and 49if (request.isComet()) {
    50	                    if(! response.isClosed() && ! response.isError()) { 51if(request.getAvailable() || (request.getContentLength() > 0 && (! request.isParametersParsed()))) { 52 // Invoke aread event right away if there are available bytes
    53	                            if (event(req, res, SocketStatus.OPEN)) {
    54	                                comet = true; 55 res.action(ActionCode.COMET_BEGIN, null); 56}} 57else {
    58	                            comet = true; 59 res.action(ActionCode.COMET_BEGIN, null); 60}} 61else {
    62	                        // Clear the filter chain, as otherwise it will not be reset elsewhere
    63	                        // since this is a Comet request
    64	                        request.setFilterChain(null);
    65	                    }
    66	                }
    67	
    68	            }
    69	            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
    70	            if(asyncConImpl ! = null) { 71 async =true; 72}else if(! comet) { 73 request.finishRequest(); 74 response.finishResponse(); 75if(postParseSuccess && 76 request.getMappingData().context ! = null) { 77 // Log onlyif processing was invoked.
    78	                    // If postParseRequest() failed, it has already logged it.
    79	                    // If context is null this was the start of a comet request
    80	                    // that failed and has already been logged.
    81	                    ((Context) request.getMappingData().context).logAccess(
    82	                            request, response,
    83	                            System.currentTimeMillis() - req.getStartTime(),
    84	                            false); 85 } 86 req.action(ActionCode.POST_REQUEST , null); 87 } 88 89 } catch (IOException e) { 90 // Ignore 91 } finally { 92 req.getRequestProcessor().setWorkerThreadName(null);  93 // Recycle the wrapper request and response 94if(! comet && ! async) { 95 request.recycle(); 96 response.recycle(); 97}else{ 98 // Clear converters so that the minimum amount of memory 99 // is used by this processor 100 request.clearEncoders(); 101 response.clearEncoders(); 102} 103} 104 105}Copy the code

In line 42, the postParseRequest method calls the request object with references to the Host, Context, and Wrapper components to be executed for this request.

Look at line 47:

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

There’s only one line, but a bunch of methods are called. Here’s a look at each of them:

The getService () to obtain the current connector associated Service components, obtained by default is org. Apache. Catalina. Core. StandardService object. Its getContainer method is org. Apache. Catalina. Core. StandardEngine object, the origin of this period in the previous Digester analytical articles, createStartDigester method in this code:

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

This code in the addRuleInstances method of the EngineRuleSet class:

    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");
Copy the code

In combination with a piece of code can be seen on the Tomcat startup, if server. The server/Service/Engine in the XML node, to instantiate a org. Apache. Catalina. Core. StandardEngine object, In line 11 to 13, can take StandardEngine object for the reference call org. Apache. Catalina. Core. StandardService setContainer method.

So the above connector.getService().getContainer() method actually yields a StandardEngine object. Then getPipeline method returns the StandardEngine class parent org. Apache. Catalina. Core. ContainerBase class member variables pipeline, look at the variables in the class declaration code:

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

So the getService (). GetContainer () getPipeline () method returns the org. Apache. Catalina. Core. StandardPipeline objects of a class, This object is the Pipeline mentioned at the beginning of this section.

Here are the concepts and implementations of pipes and valves in Tomcat 7:

All pipe classes implement org. Apache. Catalina. Pipeline this interface, see the methods defined in the interface:

All the Valve class will implement org. Apache. Catalina. Steam this interface, see the methods defined in the interface:

Tomcat 7 in the Pipeline’s default implementation class is org. Apache. Catalina. Core. StandardPipeline, its internal has three member variables: basic, first, the 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;
Copy the code

Take a look at the addValve method for this class:

     1	    public void addValve(Valve valve) {
     2	    
     3	        // Validate that we can add this Valve
     4	        if (valve instanceof Contained)
     5	            ((Contained) valve).setContainer(this.container);
     6	
     7	        // Start the new component if necessary
     8	        if (getState().isAvailable()) {
     9	            if (valve instanceof Lifecycle) {
    10	                try {
    11	                    ((Lifecycle) valve).start();
    12	                } catch (LifecycleException e) {
    13	                    log.error("StandardPipeline.addValve: start: ", e);
    14	                }
    15	            }
    16	        }
    17	
    18	        // Add this Valve to the set associated with this Pipeline
    19	        if(first == null) { 20 first = valve; 21 valve.setNext(basic); 22}else {
    23	            Valve current = first;
    24	            while(current ! = null) { 25if (current.getNext() == basic) {
    26	                    current.setNext(valve);
    27	                    valve.setNext(basic);
    28	                    break; 29 } 30 current = current.getNext(); 31 } 32 } 33 34 container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve); 35}Copy the code

In 18 to 32 lines, each time you add a normal to pipeline valve if there is no ordinary pipeline within the original valve valve of the newly added as reference member variables of the pipeline first, if already in the pipeline, the common valve, then add a new valve to all ordinary valve chain ends, and the valve under the basis of a reference is set to the pipeline valve valve. In this way, the valve structure in the pipeline is shown as follows:

Take a look at the implementation of the getFirst method:

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

Return to the beginning of the common valve chain if there is a common valve in the pipe, otherwise return to the base valve.

In Tomcat 7, the invoke method implementation of all classes that are generic valves will have this code:

getNext().invoke(request, response);
Copy the code

This mechanism is used to ensure that the invoke method of the valve at the beginning of the pipe will eventually execute all the invoke methods of the valve related to the pipe, and the invoke method of the base valve of the pipe will be executed last.

Return to connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) The Invoke methods of all valves in the Pipeline of the StandardEngine class (both normal and base) will be executed, and finally the invoke methods of the base valve will be executed.

Tomcat 7 Engine nodes do not have common valves by default. If you want to add a common Valve, you can add a Valve node under the Engine node in the server.xml file as an example of common Valve configuration in this file:

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

So let’s look at the code implementation of the base valve in the pipe of the StandardEngine class. First look at the valve set the basis of the code, in the org. Apache. Catalina. Core. StandardEngine object constructor:

     1	    public StandardEngine() {
     2	
     3	        super();
     4	        pipeline.setBasic(new StandardEngineValve());
     5	        /* Set the jmvRoute using the system property jvmRoute */
     6	        try {
     7	            setJvmRoute(System.getProperty("jvmRoute"));
     8	        } catch(Exception ex) {
     9	            log.warn(sm.getString("standardEngine.jvmRouteFail")); 10 } 11 // By default, the engine will hold the reloading thread 12 backgroundProcessorDelay = 10; 13 14}Copy the code

Line 4 sets the base valve. So the getService (). GetContainer () getPipeline (). The getFirst () invoke (request, Response) will perform to the org. Apache. Catalina. Core. StandardEngineValve invoke methods of a class:

     1	    public final void invoke(Request request, Response response)
     2	        throws IOException, ServletException {
     3	
     4	        // Select the Host to be used for this Request
     5	        Host host = request.getHost();
     6	        if (host == null) {
     7	            response.sendError
     8	                (HttpServletResponse.SC_BAD_REQUEST,
     9	                 sm.getString("standardEngine.noHost", 
    10	                              request.getServerName()));
    11	            return; 12} 13if(request.isAsyncSupported()) { 14 request.setAsyncSupported(host.getPipeline().isAsyncSupported()); 15 } 16 17 // Ask this Host to process this request 18 host.getPipeline().getFirst().invoke(request, response); 19 20}Copy the code

Line 5, from the request object to retrieve the request associated with the Host (default is org. Apache. Catalina. Core. StandardHost object), the request is how to find the associated part of the Host before this, please. From the above code analysis, you should see that line 18 executes the Invoke method of all valves in the pipe of the StandardHost object.

Take a look at StandardHost’s constructor implementation:

    public StandardHost() {

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

    }
Copy the code

So take a look at org. Apache. Catalina. Core. StandardHostValve invoke methods of a class:

1 public final void invoke(Request request, Response response) 2 throws IOException, ServletException { 3 4 // Select the Context to be used for this Request 5 Context context = request.getContext(); 6 if (context == null) { 7 response.sendError 8 (HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 9 sm.getString("standardHost.noContext")); 10 return; 11 } 12 13 // Bind the context CL to the current thread 14 if( context.getLoader() ! = null ) { 15 // Not started - it should check for availability first 16 // This should eventually move to Engine, it's generic. 17 if (Globals.IS_SECURITY_ENABLED) { 18 PrivilegedAction pa = new PrivilegedSetTccl( 19 context.getLoader().getClassLoader()); 20 AccessController.doPrivileged(pa); 21 } else { 22 Thread.currentThread().setContextClassLoader 23 (context.getLoader().getClassLoader()); 24 } 25 } 26 if (request.isAsyncSupported()) { 27 request.setAsyncSupported(context.getPipeline().isAsyncSupported()); 28 } 29 30 // Don't fire listeners during async processing 31 // If a request init listener throws an exception, the request is 32 // aborted 33 boolean asyncAtStart = request.isAsync(); 34 // An async error page may dispatch to another resource. This flag helps 35 // ensure an infinite error handling loop  is not entered 36 boolean errorAtStart = response.isError(); 37 if (asyncAtStart || context.fireRequestInitEvent(request)) { 38 39 // Ask this Context to process this request 40 try  { 41 context.getPipeline().getFirst().invoke(request, response); 42 } catch (Throwable t) { 43 ExceptionUtils.handleThrowable(t); 44 if (errorAtStart) { 45 container.getLogger().error("Exception Processing " + 46 request.getRequestURI(), t); 47 } else { 48 request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); 49 throwable(request, response, t); 50 } 51 } 52 53 // If the request was async at the start and an error occurred then 54 // the async error handling will kick-in and that will fire the 55 // request destroyed event *after* the error handling has taken 56 // place 57 if (! (request.isAsync() || (asyncAtStart && 58 request.getAttribute( 59 RequestDispatcher.ERROR_EXCEPTION) ! = null))) { 60 // Protect against NPEs if context was destroyed during a 61 // long running request. 62 if (context.getState().isAvailable()) { 63 if (! errorAtStart) { 64 // Error page processing 65 response.setSuspended(false); 66 67 Throwable t = (Throwable) request.getAttribute( 68 RequestDispatcher.ERROR_EXCEPTION); 69 70 if (t ! = null) { 71 throwable(request, response, t); 72 } else { 73 status(request, response); 74 } 75 } 76 77 context.fireRequestDestroyEvent(request); 78 } 79 } 80 } 81 82 // Access a session (if present) to update last accessed time, based on a 83 // strict interpretation of the specification 84 if (ACCESS_SESSION) { 85 request.getSession(false); 86 } 87 88 // Restore the context classloader 89 if (Globals.IS_SECURITY_ENABLED) { 90 PrivilegedAction pa = new PrivilegedSetTccl( 91 StandardHostValve.class.getClassLoader()); 92 AccessController.doPrivileged(pa); 93 } else { 94 Thread.currentThread().setContextClassLoader 95 (StandardHostValve.class.getClassLoader()); 97 96}}Copy the code

41 lines, to provide the Context to invoke the request all of the pipe valve invoke method, by default Context is org. Apache. Catalina. Core. StandardContext objects of a class, The basic valve of the pipeline is set in the construction method:

    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"); }}Copy the code

Take a look at the invoke method code for its base valve:

1 public final void invoke(Request request, Response response) 2 throws IOException, ServletException { 3 4 // Disallow any direct access to resources under WEB-INF or META-INF 5 MessageBytes requestPathMB  = request.getRequestPathMB(); 6if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
     7	                || (requestPathMB.equalsIgnoreCase("/META-INF"))
     8	                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
     9	                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
    10	            response.sendError(HttpServletResponse.SC_NOT_FOUND);
    11	            return;
    12	        }
    13	
    14	        // Select the Wrapper to be used for this Request
    15	        Wrapper wrapper = request.getWrapper();
    16	        if (wrapper == null || wrapper.isUnavailable()) {
    17	            response.sendError(HttpServletResponse.SC_NOT_FOUND);
    18	            return;
    19	        }
    20	
    21	        // Acknowledge the request
    22	        try {
    23	            response.sendAcknowledgement();
    24	        } catch (IOException ioe) {
    25	            container.getLogger().error(sm.getString(
    26	                    "standardContextValve.acknowledgeException"), ioe);
    27	            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
    28	            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    29	            return; 30} 31 and 32if(request.isAsyncSupported()) { 33 request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported()); 34 } 35 wrapper.getPipeline().getFirst().invoke(request, response); 36}Copy the code

Finally, line 35 calls the Invoke method of all valves in its pipe after the associated Wrapper object is retrieved from the request. Wrapper object. Default is org apache. Catalina. Core. StandardWrapper instances of the class, also is the basis of the constructor of the class valves:

    public StandardWrapper() {

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

    }
Copy the code

Interested can look at the basic valve org. Apache. Catalina. Core. StandardWrapperValve invoke method, This is where the doFilter method of the servlet-related filter matched by the requested URL and the Servlet’s service method are eventually called (both in the FilterChain ApplicationFilterChain class) DoFilter method), where code analysis is no longer posted.

We can see that the Engine, Host, Context, and Wrapper container components have something in common:

  1. Within these components are a member variable pipeline, because they are derived from org. Apache. Catalina. Core. ContainerBase class inherited, pipeline is defined in this class. So each container has a pipe associated with it.

  2. Both set the base valve in the pipe in the constructor of the class.

  3. All base valve implementations end by calling the getPipeline().getFirst().invoke() method of their next-level container (which gets a reference to the next-level container object directly from the request, having already set references to the specific component of that level associated with the request in the analysis above). Until the Wrapper component. Because Wrapper is a Wrapper around a Servlet, its base valve calls the doFilter method of the filter chain and the service method of the Servlet inside.

It is through this pipe-and-valve mechanism and the three premises mentioned above that requests can flow step by step from the connector into the service method of the specific Servlet. This completes the analysis of a Tomcat 7 request, showing the general flow of processing in the Tomcat container after the browser makes a Socket connection request.