相信大多Web開發者對Tomcat是非常熟悉的,眾所周知Tomcat是一款非常好用的開源Servlet容器,您一定對這個最流行的Servlet容器充滿好奇,雖然它并不像一個黑盒子那樣讓人無法觸摸但是Tomcat的源碼的確讓人看起來頭疼。筆者就在這里和大家共同分析一個簡單的Web服務器是如何工作的源碼下載地址。
Web服務器
Web服務器是一個復雜的系統,一個Web服務器要為一個Servlet的請求提供服務,需要做三件事:
1、創建一個request對象并填充那些有可能被所引用的Servlet使用的信息,如參數、頭部、cookies、查詢字符串等等。一個request對象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例
2、創建一個response對象,所引用的servlet使用它來給客戶端發送響應。一個response對象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一個實例。
3、調用servlet的service方法,并傳入request和response對象。這里servlet會從request對象取值,給response寫值。
在正式展示代碼之前還需要了解一些必須額HTTP的知識(如果您對此非常熟悉您可以直接看下面分析代碼)
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》