Note: This article source code analysis based on Tomcat 9.0.43, source gitee warehouse warehouse address: gitee.com/funcy/tomca… .

This is the sixth article in the tomcat source code analysis. In the previous article, we mentioned that in Poller threads, Tomcat will wrap the connection request as SocketProcessorBase and throw it into the thread pool to run. How does this work, and how does it end up in the servlet? This article will tell you all about it.

It is important to note that there are many links, from dropping requests to the thread pool to executing the servlet. The call links obtained through debugging are as follows:

This includes the parsing of the HTTP protocol and the implementation of the Servlet specification, which we will skip briefly and analyze only the key steps.

1. The parsinghttpProtocol:Http11Processor#service

Tomcat uses Http11Processor#service to parse the HTTP protocol.

public SocketState service(SocketWrapperBase
        socketWrapper).while (! getErrorState().isError(a) && keepAlive && !isAsync(a) && upgradeToken = =null&& sendfileState == SendfileState.DONE && ! protocol.isPaused()) {try {

            / / inputBuffer parseRequestLine () : line analytical processing requests
            if(! inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(), protocol.getKeepAliveTimeout())) { ... }// prepareRequestProtocol() : Handles the HTTP protocol version
            prepareRequestProtocol();
            if (protocol.isPaused()) {
                ...
            } else {
                keptAlive = true; .// inputBuffer.parseheaders () : parses the request headers
                if (!http09 && !inputBuffer.parseHeaders()) {
                    ...
                }

            }
        } catch(...). {... }...if (getErrorState().isIoAllowed()) {
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                // Prepare the request
                prepareRequest();
            } catch(Throwable t) { ... }}...if (getErrorState().isIoAllowed()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                // Handle read eventsgetAdapter().service(request, response); . }catch(...). {... }}... }... }Copy the code

This method is quite long, but I’ve streamlined it down to the key methods, which are listed below:

  • parsinghttpRequest:inputBuffer.parseRequestLine()
  • To deal withhttpProtocol Version:prepareRequestProtocol()
  • parsinghttpRequest header:inputBuffer.parseHeaders()
  • To preparehttpRequest data:prepareRequest()
  • Continue processing requests:CoyoteAdapter#service(request, response)

The above four methods make me sad, and without a deep understanding of every detail of HTTP, I would not recommend studying them.

After parsing the HTTP request, continue to call getAdapter().service(request, response); The logic after processing, which is the CoyoteAdapter#service(request, response) method.

2. GeneratehttpServletRequest/httpServletResponse:CoyoteAdapter#service(request, response)

Is it/httpServletResponse in CoyoteAdapter# service (request, response) generated in the HTML code is as follows:

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

    // Get httpServletRequest with the value null
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    // Get httpServletResponse with the value null
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
        // Create a request object
        request = connector.createRequest();
        request.setCoyoteRequest(req);

        // Create a Response objectresponse = connector.createResponse(); response.setCoyoteResponse(res); . }...try {
        // Process parameters, which refer to some parameters of the servlet specification, if the request method, sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Invoke the valve process of the containerconnector.getService().getContainer().getPipeline().getFirst().invoke( request, response); }... }catch (IOException e) {
        // Ignore
    } finally{... }}Copy the code

In Tomcat, there are two types of Request and Response:

  • org.apache.coyote.Requestorg.apache.coyote.ResponseTomcat is provided for storagehttpConnected to data
  • org.apache.catalina.connector.Requestwithorg.apache.catalina.connector.ResponseTomcat is also provided by Tomcat, but it is implemented separatelyHttpServletRequest/HttpServletResponse

Here we take a look at org. Apache. Catalina. The creation process of the Request:

request = connector.createRequest();
Copy the code

Connect #createRequest method:

public Request createRequest(a) {
    return new Request(this);
}
Copy the code

The constructor is called, so follow along:

/** * implements HttpServletRequest */
public class Request implements HttpServletRequest {

    /** connector */
    protected final Connector connector;

