preface

Connectors in Tomcat are designed to listen to network ports, get connection requests, and then convert servlet-compliant requests to the container for processing. So this article will follow the ideas of the previous article. Look at a request to the container and how the container requests it.

Note: The Tomcat version of this article is 9.0.21. It is not recommended for zero-base readers.

Start with the Adapter

We continue to follow the source of the Adapter in the previous article, continue to analyze, the source code at the end of the previous article is as follows:

1. Class:  CoyoteAdapter implements Adapter 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); postParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {
                //check valves ifwe support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); }}Copy the code

The main function of the above source code is to get the container, then call getPipeline() to get the Pipeline, and finally call invoke. Let’s see what the Pipeline does.

Public interface extends extends Contained {public Valve getBasic(); public voidsetBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void removeValve(Valve valve); public Valve getFirst(); public boolean isAsyncSupported(); public void findNonAsyncValves(Set<String> result); Public interface {public interface getNext(); public voidsetNext(Valve valve);
 public void backgroundProcess();
 public void invoke(Request request, Response response)
        throws IOException, ServletException;
 public boolean isAsyncSupported();
Copy the code

A Pipeline is a pipe, and a Valve is a Valve. Each container has a pipe with multiple valves in the pipe. We prove this by the following analysis.

Pipeline-Valve

The above source code is the interface between Pipeline and Valve. Pipeline mainly sets up Valve, which is a linked list, and then invokes. Let’s review the source code:

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

Here is the pipe that gets the container directly, and then gets the first Valve to make the call. As mentioned earlier, Valve is a linked list, and only the first one is called, which can be called from Next to the last one. As mentioned in our first article “How to start Tomcat in SpringBoot”, the container is divided into four children: Engine, Host, Context, and Wrapper. They are also parent and child relationships. Engine > Host > Context > Wrapper.

I mentioned earlier that each container has a Pipeline, so how does this manifest? Pipeline is a basic property of the container interface definition:

Public interface Container extends Lifecycle {** * Return the Pipe object that manages the Valves associated with * this Container. * * @return The Pipeline
     */
    public Pipeline getPipeline();
    
}
Copy the code

We know that each container has a Pipeline, there are many valves in the Pipeline,Valve can make chain call, then the question comes, how does the Valve in the parent container pipe call the Valve in the child container? In the Pipeline implementation class StandardPipeline, we found the following source code:

6. * The basic Valve (ifany) associated with this Pipeline. */ protected Valve basic = null; /** * The first valve associated with this Pipeline. */ protected Valve first = null; Public void addValve(Valve Valve) {// Omit some code // Add this Valve to theset associated with this Pipeline
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while(current ! = null) {// This loop sets Valve to ensure that the last one is basicif (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }

        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }
Copy the code

According to the above code, we know that BASIC is the last valve in a Pipeline, and all chain calls can be completed as long as the last valve is the first valve of the next container. Let’s use a request debug to see if it is as we suspected. We can make a breakpoint in the service method of the CoyoteAdapter. The effect is as follows:

Here we can see that when the adapter calls the container, the pipe that calls the Engine, there is only one valve, basic, with a StandardEngineValve value. We found the invoke method of the valve as follows:

Public final void invoke(Request Request, Response Response) throws IOException, ServletException { // Select the Host to be usedfor this Request
        Host host = request.getHost();
        if(host == null) {// HTTP 0.9 or HTTP 1.0 request without a host when no default host is defined. This is handled by the CoyoteAdapter.return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);
    }
Copy the code

We continue to debug and check the result as follows:

So basic here will actually call the pipelines and valves of the Host container, that is, the BASIC in each container pipe is the Valve that calls the next child container. Let me draw a picture:

This diagram clearly describes how requests flow through the container inside Tomcat. Requests from the Connector go to the Engine container, which makes chain calls through the valves in the Pieline. The last Basic valve is responsible for calling the first valve in the next container, all the way to the Wrapper, which then executes the Servlet.

Let’s look at the Wrapper source and see if it really does what we say:

Public final void invoke(Request Request, Response Response) throws IOException, ServletException {// omit some source code Servlet ServletException = null;if(! unavailable) { servlet = wrapper.allocate(); } // Create the filter chainforthis request ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);  filterChain.doFilter(request.getRequest(), response.getResponse()); }Copy the code

If you look at this, you might say that the Filter is created and called, not the Servlet. Yes, the Servlet is not called, but we know that the Filter is executed before the Servlet. In other words, After filterchain. doFilter is executed, the Servlet is executed. Let’s see if the source code for ApplicationFilterChain does what we say:

Public void 9doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {// omit some code internalDoFilter(request,response); Private void internalDoFilter(ServletRequest Request, ServletResponse Response) throws IOException, ServletException {// omit some code // Call the next filterif there is one
        if(pos < n) {// omit some code ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this);return; } // We fell off the end of the chain -- call the servlet instance servlet.service(request, response);Copy the code

From the source code, we can see that after calling all the filters, the servlet starts calling the service. Let’s look at the servlet implementation class

Here we are familiar with HttpServlet and GenericServlet classes of the Tomcat package, in fact only HttpServlet, because GenericServlet is the parent class of HttpServlet. After that, it is handed over to the framework to handle, and Tomcat internal requests are completed at this point.

Tomcat multi-application isolation implementation

We know that Tomcat supports deployment of multiple applications. How does Tomcat support deployment of multiple applications? How do you ensure that multiple applications don’t get confused? To understand this, let’s go back to the adapter, to the Service method

// source code 11. CoyoteAdapter public void service(org.apache.coyote.Request req, Org. Apache. Coyote. Response res) throws the Exception {/ / / / Parse and omit part of codesetPostParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {
                //check valves ifwe support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); }}Copy the code

Connector.getservice ().getContainer().getPipeline().getFirst().invoke(request, response) But there is a postParseRequest method that handles the mapping request before calling the container.

// source code 12. Class: CoyoteAdapter protected boolean postParseRequest(org.apache.coyote.Request req, Request request, Org. Apache. Coyote. Response res, the Response Response) throws IOException, ServletException {omit part of the code Boolean mapRequired =true;
         while(mapRequired) { // This will map the the latest version by default connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData()); // A 404 error was reported when the context was not foundif (request.getContext() == null) {
                // Don't overwrite an existing error if (! response.isError()) { response.sendError(404, "Not found"); } // Allow processing to continue. // If present, the error reporting valve will provide a response // body. return true; }}Copy the code

If the Context is not found, a 404 error is returned.

// source code 13. Mapper public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {if (host.isNull()) {
            String defaultHostName = this.defaultHostName;
            if (defaultHostName == null) {
                return; } host.getCharChunk().append(defaultHostName); } host.toChars(); uri.toChars(); internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData); } // source code 14. Mapper private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData MappingData) throws IOException {// Omitting the code // Virtual host mapping processing host mapping MappedHost[] hosts = this.hosts;  MappedHost mappedHost = exactFindIgnoreCase(hosts, host); // Omit some codeif (mappedHost == null) {
             mappedHost = defaultHost;
            if (mappedHost == null) {
                return; } } mappingData.host = mappedHost.object; ContextList ContextList = mappedhost. ContextList; MappedContext[] contexts = contextList.contexts; // Omit some codeif (context == null) {
            return; } mappingData.context = contextVersion.object; mappingData.contextSlashCount = contextVersion.slashCount; // Wrapper Mapping handles Servlet mappingif (!contextVersion.isPaused()) {
            internalMapWrapper(contextVersion, uri, mappingData);
        }

    }    
Copy the code

Due to the above source code is more, I omit a lot of code, retain can understand the main logic of the code, in general, Url processing includes three parts, mapping Host, mapping Context and mapping Servlet(in order to save space, the specific details of the source code please interested students to study).

One detail we can see here is that the three processing logic are closely related, and the Context will only be processed if Host is not empty. The same is true for servlets. So if the Host configuration is different, all subsequentsubcontainers will be different, and the application isolation effect will be complete. However, for SpringBoot embedded Tomcat mode (starting with JAR package), there is no mode to realize multiple applications, and an application itself is a Tomcat.

For the sake of understanding, I have also drawn a picture of multiple application isolation. Here we assume that there are two domain names admin.luozhou.com and web.luozhou.com. Then I deploy two applications under each domain name: User,log,blog, and shop. So when I want to add a User, I will request the add Servlet in the Context of User admin.luozhou.com. The design of this example is not consistent with practical development principles. The granularity of add should be done by controllers in the framework, not servlets.

conclusion

In this article we looked at how Tomcat containers handle requests. Let’s review:

  • The connector throws the request to the adapter and calls the container (Engine)
  • Inside the container is a conduit (Pieline) – valves (Valve) mode to complete the container call, the parent container call child container mainly through onebasicTo complete the valve.
  • And then the last subcontainerwrapperOnce the call is complete, the filter is built to make the filter call, and then the last step inside Tomcat is to call the servlet. We can also understand what we useHttpServlet, all based onServletThe framework of the specification enters the framework process (including SpringBoot) here.
  • Finally, we also analyze how Tomcat implements multi-application isolation. Through the analysis of multi-application isolation, we also understand why Tomcat designs so many sub-containers, which can achieve different granularity isolation levels according to the needs of different scenarios.

Copyright notice: Original article, reprint please indicate the source.