Tomcat startup process (how to accept the connection)

Let me take a look at the whole structure related to the connection part:

Through the previous introduction, the server.xml Through the Digester to create the structure of the entire container, we will review the previous content. Some contents in the createStartDigester method (here create a Connector and add it to the StandardService):

digester.addRule("Server/Service/Connector",
                 new ConnectorCreateRule());
digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
        new String[]{"executor", "sslImplementationName", "protocol"}));
digester.addSetNext("Server/Service/Connector",
                    "addConnector",
                    "org.apache.catalina.connector.Connector");

Corresponding server.xml File:

<Service name="Catalina">

       <Connector port="8080" protocol="HTTP/1.1"  connectionTimeout="20000" redirectPort="8443" />

Here is to use the ConnectorCreateRule class to resolve and create Connector objects and add them to the Service (StandardService). Let's see how the ConnectorCreateRule class creates connectors:

@Override
public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
    Service svc = (Service)digester.peek();
    Executor ex = null;
    if ( attributes.getValue("executor")!=null ) {
        ex = svc.getExecutor(attributes.getValue("executor"));
    }
    Connector con = new Connector(attributes.getValue("protocol"));
    if (ex != null) {
        setExecutor(con, ex);
    }
    String sslImplementationName = attributes.getValue("sslImplementationName");
    if (sslImplementationName != null) {
        setSSLImplementationName(con, sslImplementationName);
    }
    digester.push(con);
}

Through the new connector( attributes.getValue ("Protocol"): see how it is constructed:

public Connector(String protocol) {
    boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
            AprLifecycleListener.getUseAprConnector();
    if ("HTTP/1.1".equals(protocol) || protocol == null) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
        }
    } else if ("AJP/1.3".equals(protocol)) {
        if (aprConnector) {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
        } else {
            protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
        }
    } else {
        protocolHandlerClassName = protocol;
    }
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.getConstructor().newInstance();
    } ......
     finally {
        this.protocolHandler = p;
    }
}

The protocol here is HTTP/1.1, so the protocolHandler variable of Connector is Http11AprProtocol. And the nioendpoint related to the connection is the member variable of the Connector, while Poller and Accpetor are the internal classes of nioendpoint. So a socket can find the Connector, and then find its standard service through the Connector. For this socket connection, you can find standard server upward, Engine, Host and other containers downward to handle the socket (the contents of these containers and the contents described in the previous chapter mapperListener.start(), which is set in Mapper of standard service. Now let's see the specific process.

1. First, the acceptor accepts it and then processes it through the Poller, and then converts it to TaskThread. It will not be described in detail here. As mentioned in the previous chapter, here we start from getAdapter().service(request, response), about the initialization of these two input parameters This article It is mentioned that:

@Override
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().setQueryStringCharset(connector.getURICharset());
    }
          ........
        postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            request.setAsyncSupported(
                    connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(
                    request, response);
         ..............

    } 
             ...........
    }
}

Here two kinds of Request and Response are also mentioned in the article and are not described. First, you can't get it. Create it through connector:

public Request(Connector connector) {
    this.connector = connector;
      .........
}

Set the connector to this request, and then request.setCoyoteRequest(req) (the method that calls the request, in fact, the first time the request is to call the req method again, and then put the result into the member variable of the request.).

After that is the postParseRequest(req, request, res, response) method:

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

         ............

     parsePathParameters(req, request);

         .............

     connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());

      ..............

  MessageBytes redirectPathMB = request.getMappingData().redirectPath;
     if (!redirectPathMB.isNull()) {
         String redirectPath = URLEncoder.DEFAULT.encode(
                 redirectPathMB.toString(), StandardCharsets.UTF_8);
           ........
          response.sendRedirect(redirectPath);
        .....
 }

   doConnectorAuthenticationAuthorization(req,request);

}

  Parse the path parameter (the parameter with; from the request URL, which is not commonly seen) through the parsePathParameters(req, request) method and set it to the request.

