• 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如下:

    image.png

    高级容器可以包含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两者的关系就如下图所示

    image.png

    一种简单的方式就是,用一个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如下

    image.png

    主要类以及流程分析

    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如下图所示:

    image.png

    主要类以及流程分析

    这里主要分析以下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

  • 相关阅读:
    htmlspecialchar()
    LINUX权限bash: ./startup.sh: Permission denied
    str_replace()
    centos安装教程
    给准备做软件测试的新手们的一点个人心得
    TFS安装与管理
    TFS使用指南
    实现对n个数字随机排序,并循环输出100次
    SSM启动Tomcat报错ERROR [localhoststartStop1] Context initialization failed
    同济大学软件学院万院长谈择业
  • 原文地址:https://www.cnblogs.com/danzZ/p/13998860.html
Copyright © 2020-2023  润新知