1. 程式人生 > 實用技巧 >How tomcat works(深入剖析tomcat)servlet容器

How tomcat works(深入剖析tomcat)servlet容器

How tomcat works (5)servlet容器閱讀筆記

第四章閱讀了tomcat預設聯結器的實現,當時connector中的使用的容器是自定義的容器,也是非常之簡單奧,一個人就幹完了所有的活,完成了初始化類載入器,載入servlet,呼叫servlet的service方法等的活兒,秉承了專事專幹的也就是模組化設計的理念,這樣的設計肯定是不夠靈活的,這一章就來看看tomcat中的容器是如何設計的

總體介紹

總的來說呢,tomcat將容器分為了四類:

  • Engine:表示整個Catalina servlet引擎
  • Host: 表示包含有一個或者多個Context容器的虛擬主機,一臺tomcat還能用來部署多個web應用?非常的阿妹增啊!!
  • Context:表示一個web應用程式,一個Context可以包含多個Wrapper
  • Wrapper:(包裝紙)表示一個獨立的servlet
  • 它們的具體實現在org.apache.catalina.core包下面,對應到具體的類就是standardxxx

容器的分層有點像流水線,一個web應用可能有很多的servlet,那麼context就負責隨便乾點活(調一下valve),然後把活交給打工人(basicvalve 把活交給wrapper),而wrapper也負責隨便乾點活(調一下valve),然後把活交給真正的勞動人民(basicvalve 呼叫servlet的service方法),這裡提到的valve、basicvalve都是

所以container介面及其上述容器的UML如下:

高階容器可以包含0-多個低階子容器,但是wrapper就沒法再向下了,有如下的介面用於子容器的管理

public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
public Container[] findChildren();
//如果wrapper呼叫這些介面就會丟擲異常,以下為standardWrapper的addChild實現
public void addChild(Container child) {

        throw new IllegalStateException
            (sm.getString("standardWrapper.notChild"));

    }

Pipeline和valve

這兩者可以說是目前所接觸到的tomcat容器和普通自定義容器的最大區別了,其實說簡單點也就是加了點過濾器,來看看tomcat中這個功能如何實現吧

管道:包含了該servlet容器將要執行的任務,一個容器對應了一條管道

//standardPipeline
protected Container container = null;
//constructor
public StandardPipeline(Container container) {

        super();
        setContainer(container);

    }
public void setContainer(Container container) {

        this.container = container;

}

:也就是該servlet容器具體執行的任務,一條管道上面有很多很多閥門,但是有一個basicvalve,也就是基礎閥,它永遠是最後執行的,pipeline和valve兩者的關係就如下圖所示

一種簡單的方式就是,用一個for迴圈遍歷valves陣列,對於每個valve都執行invoke方法,但是tomcat採取了另一種執行方式,引入了一個新的介面org.apache.catalina.valveContext來實現閥的遍歷執行,具體的過程程式碼如下

//pipeline的invoke方法
public void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Invoke the first Valve in this pipeline for this request
        (new StandardPipelineValveContext()).invokeNext(request, response);

}

//StandardPipelineValveContext的invokeNext
//StandardPipelineValveContext是StandardPipeline的一個內部類
//也就是通過內部類的方式一個一個訪問valve然後呼叫
public void invokeNext(Request request, Response response)
            throws IOException, ServletException {
			//成員變數stage初始值為0,protected int stage = 0;
            int subscript = stage;
            stage = stage + 1;

            // Invoke the requested Valve for the current request thread
    		//valves陣列
    		//protected Valve valves[] = new Valve[0];
            if (subscript < valves.length) {
                valves[subscript].invoke(request, response, this);
            } else if ((subscript == valves.length) && (basic != null)) {
                //最後呼叫basic閥門
                basic.invoke(request, response, this);
            } else {
                throw new ServletException
                    (sm.getString("standardPipeline.noValve"));
            }
}