After that connector.getService ().getMapper().map(serverName, decodedURI, version, request.getMappingData () method (this method is critical):

Let's look at the entire invocation chain of the method. Get the StandardService through the connector, and then get Mapper (that is, from the previous mapperListener.start() set to StandardService), call its map method. The input parameter is the member variable MappingData with request, and its variables are:

public class MappingData {

    public Host host = null;
    public Context context = null;
    public int contextSlashCount = 0;
    public Context[] contexts = null;
    public Wrapper wrapper = null;
    public boolean jspWildCard = false;

    public final MessageBytes contextPath = MessageBytes.newInstance();
    public final MessageBytes requestPath = MessageBytes.newInstance();
    public final MessageBytes wrapperPath = MessageBytes.newInstance();
    public final MessageBytes pathInfo = MessageBytes.newInstance();

    public final MessageBytes redirectPath = MessageBytes.newInstance();

    // Fields used by ApplicationMapping to implement javax.servlet.http.Mapping
    public MappingMatch matchType = null;

}

Look at Mapper's map method (parameter content):

    

The map method calls the internalMap method:
private final void internalMap(CharChunk host, CharChunk uri,
                               String version, MappingData mappingData) throws IOException {
    // Virtual host mapping
    Mapper.MappedHost[] hosts = this.hosts;
    Mapper.MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
    ..........
    mappingData.host = mappedHost.object;
     ..........
    // Context mapping
    Mapper.ContextList contextList = mappedHost.contextList;
    Mapper.MappedContext[] contexts = contextList.contexts;
    int pos = find(contexts, uri);
    ...........
    Mapper.MappedContext context = null;
    while (pos >= 0) {
        context = contexts[pos];
        ............
        pos = find(contexts, uri);
    }
    .........
            context = contexts[0];
    ..........

    mappingData.contextPath.setString(context.name);
    Mapper.ContextVersion contextVersion = null;
    Mapper.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) {
            contextVersion = exactFind(contextVersions, version);
        }
    }
    if (contextVersion == null) {
        contextVersion = contextVersions[versionCount - 1];
    }
    mappingData.context = contextVersion.object;
    mappingData.contextSlashCount = contextVersion.slashCount;
    // Wrapper mapping
    if (!contextVersion.isPaused()) {
        internalMapWrapper(contextVersion, uri, mappingData);
    }
}

1. First, find the Mapper.MappedHost , and set it to mappingData, and then through charchunk The URI finds the corresponding MappedContext, and then finds contextVersions. Here, there is a webappVersion attribute for the concept related to contextVersions, which corresponds to the attribute in contextVersion. The default value is "", and the purpose of this design is not understood yet:

    

Finally, find the context through this contextversion: mappingData.context = contextVersion.object .

After that, we call the internalMapWrapper method (by which we find Wrapper (the corresponding servlet processing class)):

private final void internalMapWrapper(ContextVersion contextVersion,
                                      CharChunk path,
                                      MappingData mappingData) throws IOException {
         .....
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    internalMapExactWrapper(exactWrappers, path, mappingData);
       ........

  }

Now the input parameters are:Note that the path is "/ ts", indicating that the Wrapper is to be found here. Meanwhile, mappingData has two properties, contextpath (path of context) and requestPath (path of Wrapper):

internalMapExactWrapper method:

private final void internalMapExactWrapper
    (MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
    MappedWrapper wrapper = exactFind(wrappers, path);
    if (wrapper != null) {
        mappingData.requestPath.setString(wrapper.name);
        mappingData.wrapper = wrapper.object;
        if (path.equals("/")) {
            // Special handling for Context Root mapped servlet
            mappingData.pathInfo.setString("/");
            mappingData.wrapperPath.setString("");
            // This seems wrong but it is what the spec says...
            mappingData.contextPath.setString("");
            mappingData.matchType = MappingMatch.CONTEXT_ROOT;
        } else {
            mappingData.wrapperPath.setString(wrapper.name);
            mappingData.matchType = MappingMatch.EXACT;
        }
    }
}

Find the corresponding wrapper through path and set it to mappingData:

   

At this point, the corresponding host - "context -" wrapper (Servlet) is found through the requested url and the original Mapper. Now back to the original

Method postParseRequest, followed by if there is a redirect, set the redirect to response:
MessageBytes redirectPathMB = request.getMappingData().redirectPath;
if (!redirectPathMB.isNull()) {
    String redirectPath = URLEncoder.DEFAULT.encode(
            redirectPathMB.toString(), StandardCharsets.UTF_8);
    ........
    response.sendRedirect(redirectPath);
   .....
}

Then there is the doconnectoreauthenticationauthorization method. This is the authorization method, regardless of this one.

Therefore, through a series of calls, the contents of Mapper in standardService about container are finally set to mappingData in Request. Here is the use of mappingData:

    

      

Tags: Apache socket xml Attribute

Posted on Sat, 06 Jun 2020 01:37:27 -0700 by Jacquelyn L. Ja