    / * * this is ` org. Apache. Coyote. Request `, used to store data of the HTTP Request connection * /
    protectedorg.apache.coyote.Request coyoteRequest; .public Request(Connector connector) {
        this.connector = connector;

        formats = new SimpleDateFormat[formatsTemplate.length];
        for(int i = 0; i < formats.length; i++) { formats[i] = (SimpleDateFormat) formatsTemplate[i].clone(); }}/** * Part of the httpServletRequest method is as follows: * They end up calling coyoteRequest */

    @Override
    public String getMethod(a) {
        return coyoteRequest.method().toString();
    }

    @Override
    public String getRequestURI(a) {
        returncoyoteRequest.requestURI().toString(); }... }Copy the code

At this point, the Request has just been created without doing any real work, so let’s move on.

3. reqeustpostParseRequest(...)

Let’s go back to the CoyoteAdapter#service() method and after creating Request/Response, it’s time to load this:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {...try {
        // Process parameters, which refer to some parameters of the servlet specification, if the request method, sessionIdpostParseSuccess = postParseRequest(req, request, res, response); . }... }Copy the code

CoyoteAdapter#postParseRequest handles some of the servlet specification’s parameters, which is essentially an assignment to an httpServletRequest Request.

protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws IOException, ServletException {

    / / resolution scheme
    if (req.scheme().isNull()) {
        // Resolve scheme to set httServletRequest request
        req.scheme().setString(connector.getScheme());
        request.setSecure(connector.getSecure());
    } else {
        request.setSecure(req.scheme().equals("https"));
    }

    // Handle the agent
    String proxyName = connector.getProxyName();
    int proxyPort = connector.getProxyPort();
    if(proxyPort ! =0) {
        req.setServerPort(proxyPort);
    } else if (req.getServerPort() == -1) {
        if (req.scheme().equals("https")) {
            req.setServerPort(443);
        } else {
            req.setServerPort(80); }}if(proxyName ! =null) {
        req.serverName().setString(proxyName);
    }

    MessageBytes undecodedURI = req.requestURI();
    // Process the request method
    if (undecodedURI.equals("*")) {
        if (req.method().equalsIgnoreCase("OPTIONS")) {
            StringBuilder allow = new StringBuilder();
            allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
            // Trace if allowed
            if (connector.getAllowTrace()) {
                allow.append(", TRACE");
            }
            res.setHeader("Allow", allow.toString());
            connector.getService().getContainer().logAccess(request, response, 0.true);
            return false;
        } else {
            response.sendError(400."Invalid URI"); }}// Some other parsing operations will be ignored.while (mapRequired) {
        // Parse servlet contents such as Host, Context, Wrapper, etcconnector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData()); . }...return true;
}
Copy the code

This method is still quite long and does nothing to parse the HTTP request data and convert it to the parameters required for HttpServletRequest.

This method calls the following code:

// Parse servlet contents such as Host, Context, Wrapper, etc
connector.getService().getMapper().map(serverName, decodedURI,
        version, request.getMappingData());
Copy the code

This code ends up calling Mapper#map(), which looks like this:

public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {
    // Get the host and uri
    if (host.isNull()) {
        String defaultHostName = this.defaultHostName;
        if (defaultHostName == null) {
            return;
        }
        host.getCharChunk().append(defaultHostName);
    }
    host.toChars();
    uri.toChars();
    // Handle the mapping
    internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
Copy the code

This method, once it gets the host and uri, continues to call the Mapper#internalMap method, which handles the HTTP request path, which is to find the servlet that ultimately executes according to the request path. Let’s move on.

4. Handle mapping paths:Mapper#internalMap

Follow up with the Mapper#internalMap method:

private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws IOException {
    // This step is equivalent to getting all the hosts in the project
    MappedHost[] hosts = this.hosts;
    / / find the hostMappedHost mappedHost = exactFindIgnoreCase(hosts, host); . mappingData.host = mappedHost.object;if (uri.isNull()) {
        return;
    }

    uri.setLimit(-1);

    // Now that we have found the host, we can find all the contexts under that host
    ContextList contextList = mappedHost.contextList;
    MappedContext[] contexts = contextList.contexts;
    / / find the context
    int pos = find(contexts, uri);
    if (pos == -1) {
        return; }... mappingData.contextPath.setString(context.name); ContextVersion contextVersion =null;

    // Now that the context is found, the next step is to look for all contextVersions of the context
    ContextVersion[] contextVersions = context.versions;
    final int versionCount = contextVersions.length;
    if (versionCount > 1) {
        Context[] contextObjects = new Context[contextVersions.length];
        for (int i = 0; i < contextObjects.length; i++) {
            contextObjects[i] = contextVersions[i].object;
        }
        mappingData.contexts = contextObjects;
        if(version ! =null) {
            // Find contextVersions based on the version numbercontextVersion = exactFind(contextVersions, version); }}if (contextVersion == null) {
        contextVersion = contextVersions[versionCount - 1];
    }
    mappingData.context = contextVersion.object;
    mappingData.contextSlashCount = contextVersion.slashCount;

    if(! contextVersion.isPaused()) {/ / processing wrapperinternalMapWrapper(contextVersion, uri, mappingData); }}Copy the code

And notice that instead of StandardHost and StandardContext, MappedHost and MappedContext, MappedHost, MappedContext, MappedHost, MappedContext, MappedContext holds each ContextVersion:

For example, MappedHost content is as follows:

protected static final class MappedHost extends MapElement<Host> {

    /** Store the structure of each MappedContext, continue */
    public volatileContextList contextList; . }protected static final class ContextList {
    /** Use array to store MappedContext */
    public finalMappedContext[] contexts; . }Copy the code

MappedContext contains the following contents:

protected static final class MappedContext extends MapElement<Void> {
    /** store multiple ContextVersions, use arrays to store */
    public volatileContextVersion[] versions; . }Copy the code

4.1 find the host

So how do they find hosts? When tomcat creates a host, it defaults to a hostname:

public class Tomcat {

    protected String hostname = "localhost";

    public Host getHost(a) {
        // Get engine, created when it doesn't exist
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }
        / / create the Host
        Host host = new StandardHost();
        host.setName(hostname);
        // Add to engine
        getEngine().addChild(host);
        returnhost; }... }Copy the code

Tomcat will parse the host passed in during the HTTP request. When the two match, the corresponding host is found.

private static final <T, E extends MapElement<T>> E exactFindIgnoreCase( E[] map, CharChunk name) {
    // Find the element equal to or closest to name in the map array, return the subscript
    int pos = findIgnoreCase(map, name);

    if (pos >= 0) {
        // Again, because findIgnoreCase(...) Returns the closest or equal subscript to the given
        E result = map[pos];
        if (name.equalsIgnoreCase(result.name)) {
            returnresult; }}return null;
}
Copy the code

4.2 findContext

How do we find the Context? Here’s what we do when we add Context:

/ / create the context
String docBase = System.getProperty("java.io.tmpdir");
Context context = tomcat.addContext("", docBase);
Copy the code

In tomcat. AddContext (…). Method, we can specify a request path to the Context, like this:

Context context = tomcat.addContext("/api", docBase);
Copy the code

This means that requests prefixed with API are processed using the Context. The Mapper#internalMap method looks for the matching Context based on the uri.

4.3 processingwrapperMatch:Mapper#internalMapWrapper

Once the Context is found, the Wrapper method is Mapper#internalMapWrapper:

private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws IOException {

    // Get all exactWrappers
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    // Perform a lookup
    internalMapExactWrapper(exactWrappers, path, mappingData);
    // A lot of content is omitted. }Copy the code

This method is also very long and handles various matching rules. Let’s look at just one of them here. Let’s look at the matching process and enter the Mapper#internalMapExactWrapper method:

private final void internalMapExactWrapper(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
    // The path matches, whether the requested URI matches the servlet path
    MappedWrapper wrapper = exactFind(wrappers, path);
    if(wrapper ! =null) {
        mappingData.requestPath.setString(wrapper.name);
        // Find it and assign it
        mappingData.wrapper = wrapper.object;
        if (path.equals("/")) {
            mappingData.pathInfo.setString("/");
            mappingData.wrapperPath.setString("");
            mappingData.contextPath.setString("");
            mappingData.matchType = MappingMatch.CONTEXT_ROOT;
        } else{ mappingData.wrapperPath.setString(wrapper.name); mappingData.matchType = MappingMatch.EXACT; }}}Copy the code

Here the path is the path of the URI. This step is based on the path to determine whether there is a match, that is, whether the uri passed in is Chelsea to the Servlet path. After the match is successful, the Wrapper property of mappingData is assigned.

At this point, the host, context, and wrapepr corresponding to the request are found.

5. CallXxxValve

Let’s return to the CoyoteAdapter#service method:

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
        throws Exception {...try {
        // Process parameters, which refer to some parameters of the servlet specification, if the request method, sessionId
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Invoke the valve process of the containerconnector.getService().getContainer().getPipeline().getFirst().invoke( request, response); }... }catch (IOException e) {
        // Ignore
    } finally{... }}Copy the code

After parsing the HTTP parameters and requesting the corresponding servlet, the container Pipeline is called:

// Invoke the valve process of the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
Copy the code

The entire pipeline structure is as follows:

One Pipeline call after another is executed. What is stored in Pipeline? There are Valve, StandardEngine, StandardHost, StandardContext, StandardWrapper, etc.

5.1 StandardEngineValve#invoke

We follow up with connector.getService().getContainer().getPipeline().getFirst().invoke(request, response), Since connection.getservice ().getcontainer () returns StandardEngine, the StandardEngineValve#invoke method is run:

public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    // use the corresponding host to handleHost host = request.getHost(); .// Call host valve processing
    host.getPipeline().getFirst().invoke(request, response);
}
Copy the code

