tomcat之servlet容器
2017-01-26 21:13
453 查看
深入学习Java Web服务器系列二
一个简单的servlet容器在上一篇博客已经介绍了如何去实现一个简单的静态web容器,我们实现了一个可以解析静态html资源的web容器,下面,我们将进一步实现一个简单的servlet容器,来了解一下servlet容器的基本原理。
我们将在系列一的基础上进行相应的修改。服务器启动监听,当用户在浏览器输入URL发送http请求时,服务器进行解析requst,判断请求的资源类型,如果是静态资源并则返回请求的静态资源,如果是servlet资源,则进行解析并返回页面。系统的时序图如下所示:
下面我们一起来实现这个servlet容器吧,这篇博文分成三个部分,第一部分介绍servlet的生命周期,这个可以帮助我们明确容器的功能需求,第二部分介绍了这个简单的servlet容器的功能和相应的类关系,第三部分就是进行容器的代码实现了。
1. servlet的生命周期
Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。Servlet接口有五个方法,其用法如下。
public void init(ServletConfig config) throws ServletException
public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()
在Servlet的五个方法中,init,service和destroy是servlet的生命周期方法。在servlet类已经初始化之后,init方法将会被servlet容器所调用。servlet容器只调用一次,以此表明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。
servlet容器为servlet请求调用它的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息,而ServletResponse对象封装servlet的响应。在servlet的生命周期中,service方法将会给调用多次。
当从服务中移除一个servlet实例的时候,servlet容器调用destroy方法。这通常发生在servlet容器正在被关闭或者servlet容器需要一些空闲内存的时候。仅仅在所有servlet线程的service方法已经退出或者超时淘汰的时候,这个方法才被调用。在servlet容器已经调用完destroy方法之后,在同一个servlet里边将不会再调用service方法。destroy方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet的内存当前状态是同步的。
2. 结构分析
功能分析通过上面servlet的生命周期,我们可以知道,一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作:
当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法(仅仅一次)。
对每次请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
当servlet类被关闭的时候,调用servlet的destroy方法并卸载servlet类。
我们下面实现的简单的servlet容器并不是一个全功能的servlet容器,我们先实现一个简单的servlet容器,实现的功能如下:
等待HTTP请求。
构造一个ServletRequest对象和一个ServletResponse对象。
假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。
假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
类图关系
我们可以把这个容器分成6个类,
HttpServer
Request
Response
StaticResourceProcessor
ServletProcessor
Constants
这个web容器的入口点(静态main方法)可以在HttpServer类里边找到。main方法创建了一个HttpServer的实例并调用了它的await方法。await方法等待HTTP请求,为每次请求创建一个Request对象和一个Response对象,并把他们分发到一个StaticResourceProcessor实例或者一个ServletProcessor实例中去,这取决于请求一个静态资源还是一个servlet。 Constants类包括涉及其他类的静态final变量WEB_ROOT。WEB_ROOT显示了PrimitiveServlet和这个容器可以提供的静态资源的位置。
3. 代码实现
Constants这里存放web资源的指定目录,用来约定资源的存放位置。
import java.io.File; public class Constants { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; }
HttpServer
HttpServer包含服务器的启动方法,这里要实现监听,当有http请求到达时,启动request的解析,获取请求资源uri,判断请求资源的类型,如果是静态资源,则交给StaticResourceProcessor处理,如果是servlet,则交给ServletProcessor处理。
import java.net.Socket; import java.net.ServerSocket; import java.net.InetAddress; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; public class HttpServer { public static void main(String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await() { ServerSocket serverSocket = null; int port = 8100; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); } //while循环管等待socket请求并进行处理 while (true) { Socket socket = null; InputStream input = null; OutputStream output = null; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // 创建一个request Request request = new Request(input); request.parse(); // 创建一个response Response response = new Response(output); response.setRequest(request); // 检查请求资源是静态资源还是servlet,servlet的请求以"/servlet/"开头 if (request.getUri().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } socket.close(); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } } }
Request
Request类实现了ServletRequest接口,这是为了方便我们进行servlet的构建,request实现解析http请求获取请求资源uri。
import java.io.InputStream; import java.io.IOException; import java.io.BufferedReader; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class Request implements ServletRequest{ private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public String getUri() { return uri; } //解析获取请求uri private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; } // 获取http请求的数据 public void parse() { StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } for (int j=0; j<i; j++) { request.append((char) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } @Override public AsyncContext getAsyncContext() { // TODO Auto-generated method stub return null; } @Override public Object getAttribute(String arg0) { // TODO Auto-generated method stub return null; } @Override public Enumeration<String> getAttributeNames() { // TODO Auto-generated method stub return null; } @Override public String getCharacterEncoding() { // TODO Auto-generated method stub return null; } @Override public int getContentLength() { // TODO Auto-generated method stub return 0; } @Override public long getContentLengthLong() { // TODO Auto-generated method stub return 0; } @Override public String getContentType() { // TODO Auto-generated method stub return null; } @Override public DispatcherType getDispatcherType() { // TODO Auto-generated method stub return null; } @Override public ServletInputStream getInputStream() throws IOException { // TODO Auto-generated method stub return null; } @Override public String getLocalAddr() { // TODO Auto-generated method stub return null; } @Override public String getLocalName() { // TODO Auto-generated method stub return null; } @Override public int getLocalPort() { // TODO Auto-generated method stub return 0; } @Override public Locale getLocale() { // TODO Auto-generated method stub return null; } @Override public Enumeration<Locale> getLocales() { // TODO Auto-generated method stub return null; } @Override public String getParameter(String arg0) { // TODO Auto-generated method stub return null; } @Override public Map<String, String[]> getParameterMap() { // TODO Auto-generated method stub return null; } @Override public Enumeration<String> getParameterNames() { // TODO Auto-generated method stub return null; } @Override public String[] getParameterValues(String arg0) { // TODO Auto-generated method stub return null; } @Override public String getProtocol() { // TODO Auto-generated method stub return null; } @Override public BufferedReader getReader() throws IOException { // TODO Auto-generated method stub return null; } @Override public String getRealPath(String arg0) { // TODO Auto-generated method stub return null; } @Override public String getRemoteAddr() { // TODO Auto-generated method stub return null; } @Override public String getRemoteHost() { // TODO Auto-generated method stub return null; } @Override public int getRemotePort() { // TODO Auto-generated method stub return 0; } @Override public RequestDispatcher getRequestDispatcher(String arg0) { // TODO Auto-generated method stub return null; } @Override public String getScheme() { // TODO Auto-generated method stub return null; } @Override public String getServerName() { // TODO Auto-generated method stub return null; } @Override public int getServerPort() { // TODO Auto-generated method stub return 0; } @Override public ServletContext getServletContext() { // TODO Auto-generated method stub return null; } @Override public boolean isAsyncStarted() { // TODO Auto-generated method stub return false; } @Override public boolean isAsyncSupported() { // TODO Auto-generated method stub return false; } @Override public boolean isSecure() { // TODO Auto-generated method stub return false; } @Override public void removeAttribute(String arg0) { // TODO Auto-generated method stub } @Override public void setAttribute(String arg0, Object arg1) { // TODO Auto-generated method stub } @Override public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { // TODO Auto-generated method stub } @Override public AsyncContext startAsync() throws IllegalStateException { // TODO Auto-generated method stub return null; } @Override public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) throws IllegalStateException { // TODO Auto-generated method stub return null; } }
Response
Response类实现静态资源的发送。
import java.io.OutputStream; import java.io.IOException; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.File; import java.io.PrintWriter; import java.util.Locale; import javax.servlet.ServletOutputStream; import javax.servlet.ServletResponse; public class Response implements ServletResponse { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; PrintWriter writer; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } //处理静态资源请求 public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try { File file = new File(Constants.WEB_ROOT, request.getUri()); fis = new FileInputStream(file); int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch!=-1) { output.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } } catch (FileNotFoundException e) { String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } finally { if (fis!=null) fis.close(); } } @Override public void flushBuffer() throws IOException { // TODO Auto-generated method stub } @Override public int getBufferSize() { // TODO Auto-generated method stub return 0; } @Override public String getCharacterEncoding() { // TODO Auto-generated method stub return null; } @Override public String getContentType() { // TODO Auto-generated method stub return null; } @Override public Locale getLocale() { // TODO Auto-generated method stub return null; } @Override public ServletOutputStream getOutputStream() throws IOException { // TODO Auto-generated method stub return null; } @Override public PrintWriter getWriter() throws IOException { writer = new PrintWriter(output, true); return writer; } @Override public boolean isCommitted() { // TODO Auto-generated method stub return false; } @Override public void reset() { // TODO Auto-generated method stub } @Override public void resetBuffer() { // TODO Auto-generated method stub } @Override public void setBufferSize(int arg0) { // TODO Auto-generated method stub } @Override public void setCharacterEncoding(String arg0) { // TODO Auto-generated method stub } @Override public void setContentLength(int arg0) { // TODO Auto-generated method stub } @Override public void setContentLengthLong(long arg0) { // TODO Auto-generated method stub } @Override public void setContentType(String arg0) { // TODO Auto-generated method stub } @Override public void setLocale(Locale arg0) { // TODO Auto-generated method stub } }
StaticResourceProcessor
StaticResourceProcessor用来实现静态资源的处理
import java.io.IOException; public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } }
ServletProcessor
在ServletProcessor中,我们将根据request解析到servlet名字,进行类加载并获取类的实例。
这里,我们使用URLClassLoader来进行类加载,
public URLClassLoader(URL[] urls)
URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用。
import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.io.File; import java.io.IOException; import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class ServletProcessor { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); URLClassLoader loader = null; try { // 创建一个URLClassLoader //初始化数组,这里我们为了方便指定数组的长度为1 URL[] urls = new URL[1]; //servlet文件的根目录 File classPath = new File(Constants.WEB_ROOT); urls[0] = new URL("file", null, classPath.getCanonicalPath() + File.separator); loader = new URLClassLoader(urls); } catch (IOException e) { System.out.println(e.toString() ); } //加载类 Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { //获取类的实例 servlet = (Servlet) myClass.newInstance(); System.out.println("servlet=="+servlet); servlet.service((ServletRequest) request, (ServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } }
这样子,我们的servlet容器就实现了。
为了进行测试,我们新建一个servlet。
PrimitiveServlet
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class PrimitiveServlet implements Servlet{ public void service(ServletRequest request, ServletResponse response) throws IOException{ System.out.println("from service"); try{ PrintWriter out = response.getWriter(); out.print("HelloWorld"); out.close(); }catch(Exception e){ e.printStackTrace(); } } @Override public void destroy() { System.out.println("destroy"); } @Override public ServletConfig getServletConfig() { // TODO Auto-generated method stub return null; } @Override public String getServletInfo() { // TODO Auto-generated method stub return null; } @Override public void init(ServletConfig arg0) throws ServletException { System.out.println("init"); } }
并进行编译,生成PrimitiveServlet.class
在项目的根目录下新建一个文件夹webroot,并把PrimitiveServlet.class放在目录下。
下面就可以进行测试了。
http://localhost:8100/servlet/PrimitiveServlet
在浏览器上输入上面的地址。
到这里,一个简单的servlet容器就实现了。
4. 程序优化
在上面的程序中,存在一个很严重的问题。try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) request,(ServletResponse) response); }
我们把Request和Response分别向上转换为了ServletRequest和ServletResponse。
这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为Request和Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse方法。拥有一个Response实例,就可以调用sendStaticResource方法。
在Tomcat中,是采用结构模式(facade模式)来弥补这个弊端的。
我们增加两个facade类:
RequestFacade
ResponseFacade
RequestFacade实现了ServletRequest接口并通过在构造方法中传递一个引用了ServletRequest对象的Request实例作为参数来实例化。ServletRequest接口中每个方法的实现都调用了Request对象的相应方法。然而ServletRequest对象本身是私有的,并不能在类的外部访问。我们构造了一个RequestFacade对象并把它传递给service方法,而不是向下转换Request对象为ServletRequest对象并传递给service方法。Servlet程序员仍然可以向下转换ServletRequest实例为RequestFacade,不过它们只可以访问ServletRequest接口里边的公共方法。现在parseUri方法就是安全的了。
ResponseFacade
import java.io.IOException; import java.io.PrintWriter; import java.util.Locale; import javax.servlet.ServletResponse; import javax.servlet.ServletOutputStream; public class ResponseFacade implements ServletResponse { private ServletResponse response; public ResponseFacade(Response response) { this.response = response; } public void flushBuffer() throws IOException { response.flushBuffer(); } public int getBufferSize() { return response.getBufferSize(); } public String getCharacterEncoding() { return response.getCharacterEncoding(); } public Locale getLocale() { return response.getLocale(); } public ServletOutputStream getOutputStream() throws IOException { return response.getOutputStream(); } public PrintWriter getWriter() throws IOException { return response.getWriter(); } public boolean isCommitted() { return response.isCommitted(); } public void reset() { response.reset(); } public void resetBuffer() { response.resetBuffer(); } public void setBufferSize(int size) { response.setBufferSize(size); } public void setContentLength(int length) { response.setContentLength(length); } public void setContentType(String type) { response.setContentType(type); } public void setLocale(Locale locale) { response.setLocale(locale); } }
RequestFacade
import java.io.IOException; import java.io.BufferedReader; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; public class RequestFacade implements ServletRequest { private ServletRequest request = null; public RequestFacade(Request request) { this.request = request; } /* implementation of the ServletRequest*/ public Object getAttribute(String attribute) { return request.getAttribute(attribute); } public Enumeration getAttributeNames() { return request.getAttributeNames(); } public String getRealPath(String path) { return request.getRealPath(path); } public RequestDispatcher getRequestDispatcher(String path) { return request.getRequestDispatcher(path); } public boolean isSecure() { return request.isSecure(); } public String getCharacterEncoding() { return request.getCharacterEncoding(); } public int getContentLength() { return request.getContentLength(); } public String getContentType() { return request.getContentType(); } public ServletInputStream getInputStream() throws IOException { return request.getInputStream(); } public Locale getLocale() { return request.getLocale(); } public Enumeration getLocales() { return request.getLocales(); } public String getParameter(String name) { return request.getParameter(name); } public Map getParameterMap() { return request.getParameterMap(); } public Enumeration getParameterNames() { return request.getParameterNames(); } public String[] getParameterValues(String parameter) { return request.getParameterValues(parameter); } public String getProtocol() { return request.getProtocol(); } public BufferedReader getReader() throws IOException { return request.getReader(); } public String getRemoteAddr() { return request.getRemoteAddr(); } public String getRemoteHost() { return request.getRemoteHost(); } public String getScheme() { return request.getScheme(); } public String getServerName() { return request.getServerName(); } public int getServerPort() { return request.getServerPort(); } public void removeAttribute(String attribute) { request.removeAttribute(attribute); } public void setAttribute(String key, Object value) { request.setAttribute(key, value); } public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { request.setCharacterEncoding(encoding); } }
修改ServletProcessor
Servlet servlet = null; RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest)requestFacade(ServletResponse)responseFacade); }
相关文章推荐
- Google App Servlet容器转型 – 从Tomcat到Jetty
- Servlet容器Tomcat中web.xml中url-pattern的配置详解
- Tomcat学习2.1(简单的Servlet容器)
- 5.1 Tomcat学习(servlet容器)
- Tomcat与javaWeb 第三章 Servlet容器的各种对象
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
- Tomcat学习2.2(简单的Servlet容器)
- Servlet容器与Servlet,Tomcat的关系
- Resin与其他容器(tomcat/jetty)默认处理Servlet
- tomcat(5)servlet容器
- HowTomcatWorks学习笔记--一个简单的Servlet容器(续)
- 学习tomcat之servlet容器
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
- HowTomcatWork 笔记 1 Servlet 容器做的3件事情
- Apache CXF实现Web Service(3)——Tomcat容器和不借助Spring的普通Servlet实现JAX-RS(RESTful) web service
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
- Web 服务器、Servlet容器以及Tomcat服务器
- Tomcat 容器lib下添加 wlfullclient.jar 包引起项目中javax servlet 的冲突
- JSP学习 —— 开篇:JSP,servlet容器,Tomcat,servlet容器之间的关系