//這一章提供了兩個簡單的閥,就是列印一些資訊
//clientIPLoggervalve
//這裡還需要傳遞valveContext實現進來,在invoke中回撥invokeNext方法
public void invoke(Request request, Response response, ValveContext valveContext)
    throws IOException, ServletException {

    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);
    //所以反而這個是等所有的呼叫結束後才會執行
    System.out.println("Client IP Logger Valve");
    ServletRequest sreq = request.getRequest();
    System.out.println(sreq.getRemoteAddr());
    System.out.println("------------------------------------");
}
//HeaderLoggervalve
public void invoke(Request request, Response response, ValveContext valveContext)
    throws IOException, ServletException {

    // Pass this request on to the next valve in our pipeline
    valveContext.invokeNext(request, response);

    System.out.println("Header Logger Valve");
    ServletRequest sreq = request.getRequest();
    if (sreq instanceof HttpServletRequest) {
      HttpServletRequest hreq = (HttpServletRequest) sreq;
      Enumeration headerNames = hreq.getHeaderNames();
      while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement().toString();
        String headerValue = hreq.getHeader(headerName);
        System.out.println(headerName + ":" + headerValue);
      }

    }
    else
      System.out.println("Not an HTTP Request");

    System.out.println("------------------------------------");
}

Wrapper最低階的容器

wrapper介面中比較重要的方法

  • allocate方法:分配一個已經初始化的例項
  • load方法:載入並初始化例項

以Wrapper作為容器的應用程式的UML如下

主要類以及流程分析

SimpleLoader的構造方法

構造方法中初始化了classLoader,還是熟悉的味道,但是這裡只初始化了ClassLoader

public SimpleLoader() {
    try {
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      //public static final String WEB_ROOT ="D:\\tomcat\\HowTomcatWorks\\webroot";
      File classPath = new File(WEB_ROOT);
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      urls[0] = new URL(null, repository, streamHandler);
      classLoader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
}
SimpleWrapper

void load()

public void load() throws ServletException {
    instance = loadServlet();
  }

Servlet loadServlet()

將servlet載入並例項化賦值給成員變數servlet

private Servlet loadServlet() throws ServletException {
    if (instance!=null)
      return instance;

    Servlet servlet = null;
    String actualClass = servletClass;
    if (actualClass == null) {
      throw new ServletException("servlet class has not been specified");
    }
	//過去上面的simpleLoader
    Loader loader = getLoader();
    // Acquire an instance of the class loader to be used
    if (loader==null) {
      throw new ServletException("No loader.");
    }
    ClassLoader classLoader = loader.getClassLoader();
	//刪除了try catch
    // Load the specified servlet class from the appropriate class loader
    Class classClass = null;
    if (classLoader!=null) {
        classClass = classLoader.loadClass(actualClass);
      }
    servlet = (Servlet) classClass.newInstance();
    servlet.init(null);
    return servlet;
  }

至此位置,已經理清楚了classloader的載入過程,那麼具體一個請求的呼叫過程呢?也就是basicvalve是如何來的

basicvalve的呼叫過程分析

在pipeline和valve中分析過了,最後一個呼叫basicvalve,那麼這個basicvalve是誰呢?

StandardWrapper構造方法

//在構造時就指定了standardWrapper了奧,那麼這個StandardWrapperValue又是何方神聖呢?
public StandardWrapper() {
        super();
        pipeline.setBasic(new StandardWrapperValve());
}

StandardWrapperValve的invoke方法幹了啥?對basic的invoke幹了啥

public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {
        // Initialize local variables we may need
        boolean unavailable = false;
        Throwable throwable = null;
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        ServletRequest sreq = request.getRequest();
        ServletResponse sres = response.getResponse();
        Servlet servlet = null;
        HttpServletRequest hreq = null;
        if (sreq instanceof HttpServletRequest)
            hreq = (HttpServletRequest) sreq;
        HttpServletResponse hres = null;
        if (sres instanceof HttpServletResponse)
            hres = (HttpServletResponse) sres;

        // Check for the application being marked unavailable
        if (!((Context) wrapper.getParent()).getAvailable()) {
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardContext.isUnavailable"));
            unavailable = true;
        }

        // Check for the servlet being marked unavailable
        if (!unavailable && wrapper.isUnavailable()) {
            log(sm.getString("standardWrapper.isUnavailable",
                             wrapper.getName()));
            if (hres == null) {
                ;       // NOTE - Not much we can do generically
            } else {
                long available = wrapper.getAvailable();
                if ((available > 0L) && (available < Long.MAX_VALUE))
                    hres.setDateHeader("Retry-After", available);
                hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                               sm.getString("standardWrapper.isUnavailable",
                                            wrapper.getName()));
            }
            unavailable = true;
        }

        // Allocate a servlet instance to process this request
    	//呼叫了allocate方法,有點像connector的分配processor方法
       if (!unavailable) {
                servlet = wrapper.allocate();
       }

        // Acknowlege the request
    	response.sendAcknowledgement();
        

        // Create the filter chain for this request
    	//非常之容易迷失奧,我們的servlet已經到這裡了
        ApplicationFilterChain filterChain =
            createFilterChain(request, servlet);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            String jspFile = wrapper.getJspFile();
            if (jspFile != null)
                sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
            else
                sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            if ((servlet != null) && (filterChain != null)) {
                //在doFilter裡面,如果沒有filter要執行,才會終於輪到我們的service服務
                //往下的程式碼就不貼了
                filterChain.doFilter(sreq, sres);
            }
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        } catch (IOException e) {
            ...
        } 

        // Release the filter chain (if any) for this request
        try {
            if (filterChain != null)
                filterChain.release();
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.releaseFilters",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                wrapper.unload();
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.unloadException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

    }

