Request類
Request類代表一個HTTP請求。Socket處理用戶端的通訊,將返回一個InputStream對象,通過傳遞該對象,可以構造一個Request類的執行個體。通過調用InputStream 對象的read方法來獲得這個HTTP請求的未經處理資料(raw data)。
Request 有兩個公用方法:parse 和 getUri。parse方法解釋HTTP請求的未經處理資料。它不做很多事情----它能夠利用的唯一資訊只是HTTP請求的URI ,這個URI是從私人方法 parseUri.得到的。parseUri 方法儲存URI 到uri 變數中,然後調用公用方法getUri來返回一個HTTP請求的URI。
為了理解parse 和 parseUri 方法是如何工作的,需要知道HTTP請求的內部結構。這個結構是在RFC2616文檔中定義的。
一個HTTP請求包含三個部分:
- 請求行(Request line)
- 請求包頭(Headers)
- 訊息體(Message body)
現在,我們僅僅只對HTTP請求的第一部分請求行(Request line)感興趣。一個請求行由方法標記開始,後面根請求的URI和協議版本,最後由CRLF字元結束。請求行中的元素被空白字元分開。比如,使用GET方法請求的index.html檔案的請求行如下:
GET /index.html HTTP/1.1 //這是一個請求行
方法parse從socket的InputStream 中讀取整個位元組流,該位元組流是 Request 對象傳遞進來的,然後parse將這些位元組流儲存在一個緩衝區裡, 在緩衝區中組裝一個稱為request的StringBuffer對象。
下面的Listing 1.2.顯示了parse方法的用法:
Listing 1.2. The Request class' parse method
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());}
parseUri 方法從請求行那裡得到URI。Listing 1.3 展示了parseUri 方法的用途。 parseUri 減縮請求中的第一個和第二個空格來獲得URI。
Listing 1.3. The Request class' parseUri method
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;}
Response類
Response表示一個HTTP響應。它的建構函式接受一個OutputStream對象,比如下面的:
public Response(OutputStream output) { this.output = output;}
Response 對象被HttpServer類的await方法構造,該方法被傳遞的參數是從socket那裡得到的OutputStream對象。
Response類有兩個公用方法: setRequest和sendStaticResource. setRequest方法傳遞一個Request對象給Response對象。Listing 1.4中的代碼顯示了這個:
Listing 1.4. The Response class' setRequest method
public void setRequest(Request request) { this.request = request;}
sendStaticResource 方法用來發送一個靜態資源,比如HTML檔案。Listing 1.5給出了它的實現過程:
Listing 1.5. The Response class' sendStaticResource method
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(); }}
sendStaticResource 方法是非常簡單的。它首先傳遞父路徑和子路徑給File類的構造器,從而對java.io.File類進行了執行個體化。
File file = new File(HttpServer.WEB_ROOT, request.getUri());
然後它檢查檔案是否存在。如果存在,sendStaticResource 方法通過傳遞File對象來構造一個java.io.FileInputStream對象。然後調用FileInputStream 的read方法,將位元組流寫如到OutputStream輸出。注意這種情況下, 靜態資源的內容也被作為未經處理資料被發送給了瀏覽器。
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); }}
如果這個檔案不存在,sendStaticResource 方法發送一個錯誤訊息給瀏覽器。
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());
編譯和運行應用程式
為了編譯和運行應用,你首先需要解壓包含本文應用程式的.zip檔案。你解壓的目錄成為工作目錄(working directory),它有三個子目錄: src/, classes/, 和 lib/。 要編譯應用程式需要在工作目錄輸入如下語句:
javac -d . src/ex01/pyrmont/*.java
這個-d 選項參數將結果寫到目前的目錄,而不是src/ 目錄。
要運行應用程式,在工作目錄中輸入如下語句:
java ex01.pyrmont.HttpServer
要測試你的應用程式,開啟瀏覽器,在地址欄中輸入如下URL:
http://localhost:8080/index.html
你將可以看到瀏覽器中顯示的index.html 頁面,如Figure 1所示。
Figure 1. The output from the web server
在控制台(Console),你能看到如下內容:
GET /index.html HTTP/1.1Accept: */*Accept-Language: en-usAccept-Encoding: gzip, deflateUser-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Host: localhost:8080Connection: Keep-AliveGET /images/logo.gif HTTP/1.1Accept: */*Referer: http://localhost:8080/index.htmlAccept-Language: en-usAccept-Encoding: gzip, deflateUser-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Host: localhost:8080Connection: Keep-Alive
概要總結
在本文中,你瞭解了一個簡單的WEB伺服器的工作機制。本文附帶的應用程式原始碼只包含三個類,但並不是所有的都有用。儘管如此,它還是能被作為一種很好的學習工具為我們服務。
Translated by Willpower,2003.11.24