When a browser request is sent in, Tomcat does the following macro things: 1. Receive request data; 2. Invoke the corresponding Servlet for processing. 3. Return the data to the browser. How do you do this inside Tomcat?

Tomcat handles HTTP requests

In the previous architecture, we mentioned the decoupling design of the Connector and Container. It’s even clearer here:

1. The user clicks on the web content, and the request is sent to the native port 8080, picked up by the Coyote HTTP/1.1 Connector listening there;

2.Connector sends the request to the Service Engine for processing and waits for the Engine to respond.

Localhost /test/index. JSP = localhost/test/index. JSP

4. The Engine matches the Host tag named localhost, which gets the request /test/index.jsp and matches all the contexts it owns.

5. The path= “/test” Context gets the request /index.jsp and finds the corresponding Servlet in its mapping table. Context matches a Servlet whose URL PATTERN is *.jsp, corresponding to the JspServlet class;

6. Construct an HttpServletRequest object and an HttpServletResponse object that calls doGet () or doPost () of the JspServlet as arguments. Execute business logic, data storage, etc.;

7.Context returns the HttpServletResponse object to Host;

8.Host returns the HttpServletResponse object to Engine;

9.Engine returns the HttpServletResponse object to Connector;

10.Connector returns the HttpServletResponse object to client Browser;

The classic chain of responsibility model

Let’s start with step 2 and follow the Tomcat source code to see how it performs this sequence of requests in code.

Breakpoint on org. Apache. Catalina. CoyoteAdapter. The Java classes of the Service (), the class is responsible for the call from the connector to the entrance of the Container. Can be seen in the figure, the execution is StandardEngineValve. Invoke ().

But where does getPipeline () come from in StandardEngine? We’ll have to find its superclass ContainerBase getPipeline (), you can see the returned is a StandardPipeline object:

Go directly to StandardPipeline’s top-level interface and look at its methods:

To take a look at the StandardEngineValve. Invoke () :

public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Get the host contained in Engine
        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());
        }

        // Invoke () on the next node after the current node completes execution.
        // This is the key to putting all pipelines together
        host.getPipeline().getFirst().invoke(request, response);

    }
Copy the code

So this is the chain of responsibility pattern. Once triggered from coyoteadapter.service (), the code is executed along the chain of responsibility StandardHostValve, StandardContextValve, and StandardWrapperValve.

Invoke () is invoked directly when connecting, when is the valve set for StandardPipeline?

Let’s find its set method and see where it was called:

public StandardEngine(a) {
    super(a); 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;

}
Copy the code

It turns out that the required Valves are already set up when the container component is initialized. Just call it when the request comes in.

This article mentions the application of the responsibility chain pattern in Tomcat. If you don't know how to invoke the responsibility chain pattern, you can refer to another blog of the author: Design Pattern -- Responsibility Chain Pattern

See below:

Customize a Valve

The plug-and-play mechanism of the responsibility chain gives us much more extensibility, for example, it’s easy to add an additional logic processing valve.

1. Customize PrintIPValve by inheriting ValveBase and overriding invoke method.

public class PrintIPValve extends ValveBase{

  @Override
  public void invoke(Request request, Response response) throws IOException, 	   ServletException {

    System.out.println("------ custom PrintIPValve:"+request.getRemoteAddr()); getNext().invoke(request,response); }}Copy the code
  1. Configure the Tomcat core configuration file server. XML, where the valve is configured in the Engine container, scope is the entire Engine, can also be configured in the Host or Context depending on scope
<Valve className="org.apache.catalina.valves.PrintIPValve" />
Copy the code