如何設計簡單的 WebServer?

程式作品

C 語言

Java

C#

JavaScript

常用函數

文字處理

遊戲程式

衛星定位

系統程式

資料結構

網路程式

自然語言

人工智慧

機率統計

資訊安全

等待完成

訊息

相關網站

參考文獻

最新修改

簡體版

English

程式專案下載:webserver.zip

Web Server (網站伺服器) 是 WWW 網路的基礎, 1991 年 Tim Burner Lee 發明 HTML 與 URL 後,就自己寫了第一個
Web Server,導至後來 Web 網路的興起,因此、Tim Burner Lee被尊稱為 WWW 之父,然而、對於一般程式設計人員而言,
Web Server 的設計方法卻是個難解的謎,本篇文章將以一個簡單而完整的 Java 程式,讓大家了解 Web Server 的設計原理。

簡介

一個最簡單的 Web Server 之功能包含下列三個步驟:

  • 步驟一 : 接收瀏覽器所傳來的網址。
  • 步驟二 : 取出相對應的檔案。
  • 步驟三 : 將檔案內容傳回給瀏覽器。

然而、在這個接收與傳回的過程中,所有的資訊都必須遵照固定的格式,規範這個接收/傳送格式的協定,稱為超文字傳送協定
(Hyper Text Transfer Protocol),簡稱為 HTTP 協定。

HTTP 協定格式的基礎,乃是建構在網址 URL 上的傳輸方式,早期只能用來傳送簡單的 HTML 檔案,後來經擴充後也可以傳送
其他類型的檔案,包含 影像、動畫、簡報、Word 文件等。

在本文中,我們將先簡介 HTTP 協定的訊息內容,然後在介紹如何以 Java 語言實作 HTTP 協定,以建立一個簡單的 Web Server。

HTTP 協定

當你在瀏覽器上打上網址(URL)後,瀏覽器會傳出一個HTTP訊息給對應的 Web Server,Web Server 再接收到這個訊息後,
根據網址取出對應的檔案,並將該檔案以 HTTP 格式的訊息傳回給瀏覽器,以下是這個過程的一個範例。

豬小弟上網,在瀏覽器中打上 http://ccc.kmit.edu.tw/index.htm,於是、瀏覽器傳送下列訊息給 ccc.kmit.edu.tw 這台電腦。

GET /index.htm HTTP/1.0
Accept: image/gif, image/jpeg, application/msword, */*
Accept-Language: zh-tw
User-Agent: Mozilla/4.0
Content-Length: 
Host: ccc.kmit.edu.tw
Cache-Control: max-age=259200
Connection: keep-alive

當 ccc.kmit.edu.tw 電腦上的 Web Server 程式收到上述訊息後,會取出指定的路徑 /index.htm ,然後根據預設的網頁根目錄
(假設為 c:\web\),合成一個 c:\web\index.htm 的絕對路徑,接著從磁碟機中取出該檔案,並傳回下列訊息給豬小弟的瀏覽器。

HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 438
<html>
  ....
</html>

其中第一行 HTTP/1.0 200 OK 代表該網頁被成功傳回,第二行 Content-Type: text/html 代表傳回文件為 HTML 檔案,
Content-Length: 438 代表該 HTML 檔案的大小為 438 位元組。

以 Java 實作 Web Server

根據 HTTP 協定,我們以 Java 實作了一個 Web Server 程式 (WebServer.java),該程式是利用一個稱為 ServerSocket
的物件來實作的,這個物件位於 Java 的網路函式庫 java.net 中。

ServerSocket 是根據博克萊 (U.C.Berkley) 大學早期發展的 Socket 概念寫成的,其設計理念是是將網路傳輸類比成
檔案讀取與寫入 (傳送的動作被視為是寫入/接收的動作被視為是讀取),如此、傳送與接收就簡化為程式人員比較容易懂的
讀取與寫入,降低了網路程式的學習困難度。

要使用 Socket 的方式寫網路程式,首先要登記網路的埠號 (port),將該 port 占領下來,以防止其他程式使用該 port 而互相干擾,
HTTP 協定所預設使用的是 port 80 ,以下即為 WebServer.java 程式中用來建立 ServerSocket 物件並占領 port 的程式碼。

    server = new ServerSocket(pPort);       // 建立 ServerSocket 物件並佔領 port (預設為 80).

一但完成登記,就可以開始接受瀏覽器的請求,並根據請求回傳檔案訊息,以下程式為其 (接收/傳送) 程序的核心程式。

  public void run() {
    try {
      Socket socket = server.accept();      // 等待直到接受到瀏覽器的連線訊息,然後建立連線。
      Thread service = new Thread(this);    // 建立一個新的 WebServer 執行緒。
      service.start();                      // 啟動它以以便處理下一個請求。
      DataOutputStream out = new DataOutputStream(socket.getOutputStream()); // 取得傳送資料的輸出檔。
      DataInputStream in = new DataInputStream(socket.getInputStream());     // 取得接收資料的輸入檔。
      String request = request(in);         // 讀取瀏覽器傳來的請求訊息。
      response(request, out);               // 回應訊息給對方的瀏覽器。
      socket.close();                       // 關閉該連線。
    } catch (Exception e) { e.printStackTrace(); }
  }

上述的 run() 函數中,request(in) 的部份是用來接收 HTTP 訊息的程式,其最簡單版本如下:

  String request(DataInputStream in) {
    String request = "";
    try {
      // 讀取到第一個空白行為止,這是標頭訊息。
      while (true) {
        String line = in.readLine();
        if (line == null) break;
        request += line+"\r\n";
        if (line.length() == 0) break;
      }
      System.out.println(request);
    } catch (Exception e) {}
    return request;
  }

這個最簡單版以 Socket 的方式,不斷讀取資料直到發現有一空白行即結束,然而、這樣的程式是過度簡化的結果,
無法處理有 POST 訊息的狀況,然而、何謂 POST 訊息呢 ?

所謂 POST 訊息、乃是 HTML 為了傳遞較大量的填表資料,所發展出來的一種訊息格式,以下是POST訊息的一個範例:

POST /getMsg.asp HTTP/1.0
Accept: image/gif, image/jpeg, application/msword, */*
Accept-Language: zh-tw
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0
Content-Length: 66
Host: ccc.kmit.edu.tw
Cache-Control: max-age=259200
Connection: keep-alive

