how tomcat works 简单的server
让我们从一个servlet容器的角度来研究一下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的init方法和destroy方法。相反它做了下面的事情:
等待HTTP请求。
构造一个ServletRequest对象和一个ServletResponse对象。
假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。
假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
注意:在这个servlet容器中,每一次servlet被请求的时候,servlet类都会被加载。 第一个应用程序由6个类组成:
HttpServer1
Request
Response
StaticResourceProcessor
ServletProcessor1
Constants
HttpServer1类:
1 package ex02.pyrmont; 2 3 import java.net.Socket; 4 import java.net.ServerSocket; 5 import java.net.InetAddress; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.io.IOException; 9 10 public class HttpServer1 { 11 12 /** WEB_ROOT is the directory where our HTML and other files reside. 13 * For this package, WEB_ROOT is the "webroot" directory under the working 14 * directory. 15 * The working directory is the location in the file system 16 * from where the java command was invoked. 17 */ 18 // shutdown command 19 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 20 21 // the shutdown command received 22 private boolean shutdown = false; 23 24 public static void main(String[] args) { 25 HttpServer1 server = new HttpServer1(); 26 server.await(); 27 } 28 29 public void await() { 30 ServerSocket serverSocket = null; 31 int port = 8080; 32 try { 33 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 34 } 35 catch (IOException e) { 36 e.printStackTrace(); 37 System.exit(1); 38 } 39 40 // Loop waiting for a request 41 while (!shutdown) { 42 Socket socket = null; 43 InputStream input = null; 44 OutputStream output = null; 45 try { 46 socket = serverSocket.accept(); 47 input = socket.getInputStream(); 48 output = socket.getOutputStream(); 49 50 // create Request object and parse 51 Request request = new Request(input); 52 request.parse(); 53 54 // create Response object 55 Response response = new Response(output); 56 response.setRequest(request); 57 58 // check if this is a request for a servlet or a static resource 59 // a request for a servlet begins with "/servlet/" 60 if (request.getUri().startsWith("/servlet/")) { 61 ServletProcessor1 processor = new ServletProcessor1(); 62 processor.process(request, response); 63 } 64 else { 65 StaticResourceProcessor processor = new StaticResourceProcessor(); 66 processor.process(request, response); 67 } 68 69 // Close the socket 70 socket.close(); 71 //check if the previous URI is a shutdown command 72 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 73 } 74 catch (Exception e) { 75 e.printStackTrace(); 76 System.exit(1); 77 } 78 } 79 } 80 }
Request类:
1 package ex02.pyrmont; 2 3 import java.io.InputStream; 4 import java.io.IOException; 5 import java.io.BufferedReader; 6 import java.io.UnsupportedEncodingException; 7 import java.util.Enumeration; 8 import java.util.Locale; 9 import java.util.Map; 10 import javax.servlet.RequestDispatcher; 11 import javax.servlet.ServletInputStream; 12 import javax.servlet.ServletRequest; 13 14 15 public class Request implements ServletRequest { 16 17 private InputStream input; 18 private String uri; 19 20 public Request(InputStream input) { 21 this.input = input; 22 } 23 24 public String getUri() { 25 return uri; 26 } 27 28 private String parseUri(String requestString) { 29 int index1, index2; 30 index1 = requestString.indexOf(' '); 31 if (index1 != -1) { 32 index2 = requestString.indexOf(' ', index1 + 1); 33 if (index2 > index1) 34 return requestString.substring(index1 + 1, index2); 35 } 36 return null; 37 } 38 39 public void parse() { 40 // Read a set of characters from the socket 41 StringBuffer request = new StringBuffer(2048); 42 int i; 43 byte[] buffer = new byte[2048]; 44 try { 45 i = input.read(buffer); 46 } 47 catch (IOException e) { 48 e.printStackTrace(); 49 i = -1; 50 } 51 for (int j=0; j<i; j++) { 52 request.append((char) buffer[j]); 53 } 54 System.out.print(request.toString()); 55 uri = parseUri(request.toString()); 56 } 57 58 /* implementation of the ServletRequest*/ 59 public Object getAttribute(String attribute) { 60 return null; 61 } 62 63 public Enumeration getAttributeNames() { 64 return null; 65 } 66 67 public String getRealPath(String path) { 68 return null; 69 } 70 71 public RequestDispatcher getRequestDispatcher(String path) { 72 return null; 73 } 74 75 public boolean isSecure() { 76 return false; 77 } 78 79 public String getCharacterEncoding() { 80 return null; 81 } 82 83 public int getContentLength() { 84 return 0; 85 } 86 87 public String getContentType() { 88 return null; 89 } 90 91 public ServletInputStream getInputStream() throws IOException { 92 return null; 93 } 94 95 public Locale getLocale() { 96 return null; 97 } 98 99 public Enumeration getLocales() { 100 return null; 101 } 102 103 public String getParameter(String name) { 104 return null; 105 } 106 107 public Map getParameterMap() { 108 return null; 109 } 110 111 public Enumeration getParameterNames() { 112 return null; 113 } 114 115 public String[] getParameterValues(String parameter) { 116 return null; 117 } 118 119 public String getProtocol() { 120 return null; 121 } 122 123 public BufferedReader getReader() throws IOException { 124 return null; 125 } 126 127 public String getRemoteAddr() { 128 return null; 129 } 130 131 public String getRemoteHost() { 132 return null; 133 } 134 135 public String getScheme() { 136 return null; 137 } 138 139 public String getServerName() { 140 return null; 141 } 142 143 public int getServerPort() { 144 return 0; 145 } 146 147 public void removeAttribute(String attribute) { 148 } 149 150 public void setAttribute(String key, Object value) { 151 } 152 153 public void setCharacterEncoding(String encoding) 154 throws UnsupportedEncodingException { 155 } 156 157 }
Response类:
1 package ex02.pyrmont; 2 3 import java.io.OutputStream; 4 import java.io.IOException; 5 import java.io.FileInputStream; 6 import java.io.FileNotFoundException; 7 import java.io.File; 8 import java.io.PrintWriter; 9 import java.util.Locale; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.ServletOutputStream; 12 13 public class Response implements ServletResponse { 14 15 private static final int BUFFER_SIZE = 1024; 16 Request request; 17 OutputStream output; 18 PrintWriter writer; 19 20 public Response(OutputStream output) { 21 this.output = output; 22 } 23 24 public void setRequest(Request request) { 25 this.request = request; 26 } 27 28 /* This method is used to serve a static page */ 29 public void sendStaticResource() throws IOException { 30 byte[] bytes = new byte[BUFFER_SIZE]; 31 FileInputStream fis = null; 32 try { 33 /* request.getUri has been replaced by request.getRequestURI */ 34 File file = new File(Constants.WEB_ROOT, request.getUri()); 35 fis = new FileInputStream(file); 36 /* 37 HTTP Response = Status-Line 38 *(( general-header | response-header | entity-header ) CRLF) 39 CRLF 40 [ message-body ] 41 Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF 42 */ 43 int ch = fis.read(bytes, 0, BUFFER_SIZE); 44 while (ch!=-1) { 45 output.write(bytes, 0, ch); 46 ch = fis.read(bytes, 0, BUFFER_SIZE); 47 } 48 } 49 catch (FileNotFoundException e) { 50 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 51 "Content-Type: text/html\r\n" + 52 "Content-Length: 23\r\n" + 53 "\r\n" + 54 "<h1>File Not Found</h1>"; 55 output.write(errorMessage.getBytes()); 56 } 57 finally { 58 if (fis!=null) 59 fis.close(); 60 } 61 } 62 63 64 /** implementation of ServletResponse */ 65 public void flushBuffer() throws IOException { 66 } 67 68 public int getBufferSize() { 69 return 0; 70 } 71 72 public String getCharacterEncoding() { 73 return null; 74 } 75 76 public Locale getLocale() { 77 return null; 78 } 79 80 public ServletOutputStream getOutputStream() throws IOException { 81 return null; 82 } 83 84 public PrintWriter getWriter() throws IOException { 85 // autoflush is true, println() will flush, 86 // but print() will not. 87 writer = new PrintWriter(output, true); 88 return writer; 89 } 90 91 public boolean isCommitted() { 92 return false; 93 } 94 95 public void reset() { 96 } 97 98 public void resetBuffer() { 99 } 100 101 public void setBufferSize(int size) { 102 } 103 104 public void setContentLength(int length) { 105 } 106 107 public void setContentType(String type) { 108 } 109 110 public void setLocale(Locale locale) { 111 } 112 }
在getWriter方法中,PrintWriter类的构造方法的第二个参数是一个布尔值表明是否允许自动刷新。传递true作为第二个参数将会使任何println方法的调用都会刷新输出(output)。不过,print方法不会刷新输出。 因此,任何print方法的调用都会发生在servlet的service方法的最后一行,输出将不会被发送到浏览器。这个缺点将会在下一个应用程序中修复。
StaticResourceProcessor类:
1 package ex02.pyrmont; 2 3 import java.io.IOException; 4 5 public class StaticResourceProcessor { 6 7 public void process(Request request, Response response) { 8 try { 9 response.sendStaticResource(); 10 } 11 catch (IOException e) { 12 e.printStackTrace(); 13 } 14 } 15 }
ServletProcessor1类:
1 package ex02.pyrmont; 2 3 import java.net.URL; 4 import java.net.URLClassLoader; 5 import java.net.URLStreamHandler; 6 import java.io.File; 7 import java.io.IOException; 8 import javax.servlet.Servlet; 9 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 12 public class ServletProcessor1 { 13 14 public void process(Request request, Response response) { 15 16 String uri = request.getUri(); 17 String servletName = uri.substring(uri.lastIndexOf("/") + 1); 18 URLClassLoader loader = null; 19 20 try { 21 // create a URLClassLoader 22 URL[] urls = new URL[1]; 23 URLStreamHandler streamHandler = null; 24 File classPath = new File(Constants.WEB_ROOT); 25 // the forming of repository is taken from the createClassLoader method in 26 // org.apache.catalina.startup.ClassLoaderFactory 27 String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ; 28 // the code for forming the URL is taken from the addRepository method in 29 // org.apache.catalina.loader.StandardClassLoader class. 30 urls[0] = new URL(null, repository, streamHandler); 31 loader = new URLClassLoader(urls); 32 } 33 catch (IOException e) { 34 System.out.println(e.toString() ); 35 } 36 Class myClass = null; 37 try { 38 myClass = loader.loadClass(servletName); 39 } 40 catch (ClassNotFoundException e) { 41 System.out.println(e.toString()); 42 } 43 44 Servlet servlet = null; 45 46 try { 47 servlet = (Servlet) myClass.newInstance(); 48 servlet.service((ServletRequest) request, (ServletResponse) response); 49 } 50 catch (Exception e) { 51 System.out.println(e.toString()); 52 } 53 catch (Throwable e) { 54 System.out.println(e.toString()); 55 } 56 57 } 58 }
这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse方法。拥有一个Response实例,就可以调用sendStaticResource方法。 你不可以把parse和sendStaticResource方法设置为私有的,因为它们将会被其他的类调用。不过,这两个方法是在个servlet内部是不可见的。其中一个解决办法就是让Request和Response类拥有默认访问修饰,所以它们不能在ex02.pyrmont包的外部使用。不过,这里有一个更优雅的解决办法:通过使用facade类。请看Figure 2.2中的UML图。
在这第二个应用程序中,我们增加了两个façade类: RequestFacade和ResponseFacade。RequestFacade实现了ServletRequest接口并通过在构造方法中传递一个引用了ServletRequest对象的Request实例作为参数来实例化。ServletRequest接口中每个方法的实现都调用了Request对象的相应方法。然而ServletRequest对象本身是私有的,并不能在类的外部访问。我们构造了一个RequestFacade对象并把它传递给service方法,而不是向下转换Request对象为ServletRequest对象并传递给service方法。Servlet程序员仍然可以向下转换ServletRequest实例为RequestFacade,不过它们只可以访问ServletRequest接口里边的公共方法。现在parseUri方法就是安全的了。
请注意RequestFacade的构造方法。它接受一个Request对象并马上赋值给私有的servletRequest对象。还请注意,RequestFacade类的每个方法调用ServletRequest对象的相应的方法。
请注意RequestFacade的构造方法。它接受一个Request对象并马上赋值给私有的servletRequest对象。还请注意,RequestFacade类的每个方法调用ServletRequest对象的相应的方法。 这同样使用于ResponseFacade类。 这里是应用程序2中使用的类:
HttpServer2
Request
Response
StaticResourceProcessor
ServletProcessor2
Constants
HttpServer2类类似于HttpServer1,除了它在await方法中使用ServletProcessor2而不是ServletProcessor1:
1 if (request.getUri().startWith("/servlet/")) { 2 servletProcessor2 processor = new ServletProcessor2(); processor.process(request, response); 3 } else { 4 ... 5 }
ServletProcessor2类类似于ServletProcessor1,除了process方法中的以下部分:
1 Servlet servlet = null; 2 RequestFacade requestFacade = new RequestFacade(request); 3 ResponseFacade responseFacade = new ResponseFacade(response); 4 try { 5 servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) requestFacade,(ServletResponse)responseFacade); }
Listing 2.7 显示了一个不完整的RequestFacade类
Listing 2.7: RequestFacade类
1 package ex02.pyrmont; 2 3 import java.io.IOException; 4 import java.io.BufferedReader; 5 import java.io.UnsupportedEncodingException; 6 import java.util.Enumeration; 7 import java.util.Locale; 8 import java.util.Map; 9 import javax.servlet.RequestDispatcher; 10 import javax.servlet.ServletInputStream; 11 import javax.servlet.ServletRequest; 12 13 public class RequestFacade implements ServletRequest { 14 15 private ServletRequest request = null; 16 17 public RequestFacade(Request request) { 18 this.request = request; 19 } 20 21 /* implementation of the ServletRequest*/ 22 public Object getAttribute(String attribute) { 23 return request.getAttribute(attribute); 24 } 25 26 public Enumeration getAttributeNames() { 27 return request.getAttributeNames(); 28 } 29 30 public String getRealPath(String path) { 31 return request.getRealPath(path); 32 } 33 34 public RequestDispatcher getRequestDispatcher(String path) { 35 return request.getRequestDispatcher(path); 36 } 37 38 public boolean isSecure() { 39 return request.isSecure(); 40 } 41 42 public String getCharacterEncoding() { 43 return request.getCharacterEncoding(); 44 } 45 46 public int getContentLength() { 47 return request.getContentLength(); 48 } 49 50 public String getContentType() { 51 return request.getContentType(); 52 } 53 54 public ServletInputStream getInputStream() throws IOException { 55 return request.getInputStream(); 56 } 57 58 public Locale getLocale() { 59 return request.getLocale(); 60 } 61 62 public Enumeration getLocales() { 63 return request.getLocales(); 64 } 65 66 public String getParameter(String name) { 67 return request.getParameter(name); 68 } 69 70 public Map getParameterMap() { 71 return request.getParameterMap(); 72 } 73 74 public Enumeration getParameterNames() { 75 return request.getParameterNames(); 76 } 77 78 public String[] getParameterValues(String parameter) { 79 return request.getParameterValues(parameter); 80 } 81 82 public String getProtocol() { 83 return request.getProtocol(); 84 } 85 86 public BufferedReader getReader() throws IOException { 87 return request.getReader(); 88 } 89 90 public String getRemoteAddr() { 91 return request.getRemoteAddr(); 92 } 93 94 public String getRemoteHost() { 95 return request.getRemoteHost(); 96 } 97 98 public String getScheme() { 99 return request.getScheme(); 100 } 101 102 public String getServerName() { 103 return request.getServerName(); 104 } 105 106 public int getServerPort() { 107 return request.getServerPort(); 108 } 109 110 public void removeAttribute(String attribute) { 111 request.removeAttribute(attribute); 112 } 113 114 public void setAttribute(String key, Object value) { 115 request.setAttribute(key, value); 116 } 117 118 public void setCharacterEncoding(String encoding) 119 throws UnsupportedEncodingException { 120 request.setCharacterEncoding(encoding); 121 } 122 123 }