Tomcat启动过程(番外1--怎样接受连接)
发布日期:2021-05-07 20:41:22 浏览次数:23 分类:原创文章

本文共 18466 字,大约阅读时间需要 61 分钟。

   我先整个看下与连接部分相关的结构:

              通过前面的介绍,是由server.xml通过Digester去创建整个容器的结构的,我们再回顾整理下前面的内容。createStartDigester方法里面的一些内容(这里创建Connector并将其addConnector(加到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");

 对应server.xml文件:

<Service name="Catalina">

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

这里是通过ConnectorCreateRule类去解析创建Connector对象,并将其添加到Service(StandardService)的,我们看下ConnectorCreateRule类是怎样去创建Connector的:

@Overridepublic 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);}

通过new Connector(attributes.getValue("protocol")):看下其构造方法:

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;    }}

这里的协议是HTTP/1.1,所以Connector的protocolHandler变量是Http11AprProtocol。并且与连接相关的NioEndpoint是Connector的成员变量、而Poller、Accpetor又是NioEndPoint的内部类。所以一个socket能找到连接器Connector,然后通过Connector找到其StandardService。由此一个socket连接想向上能找到StandardServer、向下也能找到Engine、Host等这些容器来处理这个socket(这些容器的内容以及通过上一章讲过的mapperListener.start(),设置到了StandardService的Mapper中了),现在我们来看下具体的过程。

         1、首先是Accptor接受然后通过Poller去处理,转换为TaskThread,这里就不具体再描叙了是上一章就说过了,这里我们直接从 getAdapter().service(request, response)开始,关于这两个入参的初始化有提到:

@Overridepublic 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);         ..............    }              ...........    }}

这里两种Request、Response在文章中也有提到就不在描叙了。首先取不到,通过connector创建:

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

将connector设置到这个Request中,之后request.setCoyoteRequest(req)(调用request的方法,其实request首次是再调用req的方法,然后将结果再放到request的成员变量中)。

 之后是postParseRequest(req, request, res, response)方法:

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);

}

  通过parsePathParameters(req, request)方法解析路径参数(从req URL中带;的参数,一般没有看到这种用法)并设置到request中。

   之后是connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData())方法(这个方法很关键):

    我们看下方法其的整个调用链。通过connector获取StandardService,再获取Mapper(就是前面由mapperListener.start()设置到StandardService中的),调用其map方法,入参是有request的成员变量MappingData,其变量有:

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;

}

看下Mapper的map方法(参数内容):

    

map方法调用internalMap方法:
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、首先通过CharChunk host,找到Mapper.MappedHost,并设置到mappingData中,再之后通过CharChunk uri找到对应的MappedContext,然后找到contextVersions,这里与contextVersions相关的概念有个webappVersion属性,对应在contextVersion中就是那么属性,默认是"",现在还不明白这个设计的目的:

    

         最后通过这个contextversion找到context:mappingData.context = contextVersion.object。

         之后调用internalMapWrapper方法(通过这个方法找到Wrapper(即对应的servlet处理类)):

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

  }

现在的入参是:这里注意path已经是"/ts"了说明在这里是要找Wrapper了,同时mappingData有两个属性contextPath(Context的路径)、requestPath(Wrapper的路径):

  internalMapExactWrapper方法:

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;        }    }}

通过path找到对应的wrapper,并设置到mappingData中:

   

             至此就通过请求的url以及原来的Mapper找到了对应的host-》context-》wrapper(Servlet)  。现在再回到原来的

方法postParseRequest,之后是如果有重定向,则将重定向设置到response中:
MessageBytes redirectPathMB = request.getMappingData().redirectPath;if (!redirectPathMB.isNull()) {    String redirectPath = URLEncoder.DEFAULT.encode(            redirectPathMB.toString(), StandardCharsets.UTF_8);    ........    response.sendRedirect(redirectPath);   .....}

   然后是doConnectorAuthenticationAuthorization方法,这个是授权方法,先不管这个。

    所以这里通过一连串的调用,最终是将standardService中的Mapper的关于容器的内容设置到了Request中的mappingData中。下面就是这个mappingData的使用了。

     现在我们再将目光移到最开始的service方法,接下来是调用connector.getService().getContainer().getPipeline().getFirst().invoke( request, response)方法:

     这里就是pipeline在四个容器的调用了:我们先通过最初的StandardEngineValve来看下mappingData的使用:

       

 

