tomcatservlet容器
Posted zhchoutai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tomcatservlet容器相关的知识,希望对你有一定的参考价值。
【0】README
0.0)本文部分文字描写叙述转自:“深入剖析tomcat”,旨在学习 tomcat(5)servlet容器 的基础知识。
0.1)intro to servlet容器:servlet容器是用来处理请求servlet资源,并为web客户端填充response 对象的模块;
0.2)补充:吃个饭过来。真心没想到这么多人围观,再次对文章又一次排版(并非博主我不给力,是CSDN编辑器时不时吊链子啊,oh),并加入了測试用例的程序流程图(共3张)[1604111947]。
0.3)通知:本文因为CSDN编辑器掉链子使得博文排版乱八七糟。故晚辈我又又一次进行了排版,请大家移步到 tomcat(5)servlet容器(lastest
version),本文明天也会删除[1604112328]。
【1】Container接口
1)在Tomcat中,共同拥有四种容器(types):(干货——Tomcat中共同拥有4种容器)
t1)Engine:表示整个Catalina servlet 引擎。t2)Host:表示包括有一个或多个 Context容器的虚拟主机。t3)Context:表示一个web 应用程序,一个Context 能够有多个 Wrapper。t4)Wrapper:表示一个独立的servlet。
2)以上4中容器都是 org.apache.catalina包下的接口:分别为Engine。Host, Context。 Wrapper,他们都继承自Container接口。
这4个接口的标准实现是 StandardEngine类。StandardHost类,StandardContext类,StandardWrapper类,他们都在 org.apache.catalina.core 包内;
Attention)
A1)全部的实现类都继承自抽象类 ContainerBase ;A2)Container接口的设计满足以下条件:在部署应用时,Tomcat管理员能够通过编辑配置文件(server.xml)来决定使用哪种容器。这是通过引入容器中的管道(pipeline)和阀(valve)的集合实现的;(干货——引入了容器中的管道和阀)
【2】管道任务
1)本节旨在说明:当连接器调用了servlet容器的invoke方法后会发生什么事情,并讨论org.apache.catalina 包中的4个相关接口,Pipeline, Valve, ValveContext 和 Contained;
2)管道和阀:
2.1)管道:包括该servlet容器将要调用的任务。
2.2)一个阀:表示一个详细的任务。
2.3)在servlet容器的管道中,有一个基础阀,可是,能够加入随意数量的阀。阀的数量指的是额外加入的阀数量,即。不包括基础阀。
有意思的是, 能够通过编辑tomcat 的 配置文件(server.xml)来动态地加入阀;
2.4)一条管道和阀的示意图例如以下:
Attention)
A1)管道就想过滤器链条一样。而阀则好似过滤器。A2)当一个阀运行完毕后。会调用下一个阀继续运行。基础阀总是最后一个运行。(干货——当一个阀运行完毕后,会调用下一个阀继续运行。基础阀总是最后一个运行)
3)管道的invoke方法:一个servlet容器能够有一条管道,当调用了容器的invoke方法后,容器会将处理工作交由管道完毕,而管道会调用当中的第一个阀開始处理。当第一个阀处理完后,它会调用兴许的阀继续运行任务,直到管道中全部的阀都处理完毕。
以下是invoke方法的伪代码:
import java.io.IOException; import java.io.PrintWriter; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.Enumeration; import java.util.Locale; import java.util.ResourceBundle;
import java.io.IOException; import java.io.PrintWriter; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.Enumeration; import java.util.Locale; import java.util.ResourceBundle;
// invoke each valve added to the pipeline,先是非基础阀调用 invoke方法 for(;;){ valve[i].invoke(); } // then, invoke the basic valve, 后是基础阀调用 invoke方法(基础阀最后一个调用invoke方法) basicValve.invoke(...); public void invoke(Request request, Response response) // SimplePipeline.invoke() throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request (new SimplePipelineValveContext()).invokeNext(request, response); } public void invokeNext(Request request, Response response) // SimplePipeline.invokeNext() throws IOException, ServletException { int subscript = stage; stage = stage + 1; // Invoke the requested Valve for the current request thread if (subscript < valves.length) { valves[subscript].invoke(request, response, this); } else if ((subscript == valves.length) && (basic != null)) { basic.invoke(request, response, this); } else { throw new ServletException("No valve"); } } } // end of inner class
4)实现阀的遍历:Tomcat引入接口 org.apache.catalina.ValveContext 来实现阀的遍历运行;
4.1)管道必须保证加入到当中的全部阀和基础阀都被调用一次:这是通过调用一个 ValveContext接口实例来实现的。
4.2)ValveContext接口中最重要的方法是 invokeNext方法:在创建了ValveContext实例后,管道会调用ValveContext实例的 invokeNext方法。ValveContext实例会首先调用管道中的 第一个阀。第一个阀运行完后,会调用后面的阀继续运行。
ValveContext实例会将自身传给每一个阀,因此,每一个阀都能够调用 ValveContext实例的 invokeNext方法;
5)org.apache.catalina.core.StandardPipeline类:是全部servlet容器中的Pipeline接口的实现,Tomcat4中有一个实现了ValveContext接口的内部类,名为StandardPipelineValveContext;
6)Tomcat5 从 StandardPipeline类中移除了 StandardPipelineValveContext类:却使用 org.apache.catalina.core.StandardValveContext类来调用阀;
【2.1】Pipeline接口
1)对于Pipeline接口:首先要提到的一个方法是 invoke方法,servlet容器调用invoke方法来開始调用管道中的阀和基础阀;
public interface Pipeline { public Valve getBasic(); public void setBasic(Valve valve); public void addValve(Valve valve); public Valve[] getValves(); public void invoke(Request request, Response response) throws IOException, ServletException; public void removeValve(Valve valve); }
2)getBasic和setBasic:setBasic方法将基础阀设置到管道中,getBasic获取基础阀;(干货——管道中能够指定基础阀)
3)addValve和removeValve:新增阀和删除阀;(干货——在管道中能够新增和删除非基础阀)
【2.2】Valve接口
1)阀是Valve接口的实例。用来处理接收到的请求,有两个方法:invoke方法和getinfo方法;
public interface Valve { public String getInfo(); public void invoke(Request request, Response response,ValveContext context) throws IOException, ServletException;
【2.3.】ValveContext接口
1)有两个方法:invokeNext方法和 getInfo方法;
【2.4】Contained接口
public interface Contained { public Container getContainer(); public void setContainer(Container container); }
【3】Wrapper接口
1)intro to Wrapper: Wrapper级的servlet容器是一个 org.apache.catalina.Wrapper接口的实例,表示一个独立的servlet定义。Wrapper接口继承自 Container接口。又加入了一些额外的方法。
public interface Wrapper extends Container { public long getAvailable(); public void setAvailable(long available); public String getJspFile(); public void setJspFile(String jspFile); public int getLoadOnStartup(); public void setLoadOnStartup(int value); public String getRunAs(); public void setRunAs(String runAs); public String getServletClass(); public void setServletClass(String servletClass); public boolean isUnavailable(); public void addInitParameter(String name, String value); public void addInstanceListener(InstanceListener listener); public void addSecurityReference(String name, String link); public Servlet allocate() throws ServletException; public void deallocate(Servlet servlet) throws ServletException; public String findInitParameter(String name); public String[] findInitParameters(); public String findSecurityReference(String name); public String[] findSecurityReferences(); public void load() throws ServletException; public void removeInitParameter(String name); public void removeInstanceListener(InstanceListener listener); public void removeSecurityReference(String name); public void unavailable(UnavailableException unavailable); public void unload() throws ServletException; }
2)Wrapper接口的实现类:要负责管理继承servlet类的servlet生命周期。即,调用 servlet的 init(), service(), destroy()方法。
3)因为Wrapper已经是最低级的容器了。不能再向当中加入子容器;(干货——Wrapper已经是最低级的servlet容器)
4)Wrapper接口有两个方法:load方法 和 allocate方法。
4.1)load方法:加载并初始化servlet类;
4.2)allocate方法:会分配一个已经初始化的servlet实例。
【4】Context接口
1)intro to Context:Context接口是一个web 应用程序,一个Context实例能够有一个或多个Wrapper实例作为其子容器。
2)比較重要的方法: addWrapper() and createWrapper()。
【5】Wrapper 应用程序(demonstrate how to build a smallest servlet container)
1)SimpleWrapper类:该类实现了Wrapper接口,包括一个Pipeline实例。并使用Loader实例加载servlet类。Pipeline实例包括一个基础阀和两个额外的阀。
【5.1】SimpleLoader类
1)SimpleLoader:负责完毕类的加载工作。它知道servlet类的位置,通过调用其getClassLoader能够返回一个 java.lang.ClassLoader实例。能够用来搜索servlet类的位置;
2)SimpleLoader的构造函数:会初始化类加载器,供 SimpleWrapper实例使用;
public class SimpleLoader implements Loader { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; ClassLoader classLoader = null; Container container = null; public SimpleLoader() { try { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; 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() ); } }
【5.2】SimplePipeline类
1)该类最重要的方法是 invoke方法;
public class SimplePipeline implements Pipeline { public SimplePipeline(Container container) { setContainer(container); } // The basic Valve (if any) associated with this Pipeline. protected Valve basic = null; // The Container with which this Pipeline is associated. protected Container container = null; // the array of Valves protected Valve valves[] = new Valve[0]; public void setContainer(Container container) { this.container = container; } public Valve getBasic() { return basic; } public void setBasic(Valve valve) { this.basic = valve; ((Contained) valve).setContainer(container); } public void addValve(Valve valve) { if (valve instanceof Contained) ((Contained) valve).setContainer(this.container); synchronized (valves) { Valve results[] = new Valve[valves.length +1]; System.arraycopy(valves, 0, results, 0, valves.length); results[valves.length] = valve; valves = results; } } public Valve[] getValves() { return valves; } public void invoke(Request request, Response response) throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request (new SimplePipelineValveContext()).invokeNext(request, response); } public void removeValve(Valve valve) { } // this class is copied from org.apache.catalina.core.StandardPipeline class\'s // StandardPipelineValveContext inner class. protected class SimplePipelineValveContext implements ValveContext { protected int stage = 0; public String getInfo() { return null; } public void invokeNext(Request request, Response response) throws IOException, ServletException { int subscript = stage; stage = stage + 1; // Invoke the requested Valve for the current request thread if (subscript < valves.length) { valves[subscript].invoke(request, response, this); } else if ((subscript == valves.length) && (basic != null)) { basic.invoke(request, response, this); } else { throw new ServletException("No valve"); } } } // end of inner class }
【5.3】SimpleWrapper类
1)该类实现了Wrapper接口:并提供了 allocate 和 load 方法的实现。
2)getLoader()方法:该方法返回一个用于加载servlet 类的加载器。若Wrapper实例已经关联了一个加载器,则直接将其返回;否则,它将返回父容器的加载器。若没有父容器,getLoader方法会返回null;
3)SimpleWrapper类:有一个Pipeline实例。并该为Pipeline实例设置基础阀;
public class SimpleWrapper implements Wrapper, Pipeline { // the servlet instance private Servlet instance = null; private String servletClass; private Loader loader; private String name; private SimplePipeline pipeline = new SimplePipeline(this); protected Container parent = null; public SimpleWrapper() { pipeline.setBasic(new SimpleWrapperValve()); } public synchronized void addValve(Valve valve) { pipeline.addValve(valve); } public Servlet allocate() throws ServletException { // Load and initialize our instance if necessary if (instance==null) { try { instance = loadServlet(); } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException("Cannot allocate a servlet instance", e); } } return instance; } 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"); } 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(); // Load the specified servlet class from the appropriate class loader Class classClass = null; try { if (classLoader!=null) { classClass = classLoader.loadClass(actualClass); } } catch (ClassNotFoundException e) { throw new ServletException("Servlet class not found"); } // Instantiate and initialize an instance of the servlet class itself try { servlet = (Servlet) classClass.newInstance(); } catch (Throwable e) { throw new ServletException("Failed to instantiate servlet"); } // Call the initialization method of this servlet try { servlet.init(null); } catch (Throwable f) { throw new ServletException("Failed initialize servlet."); } return servlet; } public String getInfo() { return null; } public Loader getLoader() { if (loader != null) return (loader); if (parent != null) return (parent.getLoader()); return (null); }
【5.4】SimpleWrapperValve类
1)SimpleWrapperValve是一个基础阀:用于处理读iSimpleWrapper类的请求,其最基本的方法是 invoke方法;
public class SimpleWrapperValve implements Valve, Contained { protected Container container; public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException { SimpleWrapper wrapper = (SimpleWrapper) 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; // Allocate a servlet instance to process this request try { servlet = wrapper.allocate(); if (hres!=null && hreq!=null) { servlet.service(hreq, hres); } else { servlet.service(sreq, sres); } } catch (ServletException e) { } } public String getInfo() { return null; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }
【5.5】ClientIPLoggerValve类
1)ClientIPLoggerValve类所表示的阀:用来将client的IP 地址输出到控制台上;
2)注意其invoke方法:它先调用方法參数 valveContext的 invokeNext方法来调用管道中的下一个阀。
然后,它会把几行字符串output到 console。
public class ClientIPLoggerValve implements Valve, Contained { protected Container container; 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("------------------------------------"); } public String getInfo() { return null; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }
【5.6】HeaderLoggerValve类
1)HeaderLoggerValve类作用:会把请求头信息output到 console。
2)注意其invoke方法:它先调用方法參数 valveContext的 invokeNext方法来调用管道中的下一个阀。
public class HeaderLoggerValve implements Valve, Contained { protected Container container; 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("------------------------------------"); } public String getInfo() { return null; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }
【5.7】Bootstrap1
step1)创建 HttpConnector 和 SimpleWrapper实例,并将须要加载的 servlet name 赋值给 Wrapper实例。step2)创建一个加载器和两个阀,将加载器设置到Wrapper实例中 ;step3)将上述创建的两个阀加入到 Wrapper的管道中;step4)将Wrapper 实例设置为 连接器的servlet容器。并初始化并启动连接器;
public final class Bootstrap1 { public static void main(String[] args) { /* call by using http://localhost:8080/ModernServlet, but could be invoked by any name */ HttpConnector connector = new HttpConnector(); Wrapper wrapper = new SimpleWrapper(); wrapper.setServletClass("servlet.ModernServlet"); // 设置servlet的相对路径 Loader loader = new SimpleLoader(); // 类加载器 Valve valve1 = new HeaderLoggerValve(); // 把请求头信息output到 console Valve valve2 = new ClientIPLoggerValve();// 用来将client的IP 地址输出到控制台上 wrapper.setLoader(loader); ((Pipeline) wrapper).addValve(valve1); ((Pipeline) wrapper).addValve(valve2); connector.setContainer(wrapper); try { connector.initialize(); // 创建服务器套接字 connector.start(); // // make the application wait until we press a key. System.in.read(); } catch (Exception e) { e.printStackTrace(); } } }
Attention)我这里总结了该測试用例的调用流程图
【5.8】运行应用程序
1)运行參数
E:\\bench-cluster\\cloud-data-preprocess\\HowTomcatWorks\\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;E:\\bench-cluster\\cloud-data-preprocess\\HowTomcatWorks\\webroot com.tomcat.chapter5.startup/Bootstrap1 HttpConnector Opening server socket on all host IP addresses HttpConnector[8080] Starting background thread ModernServlet -- init Client IP Logger Valve 127.0.0.1 ------------------------------------ Header Logger Valve host:localhost:8080 connection:keep-alive accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 accept-encoding:gzip, deflate, sdch accept-language:zh-CN,zh;q=0.8,en;q=0.6 ------------------------------------ Client IP Logger Valve 127.0.0.1 ------------------------------------ Header Logger Valve host:localhost:8080 connection:keep-alive accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 user-agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 accept-encoding:gzip, deflate, sdch accept-language:zh-CN,zh;q=0.8,en;q=0.6 ------------------------------------
2)运行结果
【6】Context应用程序
0)intro to Context app:本app 展示了怎样使用一个包括了两个Wrapper实例的Context实例来构建web app。 这两个Wrapper 实例包装了两个servlet类,当应用程序有多个 Wrapper实例时,须要使用一个 映射器。
映射器是组件,帮助servlet容器(Context实例)选择一个子容器来处理某个指定的请求;
1)尽管有些应用程序仅仅须要一个servlet。但大部分web app 是须要多个servlet合作的。
这些应用程序中,须要的servlet容器是Context,不是Wrapper;
2)本应用程序的映射器:是SimpleContextMapper类的实例。该类实现类Mapper接口,servlet容器能够使用多个 映射器来支持不同的协议。
public interface Mapper { public Container getContainer(); // 返回与该映射器相关联的servlet容器的实例。 public void setContainer(Container container); // 设置与该映射器相关联的servlet容器。 public String getProtocol(); // 返回该映射器负责处理的协议 public void setProtocol(String protocol); //指定该映射器负责处理哪种协议 public Container map(Request request, boolean update); // 返回要处理某个特定请求的子容器的实例; }
3)SimpleContext类:是Context容器的一个实例,它使用了SimpleContextMapper 类的实例作为其映射器,将SimpleContextValve 的实例作为基础阀;
4)Context容器中额外加入了两个阀: ClinetIPLoggerValve 和 HeaderLoggerValve。并包括两个 Wrapper 实例作为其子容器,二者都是 SimpleWrapper 实例;这两个Wrapper实例使用 SimpleWrapperValve 实例作为其基础阀,不再加入其它阀;
5)剩下的内容包括:
step1)容器包括一个管道,容器的invoke方法会调用管道的invoke方法;step2)管道的invoke方法会调用全部加入到其容器中的阀,然后再调用其基础阀的invoke方法。step3)在Wrapper实例中。 基础阀负责加载相关联的servlet类,并对请求进行响应;step4)在包括子容器的 Context实例中。 基础阀使用映射器来查找一个子容器。该子容器负责处理接收到的请求。若找到了相应的子容器,则调用其invoke方法。转到step1继续运行;
6)以下对上述的steps 做 detailed intro
step1)SimpleContext类的invoke方法调用管道的invoke方法:step2)管道SimplePipeline的invoke例如以下:
public void invoke(Request request, Response response) throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request (new SimplePipelineValveContext()).invokeNext(request, response); // 会调用全部加入到Context 实例中的阀,然后再调用基础阀的invoke方法; }
step3)SimpleContext类中。基础阀是 SimpleContextValve类的实例。在SimpleContextValve类的 invoke方法中。 SimpleContextValve实例使用了 Context实例的映射器来查找 Wrapper容器;public class SimpleContext implements Context, Pipeline { public SimpleContext() { pipeline.setBasic(new SimpleContextValve()); } public void invoke(Request request, Response response, ValveContext valveContext) //tomcatservlet原理及其生命周期