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 @Override20 public void destroy() {21 System.out.println("destroy");22 }23 24 @Override25 public ServletConfig getServletConfig() {26 return null;27 }28 29 @Override30 public String getServletInfo() {31 return null;32 }33 34 @Override35 public void init(ServletConfig servletConfig) throws ServletException {36 System.out.println("init");37 }38 //service方法,浏览器显示相关信息39 @Override40 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 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 EnumerationgetAttributeNames() { 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 @Override101 public String getLocalAddr() {102 // TODO Auto-generated method stub103 return null;104 }105 @Override106 public String getLocalName() {107 // TODO Auto-generated method stub108 return null;109 }110 @Override111 public int getLocalPort() {112 // TODO Auto-generated method stub113 return 0;114 }115 @Override116 public Locale getLocale() {117 // TODO Auto-generated method stub118 return null;119 }120 @Override121 public Enumeration getLocales() {122 // TODO Auto-generated method stub123 return null;124 }125 @Override126 public String getParameter(String arg0) {127 // TODO Auto-generated method stub128 return null;129 }130 @Override131 public Map getParameterMap() {132 // TODO Auto-generated method stub133 return null;134 }135 @Override136 public Enumeration getParameterNames() {137 // TODO Auto-generated method stub138 return null;139 }140 @Override141 public String[] getParameterValues(String arg0) {142 // TODO Auto-generated method stub143 return null;144 }145 @Override146 public String getProtocol() {147 // TODO Auto-generated method stub148 return null;149 }150 @Override151 public BufferedReader getReader() throws IOException {152 // TODO Auto-generated method stub153 return null;154 }155 @Override156 public String getRealPath(String arg0) {157 // TODO Auto-generated method stub158 return null;159 }160 @Override161 public String getRemoteAddr() {162 // TODO Auto-generated method stub163 return null;164 }165 @Override166 public String getRemoteHost() {167 // TODO Auto-generated method stub168 return null;169 }170 @Override171 public int getRemotePort() {172 // TODO Auto-generated method stub173 return 0;174 }175 @Override176 public RequestDispatcher getRequestDispatcher(String arg0) {177 // TODO Auto-generated method stub178 return null;179 }180 @Override181 public String getScheme() {182 // TODO Auto-generated method stub183 return null;184 }185 @Override186 public String getServerName() {187 // TODO Auto-generated method stub188 return null;189 }190 @Override191 public int getServerPort() {192 // TODO Auto-generated method stub193 return 0;194 }195 @Override196 public ServletContext getServletContext() {197 // TODO Auto-generated method stub198 return null;199 }200 @Override201 public boolean isAsyncStarted() {202 // TODO Auto-generated method stub203 return false;204 }205 @Override206 public boolean isAsyncSupported() {207 // TODO Auto-generated method stub208 return false;209 }210 @Override211 public boolean isSecure() {212 // TODO Auto-generated method stub213 return false;214 }215 @Override216 public void removeAttribute(String arg0) {217 // TODO Auto-generated method stub218 219 }220 @Override221 public void setAttribute(String arg0, Object arg1) {222 // TODO Auto-generated method stub223 224 }225 @Override226 public void setCharacterEncoding(String arg0)227 throws UnsupportedEncodingException {228 // TODO Auto-generated method stub229 230 }231 @Override232 public AsyncContext startAsync() {233 // TODO Auto-generated method stub234 return null;235 }236 @Override237 public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) {238 // TODO Auto-generated method stub239 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 "File Not Found
"; 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 @Override102 public void resetBuffer() {103 // TODO Auto-generated method stub104 105 }106 @Override107 public void setBufferSize(int arg0) {108 // TODO Auto-generated method stub109 110 }111 @Override112 public void setCharacterEncoding(String arg0) {113 // TODO Auto-generated method stub114 115 }116 @Override117 public void setContentLength(int arg0) {118 // TODO Auto-generated method stub119 120 }121 @Override122 public void setContentType(String arg0) {123 // TODO Auto-generated method stub124 125 }126 @Override127 public void setLocale(Locale arg0) {128 // TODO Auto-generated method stub129 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 DELL17 *18 */19 public class ServletProcessor1 {20 //分析request中的uri,并用类加载器加载21 public void process(Request request, Response response){22 //解析uri中的servletName23 String uri = request.getUri();24 String servletName = "com.liuwei.ServletServer1."+uri.substring(uri.lastIndexOf('/')+1);25 URLClassLoader loader = null;26 try{27 //**创建类载入器loader28 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 command14 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";15 16 //the shutdown command received17 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 parse46 Request request = new Request(input);47 request.parse();48 System.out.println(request.getUri());49 50 //create response object51 Response response = new Response(output);52 response.setRequest(request);53 //response.sendStaticResource();54 55 //check if request is a servletRequest or a staticResourceRequest56 //简单的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 close65 socket.close();66 67 //check if requesturl is shutdown command68 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服务器的雏形。