深入學習Tomcat----自己動手寫伺服器(附伺服器原始碼)
相信大多Web開發者對Tomcat是非常熟悉的,眾所周知Tomcat是一款非常好用的開源Servlet容器,您一定對這個最流行的Servlet容器充滿好奇,雖然它並不像一個黑盒子那樣讓人無法觸控但是Tomcat的原始碼的確讓人看起來頭疼。筆者就在這裡和大家共同分析一個簡單的Web伺服器是如何工作的原始碼下載地址。
Web伺服器
Web伺服器是一個複雜的系統,一個Web伺服器要為一個Servlet的請求提供服務,需要做三件事:
1、建立一個request物件並填充那些有可能被所引用的Servlet使用的資訊,如引數、頭部、cookies、查詢字串等等。一個request物件是javax.servlet.ServletRequest
2、建立一個response物件,所引用的servlet使用它來給客戶端傳送響應。一個response物件是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest介面的一個例項。
3、呼叫servlet的service方法,並傳入request和response物件。這裡servlet會從request物件取值,給response寫值。
在正式展示程式碼之前還需要了解一些必須額HTTP的知識(如果您對此非常熟悉您可以直接看下面分析程式碼)
HTTP
HTTP
HTTP請求包括三部分
1、方法、統一資源識別符號(URI)、協議/版本
2、請求的頭部
3、主題內容
下面是一個HTTP請求的例子
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
第一行表明這是POST請求方法,/examples/default.jsp是URI,HTTP/1.1是協議以及版本。其中URI指明瞭一個網際網路資源,這裡通常是相對伺服器根目錄解釋的,也就是說這個HTTP請求就是告訴伺服器我需要這個檔案目錄如下:根目錄/ examples/default.jsp。
最後一行是HTTP的主題內容,Servlet會處理請求的主題內容,然後返回給客戶端HTTP響應。
類似於HTTP請求,一個HTTP響應也包括上面三個部分。
1、方法、統一資源識別符號(URI)、協議/版本
2、響應的頭部
3、主題內容
下面是一個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 to Brainy Software
</body>
</html>
第一行告訴協議版本,以及請求成功(200表示成功)
響應頭部和請求頭部一樣,一些有用的資訊。響應的主體就是響應本身HTML內容。
好了基本知識介紹完畢,下面開始解釋程式碼
部分相關程式碼
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
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;
try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
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);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
HttpServer類代表一個web伺服器。首先提供一個WEB_ROOT所在的目錄和它下面所有的子目錄下靜態資源。其次定義了一箇中止服務的命令,也就是說當得到的請求後面跟/shutdown的時候停止服務,預設是把服務設定為開啟。下面就是進入main函數了,首先例項化一個HttpServer類,然後就是通過await方法等待客戶端發來的請求。如果客戶端輸入的URL不是http://localhost:8080/SHUTDOWN則表示不停止伺服器,然後就是繼續執行await方法中的內容,在await方法中最重要的就是定義兩個物件,一個是request一個是response,下面就來說說Request和Response類。
import java.io.InputStream;
import java.io.IOException;
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
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;
}
}
首先呼叫InputStream物件中的read方法獲取HTTP請求的原始資料,然後在parseUri方法中獲得uri也就是要請求的靜態資源。說白了Request類的主要作用就是告訴伺服器使用者要的是什麼也就是在http://localhost:8080後面出現的東西。
import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
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 {
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();
}
}
}
Response類代表一個HTTP響應。首先Response接收一個OutputStream物件,然後通過sendStaticResource方法對接收的Request進行處理,整個處理過程就是根據請求在伺服器端進行尋找對應靜態資源的過程。找到所需要的資源後傳送給客戶端然後讓客戶端顯示出來。
執行程式
執行上面的HttpServer類,然後在瀏覽器的位址列中鍵入下面的地址:http:localhost:8080/index.jsp,然後你會在瀏覽器中看到index.jsp頁面。
在控制檯可以看到類似於下面的HTTP請求
GET /index.jsp HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7 360EE
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
小結
上面自己動手寫的這個所謂的伺服器僅僅有三個類組成,從功能上來說他只能顯示一些靜態的資源,並不是全部功能。一個優秀的伺服器還有很多細節要做,但是出於學習的目的大家現在有這些瞭解就足夠了,後面還會有對伺服器的詳細介紹,敬請期待。
參考資料《How Tomcat Works》