user=ccc&password=1234&msg=Hello+%21+%0D%0AHow+are+you+%21%0D%0A++

其中的HTTP的訊息開頭以 POST 取代原來的 GET ,並且多了一個 Content-Length:66 的欄位,該欄位指示了
訊息結尾會有 66 個位元組的填表資料,這些資料會被編碼成特輸的格式以利在網路上傳遞。

為了處理 POST 訊息,上述的 request() 函數必需被擴充,以下是其擴充後的程式碼。

  String request(DataInputStream in) {
    String request = "";
    try {
      // 讀取到第一個空白行為止,這是標頭訊息。
      while (true) {
        String line = in.readLine();
        if (line == null) break;
        request += line+"\r\n";
        if (line.length() == 0) break;
      }
      // 根據 Content-Length: ,讀取到第一個空白行後面的 POST 表單訊息。
      String lengthStr = Text.innerText(request.toLowerCase(), "content-length:", "\r\n");
      if (lengthStr != null) {
           int contentLength = Integer.parseInt(lengthStr.trim());
          byte[] bytes = new byte[contentLength];
        in.read(bytes);
        String post = new String(bytes);
        request += post;
      }
      System.out.println(request);
    } catch (Exception e) {}
    return request;
  }

一但取得了瀏覽器傳來的 GET 或 POST 訊息後,我們就可以根據其訊息,決定回應的方式,在 WebServer.java 中,
我們只是單純的將對應的檔案取出,並附在回應的訊息表頭後傳回,其程式碼如下。

  void response(String request, DataOutputStream out) throws Exception {
    System.out.println("========response()==========");
    try {
      String path = innerText(request, "GET ", "HTTP/").trim(); // 取得檔案路徑 : GET 版。
      path = java.net.URLDecoder.decode(path, "UTF8");      // 將檔案路徑解碼以 Unicode 方式解碼。
      String fullPath = root+path;                           // 將路徑轉為絕對路徑。
      if (fullPath.indexOf(".")<0) fullPath += "/index.htm";
      output(out, "HTTP/1.0 200 OK");                       // 傳回成功訊息。
      output(out, "Content-Type: "+type(fullPath));          // 傳回檔案類型。
      File file = new File(fullPath);                        // 開啟檔案。
      output(out, "Content-Length: " + file.length());       // 傳回內容長度。
      output(out, "");                                      // 空行代表標頭段結束。
      // 以區塊為單位回傳檔案。
      byte[] buffer = new byte[4096];
      FileInputStream is = new FileInputStream(fullPath);
      while (true) {
        int len = is.read(buffer);
        out.write(buffer, 0, len);
        if (len < 4096) break;
      }
    } catch (Exception e) { // 有錯,傳回錯誤訊息。
      output(out, "HTTP/1.0 400 " + e.getMessage());
      output(out, "Content-Type: text/html");
      output(out, "");
    }
    out.flush();
  }

結語

以上就是 Web Server 的簡單設計方式,若想了解更多細節,請直接閱讀 WebServer.java 檔並執行之,執行時請安裝好 Java 後
並於 WebServer.java 的存檔路徑上打 javac WebServer.java, 之後再打 java WebServer 即可啟動 Web Server 。

若要檢視其運作狀況,則於瀏覽器中打上 http://localhost/,即可看到本網頁。

WebServer.java 可指定兩個參數,其指令下法為

指令:java <路徑> <連接埠>
範例:java c:\ccc 80

參考文獻

  1. How Java Web Servers Work — http://www.onjava.com/pub/a/onjava/2003/04/23/java_webserver.html
  2. JDK API : java.net.ServerSocket — http://java.sun.com/j2se/1.4.2/docs/api/java/net/ServerSocket.html
  3. Hypertext Transfer Protocol — HTTP/1.0, http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html

Facebook

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License