Tomcat 源码解析 (二)自己写服务器
首先原谅我上次《How Tomcat Works 1》的粗制滥造, 这次给必要的代码都给上必要注释。
第二章是说明简单的servlet容器是如何工作的。这一章带有2个servlet容器应用,可以处
理静态资源和简单的servlet请求。尤其是你将会学到如何创建request和response对象,然
后把它们传递给被请求的servlet的service方法。在servlet容器里边还有一个servlet,你
可以从一个web浏览器中调用它。
一个基于java Web的服务器, 两个重中之重的类便是java.net.Socket 和 java.net.ServerSocket,
Socket即套接字,为了发送数据到另一台机器, 首先要知道那台机器的IP及套接字端口。
有更深入兴趣的可以继续深入了解Socket用法。
上面的Socket代表客户端套接字,那么同样的就应该有服务端的套接字:ServerSocket
我们这边使用的构造方法是 public ServerSocket(int port, int backLog, InetAddress bindingAddress);
port就是端口, backLog为输入连接指示(对连接的请求)的最大队列长度,我们这里设置为1,bindingAddress便是绑定地址,我们这里通过InetAddress.getByName("127.0.0.1")来获取。
另外我们还需要了解一下HTTP协议的基本概念:
一个HTTP请求包括三个组成部分:
方法—统一资源标识符(URI)—协议/版本
请求的头部
主体内容
下面是给一个栗子:
POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
lastName=Franks&firstName=Michael
其中第一句包含了 method(POST), uri(/examples/default.jsp) 和version(HTTP/1.1)
然后最后一句便是请求主题(Body)
没错, 中间那长长的一坨就是http请求的头部(实际上请求主题可以很长,这边我们简化了)
更详细的可以看这篇文章 http 协议详解
我们要知道,servlet实现服务基本上要有3件事情要做:
1:Servlet;
通过service()方法接受和处理request, response
对于此, 我们创建类HttpServer:
1 package chap1_ASimpleWebServer; 2 import java.io.File; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 import java.net.InetAddress; 7 import java.net.ServerSocket; 8 import java.net.Socket; 9 10 11 public class HttpServer { 12 // getProperty("user.dir")就是返回当前目录, File.separator就是'\'; 13 // 以博主机子为例, WEB_ROOT的内容就是"D:/workspace/howTomcatWorks/chap1_ASimpleWebServer/webroot" 14 public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; 15 16 // 如果要关闭服务器, 则在浏览器地址栏中输入 http://localhost:8080/SHUTDOWN 17 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 18 19 private boolean shutdown = false; 20 21 // 类似Severlet.service(), 现在只是简单的了解下概念: 22 public void await() { 23 ServerSocket serverSocket = null; 24 int port = 8080; 25 try { 26 // port就是端口, backLog为输入连接指示(对连接的请求)的最大队列长度,我们这里设置为1, 27 // bindingAddress便是绑定地址,我们这里通过InetAddress.getByName("127.0.0.1")来获取。 28 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 System.exit(1); 32 } 33 // loop waiting for a request 34 while (!shutdown) { 35 Socket socket = null; 36 InputStream input = null; 37 OutputStream output = null; 38 try { 39 // 先是通过ServerSocket实例 得到 Socket实例 40 socket = serverSocket.accept(); 41 // 再通过Socket实例来获取InputStream和OutputStream的实例 42 input = socket.getInputStream(); 43 output = socket.getOutputStream(); 44 // create Request object & parse 45 // 通过input创建request 46 Request request = new Request(input); 47 request.parse(); 48 // create Response object 49 // 通过request创建response 50 Response response = new Response(output); 51 response.setRequest(request); 52 response.sendResource(); 53 // close socket 54 socket.close(); 55 // check if the previous URI is a shutdown command 56 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 57 } catch (IOException e) { 58 e.printStackTrace(); 59 continue; 60 } 61 } 62 } 63 64 public static void main(String[] args) { 65 System.out.println(WEB_ROOT); 66 HttpServer server = new HttpServer(); 67 server.await(); 68 } 69 }
2:Request;
包含http头等信息, 是javax.Servlet.http.ServletRequest 接口的实例
1 package chap1_ASimpleWebServer; 2 import java.io.IOException; 3 import java.io.InputStream; 4 5 /* 6 * 在这一章中,我们仅仅关注HTTP请求的第一部分,请求行。请求行从 7 * 一个方法标记开始,接下去是请求的URI和协议版本,最后是用回车换 8 * 行符(CRLF)结束。请求行里边的元素是通过一个空格来分隔的。例如, 9 * 使用GET方法来请求index.html文件的请求行如下所示: 10 * 11 * GET /index.html HTTP/1.1 12 */ 13 public class Request { 14 private InputStream input; 15 private String uri; 16 17 public Request (InputStream input) { 18 this.input = input; 19 } 20 21 public void parse() { 22 // 新建StringBuffer request, 用于接收字节转化的字符 23 StringBuffer request = new StringBuffer(2048); 24 int len; 25 // 新建字节数组buffer, 用于接收套接字的InputStream字节流 26 byte[] buffer = new byte[2048]; 27 try { 28 len = input.read(buffer); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 len = -1; 32 } 33 for (int i = 0; i < len; i++) { 34 // 这里不要忘了强制转型(byte -> char) 35 request.append((char)buffer[i]); 36 } 37 System.out.println(request.toString()); 38 // 得到URI的值 39 uri = parseUri(request.toString()); 40 } 41 42 // 从HTTP请求行里截取URI, 即 "GET /index.html HTTP/1.1" 中的 "/index.html" 43 private String parseUri(String requestString) { 44 int index1, index2; 45 index1 = requestString.indexOf(' '); 46 if (index1 != -1) { 47 index2 = requestString.indexOf(' ', index1 + 1); 48 if (index2 > index1) { 49 return requestString.substring(index1 + 1, index2); 50 } 51 } 52 return null; 53 } 54 55 // 返回URI 56 public String getUri() { 57 return uri; 58 } 59 }
3:Response
通过service()处理赋值后,生成对于客户的响应,对应于Request,, 它是javax.Servlet.http.ServletResponse接口的实例
1 package chap1_ASimpleWebServer; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 import java.io.OutputStream; 6 7 8 public class Response { 9 // 设置默认响应信息长度 10 private static final int BUFFER_SIZE = 1024; 11 Request request; 12 OutputStream output; 13 14 public Response (OutputStream output) { 15 this.output = output; 16 } 17 18 public void setRequest(Request request) { 19 this.request = request; 20 } 21 22 // 用来发送一个静态资源,例如一个HTML文件。它首先通过传递上一级 23 // 目录的路径和子路径给File累的构造方法来实例化java.io.File类 24 public void sendResource() throws IOException { 25 // 创建byte数组bytes用于接收文件流数据 26 byte[] bytes = new byte[BUFFER_SIZE]; 27 FileInputStream fis = null; 28 try { 29 // 根据当前目录和URI获取文件 30 File file = new File(HttpServer.WEB_ROOT, request.getUri()); 31 if (file.exists()) { 32 fis = new FileInputStream(file); 33 int len = fis.read(bytes, 0, BUFFER_SIZE); 34 while (len != -1) { 35 // 写到output流中 36 output.write(bytes, 0, len); 37 len = fis.read(bytes, 0, BUFFER_SIZE); 38 } 39 } else { 40 // 如果文件不存在,页面提示File Not Found信息(404) 41 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 42 "ContentType: text/html\r\n" + 43 "ContentLength: 23\r\n\r\n" + 44 "<head>File Not Found</head>"; 45 output.write(errorMessage.getBytes()); 46 } 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } finally { 50 // 最后别忘记关闭文件流 51 if (fis != null) { 52 fis.close(); 53 } 54 } 55 } 56 }
现在在项目文件夹下建立一个webroot文件夹:
先运行主程序HttpServer, 然后打开浏览器,输入地址:http://localhost:8080/index.html:
另外, 控制台结果显示了HTTP信息:
到此为止, 我们已经简单的了解到了一个简单web服务器是如何工作的。这一次仅仅由三个类组
成,所以并不是功能是十分局限的。下一次是真的要将要讨论动态内容的处理过程了。。。。。。。