Introduce Basically there are three things that a servlet container does to service a request for a servlet:
创建 request 对象并塞入后续要用到的信息,比如 parameters,cookies 等。request 是 javax.servlet(.http).ServletRequest 的一个实现
创建一个 response 对象返回给 web client. response 是 javax.servlet(.http).ServletResponse 的一个实现
调用 service 方法,service 中接受 request 的参数,处理结果塞入 response 中
Catalina Block Diagram Catalina 很复杂,但是他的设计很优雅,采用模块化的思想。主要可以分为两部分 Connector 和 Container,关系如下
connector 主要作用是构建 request/response 并传递给 container 处理,这里只是简化的模型。container 除了处理 request 还有很多东西需要做,比如加载 servlet,更新 session 等。
A Simple Web Server
Chapter1 starts this book by presenting a simple HTTP server. To build a working HTTP server, you need to know the internal workings of two classes in the java.net package: Socket and ServerSocket. There is sufficient background information in this chapter about these two classes for you to understand how the accompanying application works.
第一个练习的目标,创建一个简单的 web server. 服务启动后,浏览器输入地址,server 返回请求的静态资源.
逻辑层面上来说模型可以像下面这样展示,但是代码层面上却不行。
按照上面的图示,难道 client 是直接 new 一个 request 和 web server 进行交互吗?难道 web server 会 new 一个 response 发送给 client 吗? 非也。模型画成下面的样子应该更合适
Client 和 Web Server 之间通过 socket 进行交互。在 server 内部,会将 socket 分化为 input 和 output 两个 IO 流,分别对应读取 Client 发送的数据和发送给 Client 相应
项目目录设置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 how-tomcat-works ├── ex01 │ ├── pom.xml │ └── src │ └── ex01 │ ├── HttpServer.java │ ├── Request.java │ └── Response.java ├── pom.xml └── webroot ├── images │ └── logo.gif └── index.html
HttpServer 表示服务器类,内部实现主要依赖于 ServerSocket 这个 java.net 包下的类,他在绑定本地端口并死循环等待 Client 端的 socket 访问
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 public class HttpServer { public static final String WEB_ROOT = System.getProperty("user.dir" ) + File.separator + "webroot" ; private static final String SHUTDOWN_COMMAND = "/SHUTDOWN" ; private boolean shutdown = false ; public static void main (String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await () { ServerSocket serverSocket = null ; int port = 8080 ; serverSocket = new ServerSocket(port, 1 , InetAddress.getByName("127.0.0.1" )); while (!shutdown) { Socket socket = null ; InputStream input = null ; OutputStream output = null ; socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); Request request = new Request(input); request.parse(); Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); socket.close(); shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } } }
Request 类的主要职责是拿到 socket 的输入流,解析并提取 Client 发送过来的信息,这个例子中主要是提取请求的资源路径
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 public class Request { private InputStream input; private String uri; public Request (InputStream input) { this .input = input; } public void parse () { StringBuilder request = new StringBuilder(2048 ); int i; byte [] buffer = new byte [2048 ]; i = input.read(buffer); for (int j = 0 ; j < i; j++) { request.append((char ) buffer[j]); } System.out.print(request); uri = parseUri(request.toString()); } 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 ; } public String getUri () { return uri; } }
Response 负责向 Client 返回信息
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 Response { private static final int BUFFER_SIZE = 1024 ; Request request; OutputStream output; 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(HttpServer.WEB_ROOT, request.getUri()); if (file.exists()) { String header = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "\r\n" ; output.write(header.getBytes()); 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); } } else { 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()); } } catch (Exception e) { System.out.println(e.toString()); } finally { if (fis != null ) { fis.close(); } } } }
结合计算机网络的知识做下分层的整理:Tomcat 处理的问题属于 Application 层,HTTP 规范是在处理 socket 的时候体现的. 那么写 reponse 的时候,需要特殊指定 HTTP 版本,header 等信息,这些参数都是服务器端指定的。