The method has two key codes:

  • request.getHost(): gets the host corresponding to the request, which is the previous oneMapper#internalMapThe one that corresponds to the requesthost
  • host.getPipeline().getFirst().invoke(request, response): Continue callingStandardHostValve#invokemethods

5.2 StandardHostValve#invoke

Let’s enter the StandardHostValve#invoke method again:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {
    // Get the context to use from requestContext context = request.getContext(); .try{...try {
            if(! response.isErrorReportRequired()) {// Call valve of the contextcontext.getPipeline().getFirst().invoke(request, response); }}catch(Throwable t) { ... }}finally{... }Copy the code

This method is called the same way as StandardEngineValve#invoke:

  • request.getContext()From:requestTo get the corresponding of the requestcontextThis one is also inMapper#internalMapHow to find
  • context.getPipeline().getFirst().invoke(request, response)Continue to callStandardContextValve#invokeTo deal with

5.3 StandardContextValve#invoke

The StandardContextValve#invoke method looks like this:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {...// Retrieve the wrapper from requestWrapper wrapper = request.getWrapper(); .// Call the Valve handling of the wrapper
    wrapper.getPipeline().getFirst().invoke(request, response);
}
Copy the code

Well, again the same routine as the StandardEngineValve#invoke method, we continue with the StandardWrapperValve#invoke method.

6. StandardWrapperValve#invoke

Let’s look at the Pipeline call diagram:

From the point of view of the call, StandardWrapperValve has been called to the end, and the servlet is called from here. This method code is as follows:

public final void invoke(Request request, Response response)
        throws IOException, ServletException {...try {
        if(! unavailable) {// Get the servlet, return it if the instance exists, otherwise create the instance and call servlet #initservlet = wrapper.allocate(); }}catch(...). {... }...// Create a filter chain
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    Container container = this.container;
    try {
        if((servlet ! =null) && (filterChain ! =null)) {
            // Call filterchain-dofilter here
            if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        / / call the filterfilterChain.doFilter(request.getRequest(), response.getResponse()); }}finally {
                    String log = SystemLogHandler.stopCapture();
                    if(log ! =null && log.length() > 0) { context.getLogger().info(log); }}}else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else {
                    / / call the filterfilterChain.doFilter (request.getRequest(), response.getResponse()); }}}}catch (...) {
        ...
    } finally{... }}Copy the code