@Overridepublic final void invoke(Request request, Response response)    throws IOException, ServletException {    // Select the Host to be used for 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);}

      

看下request.getHost()方法(就是通过mappingData一步步获取对应的host、context、wrapper):

public Host getHost() {    return mappingData.host;}

这里最终会调到StandardWrapperValve,看其invoke方法:

@Overridepublic final void invoke(Request request, Response response)        throws IOException, ServletException {    ...........    StandardWrapper wrapper = (StandardWrapper) getContainer();    Servlet servlet = null;    Context context = (Context) wrapper.getParent();     ...........            servlet = wrapper.allocate();    ...........    ApplicationFilterChain filterChain =            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);        if ((servlet != null) && (filterChain != null)) {            // Swallow output if needed            if (context.getSwallowOutput()) {                   ......                        filterChain.doFilter(request.getRequest(),                                response.getResponse());                }                 ........            } else {                    filterChain.doFilter                            (request.getRequest(), response.getResponse());                }            }        }   ........}

     1、获取到StandardWrapper。

     2、通过wrapper去初始化获取servlet,wrapper.allocate()。

     3、创建ApplicationFilterChain执行链,ApplicationFilterFactory.createFilterChain(request, wrapper, servlet):

         

public static ApplicationFilterChain createFilterChain(ServletRequest request,                                                       Wrapper wrapper, Servlet servlet) {    ..........    // Create and initialize a filter chain object    ApplicationFilterChain filterChain = null;    if (request instanceof Request) {            ........            filterChain = (ApplicationFilterChain) req.getFilterChain();            if (filterChain == null) {                filterChain = new ApplicationFilterChain();                req.setFilterChain(filterChain);        }    }    ..........    filterChain.setServlet(servlet);    // Acquire the filter mappings for this Context    StandardContext context = (StandardContext) wrapper.getParent();    FilterMap filterMaps[] = context.findFilterMaps();    ..........    // Acquire the information we will need to match filter mappings    DispatcherType dispatcher =            (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);    String requestPath = null;    Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);    if (attribute != null){        requestPath = attribute.toString();    }    String servletName = wrapper.getName();    // Add the relevant path-mapped filters to this filter chain    for (int i = 0; i < filterMaps.length; i++) {        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {            continue;        }        if (!matchFiltersURL(filterMaps[i], requestPath))            continue;        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                context.findFilterConfig(filterMaps[i].getFilterName());        filterChain.addFilter(filterConfig);    }    // Add filters that match on servlet name second    for (int i = 0; i < filterMaps.length; i++) {        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {            continue;        }        if (!matchFiltersServlet(filterMaps[i], servletName))            continue;        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)                context.findFilterConfig(filterMaps[i].getFilterName());        filterChain.addFilter(filterConfig);    }    return filterChain;}

   这里就是这个ApplicationFilterChain的创建,设置其的最终运行目标Servlet,然后获取context的过滤器Filter(一个context对应的就是一个web.xml文件,这个过滤器就是配置在其中的),将这些Filter添加到ApplicationFilterChain到。

然后是filterChain.doFilter(request.getRequest(), response.getResponse())方法的调用,doFilter方法调用的是internalDoFilter方法:

private void internalDoFilter(ServletRequest request,                              ServletResponse response)    throws IOException, ServletException {    // Call the next filter if there is one    if (pos < n) {        ApplicationFilterConfig filterConfig = filters[pos++];            Filter filter = filterConfig.getFilter();             ............                filter.doFilter(request, response, this);            .............           reuturn;    }      ...........            servlet.service(request, response);      ...........}

这个结构可以清楚的看到,先调用所有的filter,如果其中有的Filter类最后没有再调用filterChain.doFilter (request, response)形成循环调用,将所有的Filter都运行完,则最后会return就不会运行Servlet了。当所有的Filter运行完后,就调用Servlet的service方法了。

    至此,这个请求的处理就完成了,下面补充关于Servlet接口的内容。

                            

    

     自己写的Servlet一般会继承HttpServlet类,再重写doGet/doPost方法(自己也可以直接继承Servlet,然后去自定义一些细节):

public class MyServlet extends HttpServlet {    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        response.setContentType("text/html");        PrintWriter writer = response.getWriter();        writer.println("<html>\n" +                "  <head>\n" +                "    <title>Title</title>\n" +                "  </head>\n" +                "  <body>\n" +                "  Hello Servlet\n" +                "  </body>\n" +                "</html>");        writer.flush();    }    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {        doPost(request,response);    }}

                      

   

          前面的servlet.service方法我们看下在HttpServlet中是怎样处理的:

protected void service(HttpServletRequest req, HttpServletResponse resp)    throws ServletException, IOException {    String method = req.getMethod();    if (method.equals(METHOD_GET)) {        long lastModified = getLastModified(req);        if (lastModified == -1) {            // servlet doesn't support if-modified-since, no reason            // to go through further expensive logic            doGet(req, resp);        } else {            long ifModifiedSince;            try {                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);            } catch (IllegalArgumentException iae) {                // Invalid date header - proceed as if none was set                ifModifiedSince = -1;            }            if (ifModifiedSince < (lastModified / 1000 * 1000)) {                // If the servlet mod time is later, call doGet()                // Round down to the nearest second for a proper compare                // A ifModifiedSince of -1 will always be less                maybeSetLastModified(resp, lastModified);                doGet(req, resp);            } else {                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);            }        }    } else if (method.equals(METHOD_HEAD)) {        long lastModified = getLastModified(req);        maybeSetLastModified(resp, lastModified);        doHead(req, resp);    } else if (method.equals(METHOD_POST)) {        doPost(req, resp);    } else if (method.equals(METHOD_PUT)) {        doPut(req, resp);    } else if (method.equals(METHOD_DELETE)) {        doDelete(req, resp);    } else if (method.equals(METHOD_OPTIONS)) {        doOptions(req,resp);    } else if (method.equals(METHOD_TRACE)) {        doTrace(req,resp);    } else {        //        // Note that this means NO servlet supports whatever        // method was requested, anywhere on this server.        //        String errMsg = lStrings.getString("http.method_not_implemented");        Object[] errArgs = new Object[1];        errArgs[0] = method;        errMsg = MessageFormat.format(errMsg, errArgs);        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);    }}
上一篇:spring源码阅读准备--- 通过Class、Method、Field的JDK源码了解其结构及泛型相关内容
下一篇:Tomcat启动过程连接部分-(下)

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2025年04月06日 07时46分42秒