至此,connector的一個簡簡單單的container.invoke()的具體流程就已經清晰了,先經過pipeline和valve的摧殘,等到最後basicValve呼叫的時候,又要先經過filter的摧殘,然後才呼叫servlet的service方法,非常之幸苦啊,期間還需要做一些準備工作,設定classLoader,載入並且例項化servlet

啟動Bootstrap1,列印輸出如下:

Client IP Logger Valve
127.0.0.1
------------------------------------
Header Logger Valve
host:localhost:8080
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-language:en-US,en;q=0.5
accept-encoding:gzip, deflate
connection:keep-alive
upgrade-insecure-requests:1
cache-control:max-age=0
------------------------------------

這兩個valve確實是幹了活的,並且由於這兩個valve的特點,先呼叫的valve後列印資訊

Context

通過上面的學習,已經學會了部署一個僅包含一個servlet的wrapper應用,但是通常來說,應用怎麼可能只包含一個servlet呢?肯定是要多個servlet協作的呀

其實context的大部分和wrapper基本一致,只不過這裡的context的BasicVavle就不負責真正呼叫servlet的service方法了,它負責分發請求,根據servletName或者其他的東西將請求分發給context容器內包含的那些wrappers,然後在wrapper裡面再執行上面的流程

這個時候就來了個新的元件Mapper對映器,顧名思義,就是負責將請求對映到對應的wrapper上面

mapper介面的定義如下:

package org.apache.catalina;
public interface Mapper{
    public Container getContainer();
    public void setContainer(Container container);
    public String getProtocol();
    public void setProtocol(String procotol);
    public Container map(Request request,boolean update);
}

  • map方法返回指定的container,也是它的核心方法,找到對應的wrapper

本應用程式的UML如下圖所示:

主要類以及流程分析

這裡主要分析以下Context是如何找到對應的mapper這條線