This method is still quite long, but I have removed all the non-critical points, leaving only three key actions:

  • servlet = wrapper.allocate(): getservletIf theservlettheloadOnStartLess than zero, that’s where it gets instantiated and calledServlet#initmethods
  • ApplicationFilterFactory.createFilterChain(...): Creates a filter chain
  • filterChain.doFilter(...): Invokes the filter operation

Let’s take a look at these methods.

6.1 StandardWrapper#allocate

Servlet #init (loadOnStartup < 0); servlet#init (loadOnStartup < 0);

public Servlet allocate(a) throws ServletException {...boolean newInstance = false;

    // If not SingleThreadedModel, return the same instance every time
    if(! singleThreadModel) {// The instance does not exist and is not initialized
        if (instance == null| |! instanceInitialized) {synchronized (this) {
                if (instance == null) {
                    try{...// call the StandardWrapper#loadServlet method
                        instance = loadServlet();
                        newInstance = true; . }catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        ExceptionUtils.handleThrowable(e);
                        throw new ServletException(sm.getString("standardWrapper.allocate"), e); }}if(! instanceInitialized) { initServlet(instance); }}}... }... }Copy the code

This determines whether the instance exists and has been initialized, and if so calls the StandardWrapper#loadServlet method, which was analyzed in the previous article. It does two main things:

  • instantiationservlet
  • callServlet#init(...)methods

6.2 ApplicationFilterFactory#createFilterChain

The ApplicationFilterFactory#createFilterChain method does this:

public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {

    if (servlet == null)
        return null;

    ApplicationFilterChain filterChain = null;
    // Create a filterChain object
    if (request instanceof Request) {
        Request req = (Request) request;
        if (Globals.IS_SECURITY_ENABLED) {
            filterChain = new ApplicationFilterChain();
        } else {
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = newApplicationFilterChain(); req.setFilterChain(filterChain); }}}else {
        filterChain = new ApplicationFilterChain();
    }

    filterChain.setServlet(servlet);
    filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

    // Get the warpper Context, and then get filterMaps from the ContextStandardContext context = (StandardContext) wrapper.getParent(); FilterMap filterMaps[] = context.findFilterMaps(); .// Add a filter that matches the servlet path to the filter chain
    for (FilterMap filterMap : filterMaps) {
        if(! matchDispatcher(filterMap, dispatcher)) {continue;
        }
        // Determine which filters satisfy the servlet path
        if(! matchFiltersURL(filterMap, requestPath))continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }

    // Add a filter that matches the servlet name to the filter chain
    for (FilterMap filterMap : filterMaps) {
        if(! matchDispatcher(filterMap, dispatcher)) {continue;
        }
        if(! matchFiltersServlet(filterMap, servletName))continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMap.getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
    }
    return filterChain;
}
Copy the code

The ApplicationFilterFactory#createFilterChain method is longer, but the logic is fairly clear. It is used to create a chain of filters.

  1. createApplicationFilterChainobject
  2. Gets the currentwrapperThe correspondingContextAnd then fromContextgetfilterMapsIn theContextThere is a structure for storagefilteraboutfilterLoad, the previous article also analyzed, here will not analyze
  3. Find what satisfies the criteriafilterAnd then add tofilterChainC, and notice that this satisfies the conditionfilterIt means to satisfy the presentservletthefilterIs passed when processing matching conditionsservletThe path andservletIf the name is matched, either of the two can be satisfied.

6.3 ApplicationFilterChain#doFilter

After obtaining the chain, the next step is to execute it using ApplicationFilterChain#doFilter:

public void doFilter(ServletRequest request, ServletResponse response)
    throws IOException, ServletException {

    if( Globals.IS_SECURITY_ENABLED ) {
        final ServletRequest req = request;
        final ServletResponse res = response;
        try {
            java.security.AccessController.doPrivileged(
                    (java.security.PrivilegedExceptionAction<Void>) () -> {
                        internalDoFilter(req,res);
                        return null; }); }catch( PrivilegedActionException pe) { ... }}else {
        // Execute the filterinternalDoFilter(request,response); }}Copy the code

Call the internalDoFilter (…). Carry out, continue to follow:

/** The index of the filter currently in use */
private int pos = 0;

/** The number of filters in the current filter chain */
private int n = 0;

/** Where the filter is stored */
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

/** * Execute filter */
private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // Check whether the execution is complete
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        try {
            // Get the filter to executeFilter filter = filterConfig.getFilter(); .// Execute doFilter of filter (...) methods
            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res, this};
                SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
            } else {
                filter.doFilter(request, response, this); }}catch (...) {
            ...
        }
        return;
    }

    try{...// Call the servlet#service method
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            Execute the servlet#service methodservlet.service(request, response); }}catch(...). {... }}Copy the code

internalDoFilter(…) Divided into two parts:

  1. performfilter.doFilter(...)methods
  2. performservlet.service(...)methods

There are two member variables in ApplicationFilterChain:

  • n: The number of filters in the current filter chain
  • post: Indicates the index of the filter currently in use

The size of these two parameters is used to determine whether to enter the filter call:

 // Check whether the execution is complete
if (pos < n) {
    ApplicationFilterConfig filterConfig = filters[pos++];
    try {
        // Get the filter to executeFilter filter = filterConfig.getFilter(); .// Execute doFilter of filter (...) methods
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();

            Object[] args = new Object[]{req, res, this};
            SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
        } else {
            filter.doFilter(request, response, this); }}catch (...) {
        ...
    }
    return;
}
Copy the code

This filter.dofilter (request, response, this) is the code that actually calls filter.

After executing the current filter, how does it proceed to the next filter? When we implement Filter, we always have this code:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    // Your business logic.// call the FilterChain#doFilter method
    chain.doFilter(request, response)

    // Your business logic. }Copy the code

In tomcat, FilterChain#doFilter calls ApplicationFilterChain#doFilter, and then ApplicationFilterChain#internalDoFilter. This call increments pos and continues with the Filter#doFilter method, which then returns to ApplicationFilterChain#doFilter… This iterative approach has a technical name in design patterns: the chain of responsibility pattern.

Each time the ApplicationFilterChain#doFilter method is called, the value of pos is incremented by 1 until pos >= n completes the filter and the servlet is executed:

private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
    
    // Check whether the execution is complete
    if (pos < n) {
        / / filter. }try{...// Call the servlet#service method
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) 
                && Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            Principal principal = ((HttpServletRequest) req).getUserPrincipal();
            Object[] args = new Object[]{req, res};
            Execute the servlet#service method
            SecurityUtil.doAsPrivilege("service", servlet, 
                    classTypeUsedInService, args, principal);
        } else {
            Execute the servlet#service methodservlet.service(request, response); }}catch(...). {... }}Copy the code

When it comes to servlet execution, it’s pretty simple: you’ve got an instance of the servlet, so you just call its service() method.

7. To summarize

This article mainly analyzes the whole process of tomcat thread pool processing request, Including the HTTP protocol parsing, it/httpServletResponse create and preparation, analytical mapping path (find corresponding host, context, wrapper), execute the filter with a servlet, However, for HTTP parsing, servlet specification implementation, this paper only points out the processing method, and did not go into the details of these methods.


Limited to the author’s personal level, there are inevitable mistakes in the article, welcome to correct! Original is not easy, commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.

In this paper, starting from WeChat number public road, Java technology link: mp.weixin.qq.com/s/o6ZKNqdcd… If you like this article, welcome to pay attention to the public account, let’s explore the world of technology together!