这是《How Tomcat Works》第一二章的读书笔记。第一张主要写了一个静态资源处理的web服务器,第二章加了对servlet的处理。
概述 基本的架构
HttpServer:表示Http服务器,与客户端通信,处理Http请求。
StaticResourceProcessor:对静态资源请求进行处理。
ServletProcessor:对Servlet资源请求进行处理。
Request:表示Http请求,实现了ServletRequest接口。
Response:表示Http响应,实现了ServletResponse接口。
RequestFacade/ResponseFacade:Request/Response的门面类。
PrimitiveServlet:表示一个Servlet类。
处理流程
实现 一些方法 HttpServer处理逻辑:
1 2 3 4 5 6 7 8 9 10 11 if (request.getUri().startsWith("/servlet/" )) { ServletProcessor1 processor = new ServletProcessor1 (); processor.process(request, response); } else { StaticResoureProcessor processor = new StaticResourceProcessor (); processor.process(request, response); }
ServletProcessor中通过类加载器把.class文件动态的加载为Servlet对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public void process (Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/" ) + 1 ); URLClassLoader loader = null ; try { URL [] urls = new URL [1 ]; URLStreamHandler streamHandler = null ; File classPath = new File (Constants.WEB_ROOT); String repository = (new URL ("file" , null , classPath.getCanonicalPath() + File.separator)).toString() ; urls[0 ] = new URL (null , repository, streamHandler); loader = new URLClassLoader (urls); } catch (IOException e) { } Class myClass = null ; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { } Servlet servlet = null ; try { servlet = (Servlet) myClass.newInstance(); RequestFacade rquestFacade = new RequestFacade (request); ResponseFacade responseFacade = new RespondeFacade (response); servlet.service(requestFacade, responseFacade); } }
设计模式 在这里使用了门面的设计模式,目的是安全性。在StaticResourceProcessor.processor()方法中会把Request、Response对象传给Servelt进行处理,在后面程序猿处理Servlet请求时就可以把ServletRequest向下转型为Request对象,然后使用不该在此使用的方法,如:parse()方法。所以,在这里使用了门面设计模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ResponseFacade implements ServletResponse { private ServletResponse servletResponse = null ; public ResponseFacade (Response response) { this .servletResponse = response; } @Override public String getCharacterEncoding () { return servletResponse.getCharacterEncoding(); } @Override public String getContentType () { return servletResponse.getContentType(); }
注意点 HTTP协议 HTTP协议是应用层协议,它基于TCP/IP协议进行传输数据。在客户端与web服务器解析数据时,必须有相关的头部数据。所以web服务器给客户端发出响应,该响应必须加入头部数据。自己在写该代码时,就忘了加头部数据,出个bug,解决了一段时间才发现。PrimitiveServlet的service()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { String header = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html;charset=UTF-8\r\n" + "\r\n" ; String content = "<html>\r\n" + "<head>" + "</head>" + "<body>" + "Primitive Servlet" + "</body>" + "</html>" ; PrintWriter writer = res.getWriter(); writer.write(header); writer.write(content); writer.flush(); writer.close(); }
MIME类型 HTTP Respose header中有一个ContentType字段,它要求告诉客户端该Response正文的内容MIME类型,我们可以通过下面的API获得某一个文件的MIME类型。
1 String mimeType = URLConnection.getFileNameMap().getContentTypeFor(fileName)
Class文件 该系统使用到了URLClassLoader加载Class对象,ClassLoader把字节码(.class文件)加载为Class对象,所以需要编译好的.class文件。在Idea中Project中有个out目录,其中放的就是编译好的.class文件(只要你使用Idea运行过的.java文件)。
实现 与原书逻辑基本一致,由于自己加入了多线程技术,所以新增了Servlet容器(使用了Map),并且该容器一开始就加载所有的Servlet。
HttpServer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class HttpServer { public static final int PORT = 10086 ; public static final String STATIC_RESOURCE_ROOT = "E:/java/HttpServer/staticresource" ; public static final String SERVLET_ROOT = "E:/java/HttpServer/servlet" ; private ExecutorService executorService; public static final int THREAD_POOL_SIZE = 50 ; HttpServer() { executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); } public void start () { try (ServerSocket serverSocket = new ServerSocket (PORT)) { while (true ) { Socket socket = serverSocket.accept(); ServerHandle serverHandle = new ServerHandle (socket); executorService.execute(serverHandle); } } catch (IOException e) { System.out.println(e); } } }
Request和Response,省略部分方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 public class Request implements ServletRequest { private final InputStream inputStream; private String url; public Request (InputStream inputStream) { this .inputStream = inputStream; } public void parse () { BufferedInputStream in = new BufferedInputStream (inputStream); StringBuilder result = new StringBuilder (1024 ); byte [] buffer = new byte [1024 ]; int readNum = 0 ; try { readNum = in.read(buffer); } catch (IOException e) { e.printStackTrace(); } for (int i = 0 ; i < readNum; i++) { result.append((char ) buffer[i]); } int start = result.toString().indexOf(" " ) + 1 ; int end = result.toString().indexOf(" " , start); this .url = result.toString().substring(start, end); } } public class Response implements ServletResponse { private final Request request; private final OutputStream outputStream; public Response (Request request, OutputStream outputStream) { this .request = request; this .outputStream = outputStream; } public void sendStaticResource () throws IOException { BufferedOutputStream out = new BufferedOutputStream (outputStream); File file = new File (HttpServer.STATIC_RESOURCE_ROOT + request.getUrl()); if (file.exists()) { String mimeType = URLConnection.getFileNameMap().getContentTypeFor(file.getName()); String header = "HTTP/1.1 200 OK\r\n" + "Content-Length: " + file.length() + "\r\n" + "Content-Type: " + mimeType + "; charset=UTF-8\r\n" + "\r\n" ; out.write(header.getBytes()); out.flush(); byte [] buffer = new byte [1024 ]; try (BufferedInputStream in = new BufferedInputStream (new FileInputStream (file))) { for (int b = in.read(buffer); b != -1 ; b = in.read(buffer)) { out.write(buffer, 0 , b); out.flush(); } } catch (IOException e) { System.out.println(); } try { out.close(); } catch (IOException e) { System.out.println(); } return ; } sendNotFound(); } public void sendNotFound () { BufferedOutputStream out = new BufferedOutputStream (outputStream); String header = "HTTP/1.1 404 NOT FOUND\r\n" + "\r\n" ; String content = "<html>\r\n" + "<head>" + "</head>" + "<body>" + "File Not Found" + "</body>" + "</html>" ; try { out.write(header.getBytes()); out.write(content.getBytes()); out.flush(); } catch (IOException e) { System.out.println(e); } finally { try { out.close(); } catch (IOException e) { System.out.println(e); } } } }
StaticResourceProcessor,处理静态资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class StaticResourceProcessor { public void process (Request request, Response response) { try { response.sendStaticResource(); } catch (IOException e) { e.printStackTrace(); } } }
ServletProcessor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ServletProcessor { public void process (Request request, Response response) { String url = request.getUrl(); String servletName = url.substring(url.lastIndexOf("/" ) + 1 ); Servlet servlet = ServletContainer.container.get(servletName); if (servlet != null ) { RequestFacade requestFacade = new RequestFacade (request); ResponseFacade responseFacade = new ResponseFacade (response); try { servlet.service(requestFacade, responseFacade); } catch (ServletException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return ; } response.sendNotFound(); } }
ServletContainer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class ServletContainer { public static Map <String, Servlet> container = new HashMap <>(); @SuppressWarnings("unchecked") public void init () { URLClassLoader classLoader = null ; try { URL [] urls = new URL [1 ]; File classPath = new File (HttpServer.SERVLET_ROOT); String repository = (new URL ("file" , null , classPath.getCanonicalPath() + File.separator)).toString(); URLStreamHandler streamHandler = null ; urls[0 ] = new URL (null , repository, streamHandler); classLoader = new URLClassLoader (urls); } catch (IOException e) { System.out.println(); } File path = new File (HttpServer.SERVLET_ROOT); File [] files = path.listFiles(new FileFilter () { @Override public boolean accept (File pathname) { return pathname.toString().endsWith(".class" ); } }); for (File file : files) { String servletName = file.getName().substring(0 , file.getName().indexOf("." )); Class <Servlet> servletClass = null ; try { servletClass = (Class<Servlet>) classLoader.loadClass(servletName); } catch (ClassNotFoundException e) { e.printStackTrace(); } Servlet servlet = null ; try { servlet = servletClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } ServletContainer.container.put(servletName, servlet); } } }