StandardContextValve的invoke
public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {

        // Validate the request and response object types
        if (!(request.getRequest() instanceof HttpServletRequest) ||
            !(response.getResponse() instanceof HttpServletResponse)) {
            return;     // NOTE - Not much else we can do generically
        }
    
        // Disallow any direct access to resources under WEB-INF or META-INF
        HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
        String contextPath = hreq.getContextPath();
        String requestURI = ((HttpRequest) request).getDecodedRequestURI();
        String relativeURI =
            requestURI.substring(contextPath.length()).toUpperCase();
        if (relativeURI.equals("/META-INF") ||
            relativeURI.equals("/WEB-INF") ||
            relativeURI.startsWith("/META-INF/") ||
            relativeURI.startsWith("/WEB-INF/")) {
            notFound(requestURI, (HttpServletResponse) response.getResponse());
            return;
        }

        Context context = (Context) getContainer();

        // Select the Wrapper to be used for this Request
        Wrapper wrapper = null;
        try {
            //呼叫map方法找到對應的wrapper
            wrapper = (Wrapper) context.map(request, true);
        } catch (IllegalArgumentException e) {
            badRequest(requestURI, 
                       (HttpServletResponse) response.getResponse());
            return;
        }
        if (wrapper == null) {
            notFound(requestURI, (HttpServletResponse) response.getResponse());
            return;
        }

        // Ask this Wrapper to process this Request
        response.setContext(context);
		//呼叫wrapper的invoke方法
        wrapper.invoke(request, response);

    }

SimpleContext的map方法
public Container map(Request request, boolean update) {
    //this method is taken from the map method in org.apache.cataline.core.ContainerBase
    //the findMapper method always returns the default mapper, if any, regardless the
    //request's protocol
    Mapper mapper = findMapper(request.getRequest().getProtocol());
    if (mapper == null)
      return (null);

    // Use this Mapper to perform this mapping
    //呼叫SimpleContextMapper的map方法
    return (mapper.map(request, update));
  }

SimpleContextMapper的map方法
public Container map(Request request, boolean update) {
    // Identify the context-relative URI to be mapped
    String contextPath =
      ((HttpServletRequest) request.getRequest()).getContextPath();
    String requestURI = ((HttpRequest) request).getDecodedRequestURI();
    //從request中切割出來relativeURI
    String relativeURI = requestURI.substring(contextPath.length());
    // Apply the standard request URI mapping rules from the specification
    Wrapper wrapper = null;
    String servletPath = relativeURI;
    String pathInfo = null;
    //根據這個URI去尋找對應的wrapper name
    String name = context.findServletMapping(relativeURI);
    if (name != null)
      //根據name找到對應的wrapper
      wrapper = (Wrapper) context.findChild(name);
    return (wrapper);
  }

SimpleContext的findServletMapping方法

public String findServletMapping(String pattern) {
    synchronized (servletMappings) {
        //servletMappings就是一個HashMap
      return ((String) servletMappings.get(pattern));
    }
  }

SimpleContext的findChild方法

public Container findChild(String name) {
    if (name == null)
      return (null);
    synchronized (children) {       // Required by post-start changes
      return ((Container) children.get(name));
    }
  }

至此context是如何根據relativeURI找到對應的wrapper的流程分析結束

容器啟動

可以看到,此時需要啟動一個伺服器需要事先做多少準備,準備classloader,準備wrapper,準備context,新增valve,新增mapper,然後就可以啟動了

public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

    Context context = new SimpleContext();
    context.addChild(wrapper1);
    context.addChild(wrapper2);

    Valve valve1 = new HeaderLoggerValve();
    Valve valve2 = new ClientIPLoggerValve();

    ((Pipeline) context).addValve(valve1);
    ((Pipeline) context).addValve(valve2);

    Mapper mapper = new SimpleContextMapper();
    mapper.setProtocol("http");
    context.addMapper(mapper);
    Loader loader = new SimpleLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    connector.setContainer(context);
    try {
      connector.initialize();
      connector.start();

      // make the application wait until we press a key.
      System.in.read();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

訪問localhost:8080/Modern或者localhost:8080/Primitive