Tomcat深入剖析学习02
Tomcat深入剖析学习02-实现一个简单的Servlet容器
一.什么是Servlet
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1、编写一个Java类,实现servlet接口。
2、把开发好的Java类部署到web服务器中。
按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet
二.Servlet容器
就是创建、管理servlet规范中相关对象、生命周期的应用程序。Tomcat就是一种Servlet容器。
三.相关类
javax.servlet.Servlet接口
所有Servlet类必须实现此Servlet接口,实现关于Servlet接口相关方法。这些方法如下:
1.init():实例化某个servlet类后,容器调用对应的init方法进行初始化,该方法只会被调用一次;
2.service():当servlet的一个客户端请求到达后,容器就开始调用对应servlet的service方法。同时需要传入参数servletRequest和servletResponse。
从字面意思就能知道,servletRequest携带了客户端发送的HTTP请求的信息,而servletResponse则用于封装servlet的响应信息。
3.destroy():当servlet实例调用完毕要被移除时,destroy方法将被调用。
4.getServletConfig():该方法用于取得<servlet> <init-param>配置的参数
5.getServletInfo():该方法提供有关servlet的信息,如作者、版本、版权。
**其中1,2,3与Servlet类生命周期相关
四.本节应用程序实现类
本节servlet容器程序包含六个类,与01节对比,HttpServer1主程序已经做了一些功能解耦,具体说明如下。
1.HttpServer1:servlet容器核心程序,包括:主程序入口,服务器创建与启动,request/response创建,按请求uri调用不同的处理类
2.Request:实现javax.servlet.ServletRequest接口,以及01节中自己实现的解析方法
3.Response:实现javax.servlet.ServletResponse接口,
4.StaticResourceProcessor:静态资源处理类,处理uri是静态资源的请求
5.ServletProcessor1:Servlet处理类,处理uri形如/servlet/servletname的请求(动态资源请求)
6.Constants:常量类,将01节中服务器的常量部分功能拆解出来,包含了文根信息。
此外,既然是Servlet容器,我们还需定义一个Servlet类PrimitiveServlet实现Servlet接口。
五.具体代码
1.PrimitiveServlet:主要实现了init,service,destroy方法
1 package com.liuwei.ServletServer1; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 6 import javax.servlet.Servlet; 7 import javax.servlet.ServletConfig; 8 import javax.servlet.ServletException; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 12 /** 13 * 14 * @author liuwei 15 * 继承了servlet接口,重写5个方法 16 */ 17 public class PrimitiveServlet implements Servlet{ 18 19 @Override 20 public void destroy() { 21 System.out.println("destroy"); 22 } 23 24 @Override 25 public ServletConfig getServletConfig() { 26 return null; 27 } 28 29 @Override 30 public String getServletInfo() { 31 return null; 32 } 33 34 @Override 35 public void init(ServletConfig servletConfig) throws ServletException { 36 System.out.println("init"); 37 } 38 //service方法,浏览器显示相关信息 39 @Override 40 public void service(ServletRequest arg0, ServletResponse arg1) 41 throws ServletException, IOException { 42 System.out.println("from service"); 43 PrintWriter pw = arg1.getWriter(); 44 pw.println("this is primitiveServlet!"); 45 } 46 47 }
2.Request类:接口的类没有具体实现,保留了01节中的解析方法parse,parseuri
1 package com.liuwei.ServletServer1; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.UnsupportedEncodingException; 7 import java.util.Enumeration; 8 import java.util.Locale; 9 import java.util.Map; 10 11 import javax.servlet.AsyncContext; 12 import javax.servlet.DispatcherType; 13 import javax.servlet.RequestDispatcher; 14 import javax.servlet.ServletContext; 15 import javax.servlet.ServletInputStream; 16 import javax.servlet.ServletRequest; 17 import javax.servlet.ServletResponse; 18 19 //HTTP请求类 20 public class Request implements ServletRequest{ 21 //请求对象中的输入流对象 22 private InputStream input; 23 24 private String uri; 25 //构造函数 26 public Request(InputStream input){ 27 this.input = input; 28 } 29 //解析请求的函数 30 public void parse(){ 31 StringBuffer request = new StringBuffer(); 32 byte[] buffer = new byte[2038]; 33 int i=-1; 34 try{ 35 i = input.read(buffer); 36 }catch(Exception e){ 37 e.printStackTrace(); 38 } 39 for(int j=0;j<i;j++){ 40 request.append((char)buffer[j]); 41 } 42 System.out.println(request.toString()); 43 this.uri = parseUri(request.toString()); 44 } 45 //分析请求的静态资源uri,从两个空格间获取 46 public String parseUri(String request){ 47 int index1,index2; 48 index1 = request.indexOf(" "); 49 if (index1 != -1){ 50 index2 = request.indexOf(" ", index1+1); 51 if (index2>index1){ 52 return request.substring(index1+1,index2); 53 } 54 } 55 return null; 56 } 57 public String getUri(){ 58 return uri; 59 } 60 @Override 61 public AsyncContext getAsyncContext() { 62 // TODO Auto-generated method stub 63 return null; 64 } 65 @Override 66 public Object getAttribute(String arg0) { 67 // TODO Auto-generated method stub 68 return null; 69 } 70 @Override 71 public Enumeration<String> getAttributeNames() { 72 // TODO Auto-generated method stub 73 return null; 74 } 75 @Override 76 public String getCharacterEncoding() { 77 // TODO Auto-generated method stub 78 return null; 79 } 80 @Override 81 public int getContentLength() { 82 // TODO Auto-generated method stub 83 return 0; 84 } 85 @Override 86 public String getContentType() { 87 // TODO Auto-generated method stub 88 return null; 89 } 90 @Override 91 public DispatcherType getDispatcherType() { 92 // TODO Auto-generated method stub 93 return null; 94 } 95 @Override 96 public ServletInputStream getInputStream() throws IOException { 97 // TODO Auto-generated method stub 98 return null; 99 } 100 @Override 101 public String getLocalAddr() { 102 // TODO Auto-generated method stub 103 return null; 104 } 105 @Override 106 public String getLocalName() { 107 // TODO Auto-generated method stub 108 return null; 109 } 110 @Override 111 public int getLocalPort() { 112 // TODO Auto-generated method stub 113 return 0; 114 } 115 @Override 116 public Locale getLocale() { 117 // TODO Auto-generated method stub 118 return null; 119 } 120 @Override 121 public Enumeration<Locale> getLocales() { 122 // TODO Auto-generated method stub 123 return null; 124 } 125 @Override 126 public String getParameter(String arg0) { 127 // TODO Auto-generated method stub 128 return null; 129 } 130 @Override 131 public Map<String, String[]> getParameterMap() { 132 // TODO Auto-generated method stub 133 return null; 134 } 135 @Override 136 public Enumeration<String> getParameterNames() { 137 // TODO Auto-generated method stub 138 return null; 139 } 140 @Override 141 public String[] getParameterValues(String arg0) { 142 // TODO Auto-generated method stub 143 return null; 144 } 145 @Override 146 public String getProtocol() { 147 // TODO Auto-generated method stub 148 return null; 149 } 150 @Override 151 public BufferedReader getReader() throws IOException { 152 // TODO Auto-generated method stub 153 return null; 154 } 155 @Override 156 public String getRealPath(String arg0) { 157 // TODO Auto-generated method stub 158 return null; 159 } 160 @Override 161 public String getRemoteAddr() { 162 // TODO Auto-generated method stub 163 return null; 164 } 165 @Override 166 public String getRemoteHost() { 167 // TODO Auto-generated method stub 168 return null; 169 } 170 @Override 171 public int getRemotePort() { 172 // TODO Auto-generated method stub 173 return 0; 174 } 175 @Override 176 public RequestDispatcher getRequestDispatcher(String arg0) { 177 // TODO Auto-generated method stub 178 return null; 179 } 180 @Override 181 public String getScheme() { 182 // TODO Auto-generated method stub 183 return null; 184 } 185 @Override 186 public String getServerName() { 187 // TODO Auto-generated method stub 188 return null; 189 } 190 @Override 191 public int getServerPort() { 192 // TODO Auto-generated method stub 193 return 0; 194 } 195 @Override 196 public ServletContext getServletContext() { 197 // TODO Auto-generated method stub 198 return null; 199 } 200 @Override 201 public boolean isAsyncStarted() { 202 // TODO Auto-generated method stub 203 return false; 204 } 205 @Override 206 public boolean isAsyncSupported() { 207 // TODO Auto-generated method stub 208 return false; 209 } 210 @Override 211 public boolean isSecure() { 212 // TODO Auto-generated method stub 213 return false; 214 } 215 @Override 216 public void removeAttribute(String arg0) { 217 // TODO Auto-generated method stub 218 219 } 220 @Override 221 public void setAttribute(String arg0, Object arg1) { 222 // TODO Auto-generated method stub 223 224 } 225 @Override 226 public void setCharacterEncoding(String arg0) 227 throws UnsupportedEncodingException { 228 // TODO Auto-generated method stub 229 230 } 231 @Override 232 public AsyncContext startAsync() { 233 // TODO Auto-generated method stub 234 return null; 235 } 236 @Override 237 public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) { 238 // TODO Auto-generated method stub 239 return null; 240 } 241 }
3.Response类:
1 package com.liuwei.ServletServer1; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 import java.io.OutputStream; 7 import java.io.PrintWriter; 8 import java.util.Locale; 9 10 import javax.servlet.ServletOutputStream; 11 import javax.servlet.ServletResponse; 12 13 public class Response implements ServletResponse{ 14 15 private Request request; 16 public OutputStream output; 17 private static final int BufferLength = 1024; 18 private PrintWriter pw; 19 20 public Response(OutputStream output){ 21 this.output = output; 22 } 23 public void setRequest(Request request){ 24 this.request = request; 25 } 26 //发送静态资源方法 27 public void sendStaticResource() throws IOException { 28 byte[] buffer = new byte[BufferLength]; 29 FileInputStream fis = null; 30 try{ 31 //查找请求中资源是否在root文件夹中存在,存在则写到output中,未存在在output写404信息 32 File file = new File(Constants.WEB_ROOT,request.getUri()); 33 if(file.exists()){ 34 fis = new FileInputStream(file); 35 int num = fis.read(buffer,0,BufferLength); 36 if(num!=-1){ 37 output.write(buffer,0,num); 38 fis.read(buffer,0,BufferLength); 39 } 40 }else{ 41 String errorMsg = "HTTP/1.1 404 File Not Found\r\n" + 42 "Content-Type:text/html\r\n" + 43 "Content-Length:23\r\n" + 44 "\r\n" + 45 "<h1>File Not Found</h1>"; 46 output.write(errorMsg.getBytes()); 47 } 48 }catch(Exception ex){ 49 ex.printStackTrace(); 50 }finally{ 51 if(null!=fis){ 52 fis.close(); 53 } 54 } 55 } 56 @Override 57 public void flushBuffer() throws IOException { 58 // TODO Auto-generated method stub 59 60 } 61 @Override 62 public int getBufferSize() { 63 // TODO Auto-generated method stub 64 return 0; 65 } 66 @Override 67 public String getCharacterEncoding() { 68 // TODO Auto-generated method stub 69 return null; 70 } 71 @Override 72 public String getContentType() { 73 // TODO Auto-generated method stub 74 return null; 75 } 76 @Override 77 public Locale getLocale() { 78 // TODO Auto-generated method stub 79 return null; 80 } 81 @Override 82 public ServletOutputStream getOutputStream() throws IOException { 83 // TODO Auto-generated method stub 84 return null; 85 } 86 @Override 87 public PrintWriter getWriter() throws IOException { 88 pw = new PrintWriter(output,true); 89 return pw; 90 } 91 @Override 92 public boolean isCommitted() { 93 // TODO Auto-generated method stub 94 return false; 95 } 96 @Override 97 public void reset() { 98 // TODO Auto-generated method stub 99 100 } 101 @Override 102 public void resetBuffer() { 103 // TODO Auto-generated method stub 104 105 } 106 @Override 107 public void setBufferSize(int arg0) { 108 // TODO Auto-generated method stub 109 110 } 111 @Override 112 public void setCharacterEncoding(String arg0) { 113 // TODO Auto-generated method stub 114 115 } 116 @Override 117 public void setContentLength(int arg0) { 118 // TODO Auto-generated method stub 119 120 } 121 @Override 122 public void setContentType(String arg0) { 123 // TODO Auto-generated method stub 124 125 } 126 @Override 127 public void setLocale(Locale arg0) { 128 // TODO Auto-generated method stub 129 130 } 131 }
4.ServletProcessor类:负责根据请求uri创建对应的servlet类,初始化并调用其service方法,由此可见,对应请求来临时,才会加载对应类实例
1 package com.liuwei.ServletServer1; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.net.URL; 6 import java.net.URLClassLoader; 7 import java.net.URLStreamHandler; 8 9 import javax.servlet.Servlet; 10 import javax.servlet.ServletRequest; 11 import javax.servlet.ServletResponse; 12 13 /** 14 * servlet加载类 15 * 处理uri形如/servlet/servletName请求 16 * @author DELL 17 * 18 */ 19 public class ServletProcessor1 { 20 //分析request中的uri,并用类加载器加载 21 public void process(Request request, Response response){ 22 //解析uri中的servletName 23 String uri = request.getUri(); 24 String servletName = "com.liuwei.ServletServer1."+uri.substring(uri.lastIndexOf('/')+1); 25 URLClassLoader loader = null; 26 try{ 27 //**创建类载入器loader 28 URL[] urls = new URL[1]; 29 URLStreamHandler streamHandler = null; 30 File classPath = new File(Constants.WEB_ROOT); 31 //servlet容器中,类载入器查找servlet类的目录称为仓库(repository) 32 String repository = (new URL("file",null,classPath.getCanonicalPath()+File.separator)).toString(); 33 urls[0] = new URL(null,repository,streamHandler); 34 loader = new URLClassLoader(urls); 35 }catch(IOException ex){ 36 ex.printStackTrace(); 37 } 38 //载入对应的servlet类 39 Class myClass = null; 40 try{ 41 myClass = loader.loadClass(servletName); 42 }catch(ClassNotFoundException ex){ 43 ex.printStackTrace(); 44 } 45 //实例化载入的servlet类 46 Servlet servlet = null; 47 try{ 48 servlet = (Servlet)myClass.newInstance(); 49 servlet.service((ServletRequest)request, (ServletResponse)response); 50 }catch(Exception e){ 51 System.out.println(e.toString()); 52 }catch(Throwable e){ 53 System.out.println(e.toString()); 54 } 55 } 56 57 }
5.StaticResourceProcessor类:静态资源处理类
1 package com.liuwei.ServletServer1; 2 3 import java.io.IOException; 4 5 /** 6 * 静态资源处理类 7 * @author DELL 8 * 9 */ 10 public class StaticResourceProcessor { 11 //实质还是调用了response的sendStaticResource方法 12 public void process(Request request,Response response){ 13 try{ 14 response.sendStaticResource(); 15 }catch(IOException e){ 16 System.out.println(e.toString()); 17 } 18 } 19 }
6.Constants类:
1 package com.liuwei.ServletServer1; 2 3 import java.io.File; 4 5 6 public class Constants { 7 //服务器的web项目所在目录 8 public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; 9 //servlet所在地址 10 public static final String SERVLET_ROOT = System.getProperty("user.dir") + File.separator + "webroot" +File.separator+"com"+File.separator+"liuwei"+File.separator+"ServletServer1"; 11 }
7.HttpServer1类:核心容器
1 package com.liuwei.ServletServer1; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 import java.net.InetAddress; 8 import java.net.ServerSocket; 9 import java.net.Socket; 10 11 public class HttpServer1 { 12 13 //shutdown command 14 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 15 16 //the shutdown command received 17 private static boolean shutdown = false; 18 19 public static void main(String[] args) { 20 HttpServer1 server = new HttpServer1(); 21 server.await(); 22 } 23 //服务器启动方法 24 public void await(){ 25 ServerSocket serverSocket = null; 26 int port = 8888; 27 try{ 28 serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1")); 29 }catch(IOException ex){ 30 ex.printStackTrace(); 31 System.exit(1); 32 } 33 //此处判断shutdown标志 34 while(!shutdown){ 35 Socket socket = null; 36 InputStream input = null; 37 OutputStream output = null; 38 try{ 39 //阻塞式等待一个请求,并返回请求端的socket对象 40 socket = serverSocket.accept(); 41 //获取socket对象中的输入输出流对象 42 input = socket.getInputStream(); 43 output = socket.getOutputStream(); 44 45 //create request object and parse 46 Request request = new Request(input); 47 request.parse(); 48 System.out.println(request.getUri()); 49 50 //create response object 51 Response response = new Response(output); 52 response.setRequest(request); 53 //response.sendStaticResource(); 54 55 //check if request is a servletRequest or a staticResourceRequest 56 //简单的http请求分发,分为servlet处理器和静态资源处理器 57 if(request.getUri().startsWith("/servlet/")){ 58 ServletProcessor1 processor = new ServletProcessor1(); 59 processor.process(request,response); 60 }else{ 61 StaticResourceProcessor processor = new StaticResourceProcessor(); 62 processor.process(request,response); 63 } 64 //socket close 65 socket.close(); 66 67 //check if requesturl is shutdown command 68 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 69 }catch(Exception e){ 70 e.printStackTrace(); 71 continue; 72 } 73 } 74 } 75 }
六.工作流程
如下图所示:
此时,02节实现的servlet容器仍然负责一系列核心流程,包括:程序入口,服务创建,请求-响应对象创建,处理类分发。
注意处理类分发是02节的servlet容器所多出来的部分,由于除了静态资源的处理,还要对servlet请求处理,容器按照不同的请求uri,调用了不同的处理类。
此时对比01节,将如下两部分解耦,服务器可访问常量保存在了特定类Constants,处理逻辑分别形成了2个类StaticResourceProcessor和ServletProcessor。是功能的第一次拆分解耦。
此处注意ServletProcessor类,它在第8步会自动根据请求uri加载对应的servlet类,并调用其service方法。如果我们把02这样一个简单的servlet容器提供出来,那么编程人员就可以编写不同的servlet类来实现自己的功能,返回对应的结果。这就是tomcat服务器的雏形。