源码学习-Tomcat-01-一个简单的Web服务器
本文会介绍如何实现一个简单处理HTTP请求的Web服务器。
预备知识:
1. 简单了解HTTP协议的基本内容,及简单的请求、处理流程;
2. 会使用到Socket类、ServerSocket类;
3. HTTP, TCP, Socket 之间的区别与联系
1.
HTTP 的请求格式:
方法—统一资源标识符(URI)—协议/版本
请求的头部
主体内容
比如:
GET /index.html HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) QQBrowser/8.2.4258.400
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
name=shouhui
HTTP 的响应格式:
方法—相应状态码—相应状态码描述
响应的头部
主体内容
比如:
HTTP/1.1 200 OK
Server: Microsoft -IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT Content-Type: text/html
Last -Modified: Mon, 5 Jan 2004 13:13:12 GMT Content-Length: 112
<html>
<head>
<title>HTTP Response Example</title> </head>
<body>
Welcome !
</body>
</html >
2.
使用的Socket类方法:
InputStream java.net.Socket.getInputStream() throws IOException
得到servlet调用对象的输入字节流,用于从中取从socket连接另一端传递过来的字节流数据。
OutputStream java.net.Socket.getOutputStream() throws IOException
得到servlet调用对象的输出字节流,用于向socket连接的另一端传递过去字节流数据。
void java.net.Socket.close() throws IOException
关闭servlet调用对象,同时也会关闭该socket对应的InputStream, OutputStream对象(该方法被synchronized 修饰)
使用的ServerSocket类方法:
java.net.ServerSocket.ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException
ServerSocket类的一个构造方法:新建的ServerSocket对象监听bindAddr对应的IP地址,port对应的端口号,且请求的socket连接数最多为backlog个。
Socket java.net.ServerSocket.accept() throws IOException
一直处于阻塞状态监听socket连接请求,直到有建立连接请求到达,成功建立连接则返回对应的socket对象。
代码如下:
1 public class HttpServer { 2 3 /** WEB_ROOT is the directory where our HTML and other files reside. 4 * For this package, WEB_ROOT is the "webroot" directory under the working 5 * directory. 6 * The working directory is the location in the file system 7 * from where the java command was invoked. 8 */ 9 public static final String WEB_ROOT = 10 System.getProperty("user.dir") + File.separator + "webroot"; 11 12 // shutdown command 13 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 14 15 // the shutdown command received 16 private boolean shutdown = false; 17 18 public static void main(String[] args) { 19 HttpServer server = new HttpServer(); 20 server.await(); 21 } 22 23 public void await() { 24 ServerSocket serverSocket = null; 25 int port = 8080; 26 try { 27 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 28 } 29 catch (IOException e) { 30 e.printStackTrace(); 31 System.exit(1); 32 } 33 34 // Loop waiting for a request 35 while (!shutdown) { 36 Socket socket = null; 37 InputStream input = null; 38 OutputStream output = null; 39 try { 40 socket = serverSocket.accept(); 41 input = socket.getInputStream(); 42 output = socket.getOutputStream(); 43 44 // create Request object and parse 45 Request request = new Request(input); 46 request.parse(); 47 48 // create Response object 49 Response response = new Response(output); 50 response.setRequest(request); 51 response.sendStaticResource(); 52 53 // Close the socket 54 socket.close(); 55 56 //check if the previous URI is a shutdown command 57 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 58 } 59 catch (Exception e) { 60 e.printStackTrace(); 61 continue; 62 } 63 } 64 } 65 }
public class Request { private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } for (int j=0; j<i; j++) { request.append((char) buffer[j]); } System.out.print(request.toString()); 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; } }
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()) { 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 { // file not found 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) { // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if (fis!=null) fis.close(); } } }
参考:
Budi Kurniawan, Paul Deck. 深入剖析Tomcat (M). 北京: 机械工业出版社华章公司, 2011.