HTTP 協議 請求頭詳解


HTTP由兩部分組成:請求和響應。當你在Web瀏覽器中輸入一個URL時,瀏覽 器將根據你的要求創建並發送請求,該請求包含所輸入的URL以及一些與瀏覽器本身相關的信息。當服務器收到這個請求時將返回一個響應,該響應包括與該請求 相關的信息以及位於指定URL(如果有的話)的數據。直到瀏覽器解析該響應並顯示出網頁(或其他資源)為止。

HTTP請求

HTTP請求的格式如下所示:

<request-line>
<headers>
<blank line>
[<request-body>]

在HTTP請求中,第一行必須是一個請求行(request line),用來說明請求類型、要訪問的資源以及使用的HTTP版本。緊接着是一個首部(header)小節,用來說明服務器要使用的附加信息。在首部之 后是一個空行,再此之后可以添加任意的其他數據[稱之為主體(body)]。

在HTTP中,定義了多種請求類型,通常我們關心的只有GET請求和POST請求。只要在Web瀏覽器上輸入一個URL,瀏覽器就將基於該URL向服務器發送一個GET請求,以告訴服務器獲取並返回什么資源。對於www.baidu.com的GET請求如下所示:

GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive

請求行的第一部分說明了該請求是GET請求。該行的第二部分是一個斜杠(/),用來說明請求的是該域名的根目錄。該行的最后一部分說明使用的是HTTP 1.1版本(另一個可選項是1.0)。那么請求發到哪里去呢?這就是第二行的內容。

第2行是請求的第一個首部,HOST。首部HOST將指出請求的目的地。結合HOST和上一行中的斜杠(/),可以通知服務器請求的是www.baidu.com/(HTTP 1.1才需要使用首部HOST,而原來的1.0版本則不需要使用)。第三行中包含的是首部User-Agent,服務器端和客戶端腳本都能夠訪問它,它是 瀏覽器類型檢測邏輯的重要基礎。該信息由你使用的瀏覽器來定義(在本例中是Firefox 1.0.1),並且在每個請求中將自動發送。最后一行是首部Connection,通常將瀏覽器操作設置為Keep-Alive(當然也可以設置為其他 值)。注意,在最后一個首部之后有一個空行。即使不存在請求主體,這個空行也是必需的。

要發送GET請求的參數,則必須將這些額外的信息附在URL本身的后面。其格式類似於:

URL ? name1=value1&name2=value2&..&nameN=valueN

該信息稱之為查詢字符串(query string),它將會復制在HTTP請求的請求行中,如下所示:

GET /books/?name=Professional%20Ajax HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive

注意,為了將文本“Professional Ajax”作為URL的參數,需要編碼處理其內容,將空格替換成%20,這稱為URL編碼(URL encoding),常用於HTTP的許多地方(JavaScript提供了內建的函數來處理URL編碼和解碼)。“名稱—值”(name—value) 對用 & 隔開。絕大部分的服務器端技術能夠自動對請求主體進行解碼,並為這些值的訪問提供一些邏輯方式。當然,如何使用這些數據還是由服務器決定的。

另一方面,POST請求在請求主體中為服務器提供了一些附加的信息。通常,當填寫一個在線表單並提交它時,這些填入的數據將以POST請求的方式發送給服務器。

以下就是一個典型的POST請求:

POST / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

從上面可以發現, POST請求和GET請求之間有一些區別。首先,請求行開始處的GET改為了POST,以表示不同的請求類型。你會發現首部Host和User- Agent仍然存在,在后面有兩個新行。其中首部Content-Type說明了請求主體的內容是如何編碼的。瀏覽器始終以application/ x-www-form- urlencoded的格式編碼來傳送數據,這是針對簡單URL編碼的MIME類型。首部Content-Length說明了請求主體的字節數。在首部 Connection后是一個空行,再后面就是請求主體。與大多數瀏覽器的POST請求一樣,這是以簡單的“名稱—值”對的形式給出的,其中name是 Professional Ajax,publisher是Wiley。你可以以同樣的格式來組織URL的查詢字符串參數。

下面是一些最常見的請求頭:

    Accept:瀏覽器可接受的MIME類型。
    Accept - Charset:瀏覽器可接受的字符集。
    Accept - Encoding:瀏覽器能夠進行解碼的數據編碼方式,比如gzip。Servlet能夠向支持gzip的瀏覽器返回經gzip編碼的HTML頁面。許多情形下這可以減少5到10倍的下載時間。
    Accept - Language:瀏覽器所希望的語言種類,當服務器能夠提供一種以上的語言版本時要用到。
    Authorization:授權信息,通常出現在對服務器發送的WWW - Authenticate頭的應答中。
    Connection:表示是否需要持久連接。如果Servlet看到這里的值為“Keep - Alive”,或者看到請求使用的是HTTP 1.1(HTTP 1.1默認進行持久連接),它就可以利用持久連接的優點,當頁面包含多個元素時(例如Applet,圖片),顯著地減少下載所需要的時間。要實現這一 點,Servlet需要在應答中發送一個Content - Length頭,最簡單的實現方法是:先把內容寫入ByteArrayOutputStream,然后在正式寫出內容之前計算它的大小。
    Content - Length:表示請求消息正文的長度。
    Cookie:這是最重要的請求頭信息之一,參見后面《Cookie處理》一章中的討論。
    From:請求發送者的email地址,由一些特殊的Web客戶程序使用,瀏覽器不會用到它。
    Host:初始URL中的主機和端口。
    If - Modified - Since:只有當所請求的內容在指定的日期之后又經過修改才返回它,否則返回304“Not Modified”應答。
    Pragma:指定“no - cache”值表示服務器必須返回一個刷新后的文檔,即使它是代理服務器而且已經有了頁面的本地拷貝。
    Referer:包含一個URL,用戶從該URL代表的頁面出發訪問當前請求的頁面。
    User - Agent:瀏覽器類型,如果Servlet返回的內容與瀏覽器類型有關則該值非常有用。
    UA - Pixels,UA - Color,UA - OS,UA - CPU:由某些版本的IE瀏覽器所發送的非標准的請求頭,表示屏幕大小、顏色深度、操作系統和CPU類型。


HTTP響應

如下所示,HTTP響應的格式與請求的格式十分類似:
<status-line>
<headers>
<blank line>
[<response-body>]

正如你所見,在響應中唯一真正的區別在於第一行中用狀態信息代替了請求信息。狀態行(status line)通過提供一個狀態碼來說明所請求的資源情況。以下就是一個HTTP響應的例子:

HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122

<html>
<head>
<title>Wrox Homepage</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>

在本例中,狀態行給出的HTTP狀態代碼是200,以及消息OK。狀態行始終包含的是狀態碼和相應的簡短消息,以避免混亂。最常用的狀態碼有:
◆200 (OK): 找到了該資源,並且一切正常。
◆304 (NOT MODIFIED): 該資源在上次請求之后沒有任何修改。這通常用於瀏覽器的緩存機制。
◆401 (UNAUTHORIZED): 客戶端無權訪問該資源。這通常會使得瀏覽器要求用戶輸入用戶名和密碼,以登錄到服務器。
◆403 (FORBIDDEN): 客戶端未能獲得授權。這通常是在401之后輸入了不正確的用戶名或密碼。
◆404 (NOT FOUND): 在指定的位置不存在所申請的資源。

在狀態行之后是一些首部。通常,服務器會返回一個名為Data的首部,用來說明響 應生成的日期和時間(服務器通常還會返回一些關於其自身的信息,盡管並非是必需的)。接下來的兩個首部大家應該熟悉,就是與POST請求中一樣的 Content-Type和Content-Length。在本例中,首部Content-Type指定了MIME類型HTML(text/html), 其編碼類型是ISO-8859-1(這是針對美國英語資源的編碼標准)。響應主體所包含的就是所請求資源的HTML源文件(盡管還可能包含純文本或其他資 源類型的二進制數據)。瀏覽器將把這些數據顯示給用戶。

注意,這里並沒有指明針對該響應的請求類型,不過這對於服務器並不重要。客戶端知道每種類型的請求將返回什么類型的數據,並決定如何使用這些數據。


附錄:使用Java套接字實現一個可以處理get和post請求的小HTTP服務器程序
/**
* SimpleHttpServer.java
*/

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

/**
* 一個簡單的用 Java Socket 編寫的 HTTP 服務器應用, 演示了請求和應答的協議通信內容以及
* 給客戶端返回 HTML 文本和二進制數據文件(一個圖片), 同時展示了 404, 200 等狀態碼.
* 首先運行這個程序,然后打開Web瀏覽器,鍵入http://localhost,則這個程序能夠顯示出瀏覽器發送了那些信息
* 並且向瀏覽器返回一個網頁和一副圖片, 並測試同瀏覽器對話.
* 當瀏覽器看到 HTML 中帶有圖片地址時, 則會發出第二次連接來請求圖片等資源.
* 這個例子可以幫您理解 Java 的 HTTP 服務器軟件是基於 J2SE 的 Socket 等軟件編寫的概念, 並熟悉
* HTTP 協議.
* 相反的用 Telnet 連接到已有的服務器則可以幫忙理解瀏覽器的運行過程和服務器端的返回內容.
*
* <pre>
*       當用戶在Web瀏覽器地址欄中輸入一個帶有http://前綴的URL並按下Enter后,或者在Web頁面中某個以http://開頭的超鏈接上單擊鼠標,HTTP事務處理的第一個階段--建立連接階段就開始了.HTTP的默認端口是80.
*    隨着連接的建立,HTTP就進入了客戶向服務器發送請求的階段.客戶向服務器發送的請求是一個有特定格式的ASCII消息,其語法規則為:
* < Method > < URL > < HTTP Version > < >
* { <Header>:<Value> < >}*
* < >
* { Entity Body }
*    請求消息的頂端是請求行,用於指定方法,URL和HTTP協議的版本,請求行的最后是回車換行.方法有GET,POST,HEAD,PUT,DELETE等.
* 在請求行之后是若干個報頭(Header)行.每個報頭行都是由一個報頭和一個取值構成的二元對,報頭和取值之間以":"分隔;報頭行的最后是回車換行. 常見的報頭有Accept(指定MIME媒體類型),Accept_Charset(響應消息的編碼方式),Accept_Encoding(響應消息的 字符集),User_Agent(用戶的瀏覽器信息)等.
*    在請求消息的報頭行之后是一個回車換行,表明請求消息的報頭部分結束.在這個之后是請求消息的消息實體(Entity Body).具體的例子參看httpRequest.txt.
*     Web服務器在收到客戶請求並作出處理之后,要向客戶發送應答消息.與請求消息一樣,應答消息的語法規則為:
* < HTTP Version> <Status Code> [<Message>]< >
* { <Header>:<Value> < > } *
* < >
* { Entity Body }
*    應答消息的第一行為狀態行,其中包括了HTTP版本號,狀態碼和對狀態碼進行簡短解釋的消息;狀態行的最后是回車換行.狀態碼由3位數字組成,有5類: 
* 參看:HTTP應答碼及其意義 

* 1XX 保留 
* 2XX 表示成功 
* 3XX 表示URL已經被移走 
* 4XX 表示客戶錯誤 
* 5XX 表示服務器錯誤 
* 例如:415,表示不支持改媒體類型;503,表示服務器不能訪問.最常見的是200,表示成功.常見的報頭有:Last_Modified(最后修改時間),Content_Type(消息內容的MIME類型),Content_Length(內容長度)等.
*    在報頭行之后也是一個回車換行,用以表示應答消息的報頭部分的結束,以及應答消息實體的開始.
*    下面是一個應答消息的例子:
* HTTP/1.0 200 OK
* Date: Moday,07-Apr-97 21:13:02 GMT
* Server:NCSA/1.1
* MIME_Version:1.0
* Content_Type:text/html
* Last_Modified:Thu Dec 5 09:28:01 1996
* Coentent_Length:3107

* <HTML><HEAD><TITLE></HTML>

 

下面是不同狀態碼代表的意思:

 

http狀態返回代碼 1xx(臨時響應)
表示臨時響應並需要請求者繼續執行操作的狀態代碼。

 

http狀態返回代碼 代碼 說明
100 (繼續) 請求者應當繼續提出請求。 服務器返回此代碼表示已收到請求的第一部分,正在等待其余部分。
101 (切換協議) 請求者已要求服務器切換協議,服務器已確認並准備切換。

 

http狀態返回代碼 2xx (成功)
表示成功處理了請求的狀態代碼。

 

http狀態返回代碼 代碼 說明
200 (成功) 服務器已成功處理了請求。 通常,這表示服務器提供了請求的網頁。
201 (已創建) 請求成功並且服務器創建了新的資源。
202 (已接受) 服務器已接受請求,但尚未處理。
203 (非授權信息) 服務器已成功處理了請求,但返回的信息可能來自另一來源。
204 (無內容) 服務器成功處理了請求,但沒有返回任何內容。
205 (重置內容) 服務器成功處理了請求,但沒有返回任何內容。
206 (部分內容) 服務器成功處理了部分 GET 請求。

 

http狀態返回代碼 3xx (重定向)
表示要完成請求,需要進一步操作。 通常,這些狀態代碼用來重定向。

 

http狀態返回代碼 代碼 說明
300 (多種選擇) 針對請求,服務器可執行多種操作。 服務器可根據請求者 (user agent) 選擇一項操作,或提供操作列表供請求者選擇。
301 (永久移動) 請求的網頁已永久移動到新位置。 服務器返回此響應(對 GET 或 HEAD 請求的響應)時,會自動將請求者轉到新位置。
302 (臨時移動) 服務器目前從不同位置的網頁響應請求,但請求者應繼續使用原有位置來進行以后的請求。
303 (查看其他位置) 請求者應當對不同的位置使用單獨的 GET 請求來檢索響應時,服務器返回此代碼。
304 (未修改) 自從上次請求后,請求的網頁未修改過。 服務器返回此響應時,不會返回網頁內容。
305 (使用代理) 請求者只能使用代理訪問請求的網頁。 如果服務器返回此響應,還表示請求者應使用代理。
307 (臨時重定向) 服務器目前從不同位置的網頁響應請求,但請求者應繼續使用原有位置來進行以后的請求。
http狀態返回代碼 4xx(請求錯誤)
這些狀態代碼表示請求可能出錯,妨礙了服務器的處理。

 

http狀態返回代碼 代碼 說明
400 (錯誤請求) 服務器不理解請求的語法。
401 (未授權) 請求要求身份驗證。 對於需要登錄的網頁,服務器可能返回此響應。
403 (禁止) 服務器拒絕請求。
404 (未找到) 服務器找不到請求的網頁。
405 (方法禁用) 禁用請求中指定的方法。
406 (不接受) 無法使用請求的內容特性響應請求的網頁。
407 (需要代理授權) 此狀態代碼與 401(未授權)類似,但指定請求者應當授權使用代理。
408 (請求超時) 服務器等候請求時發生超時。
409 (沖突) 服務器在完成請求時發生沖突。 服務器必須在響應中包含有關沖突的信息。
410 (已刪除) 如果請求的資源已永久刪除,服務器就會返回此響應。
411 (需要有效長度) 服務器不接受不含有效內容長度標頭字段的請求。
412 (未滿足前提條件) 服務器未滿足請求者在請求中設置的其中一個前提條件。
413 (請求實體過大) 服務器無法處理請求,因為請求實體過大,超出服務器的處理能力。
414 (請求的 URI 過長) 請求的 URI(通常為網址)過長,服務器無法處理。
415 (不支持的媒體類型) 請求的格式不受請求頁面的支持。
416 (請求范圍不符合要求) 如果頁面無法提供請求的范圍,則服務器會返回此狀態代碼。
417 (未滿足期望值) 服務器未滿足”期望”請求標頭字段的要求。

 

http狀態返回代碼 5xx(服務器錯誤)
這些狀態代碼表示服務器在嘗試處理請求時發生內部錯誤。 這些錯誤可能是服務器本身的錯誤,而不是請求出錯。

 

http狀態返回代碼 代碼 說明
500 (服務器內部錯誤) 服務器遇到錯誤,無法完成請求。
501 (尚未實施) 服務器不具備完成請求的功能。 例如,服務器無法識別請求方法時可能會返回此代碼。
502 (錯誤網關) 服務器作為網關或代理,從上游服務器收到無效響應。
503 (服務不可用) 服務器目前無法使用(由於超載或停機維護)。 通常,這只是暫時狀態。
504 (網關超時) 服務器作為網關或代理,但是沒有及時從上游服務器收到請求。
505 (HTTP 版本不受支持) 服務器不支持請求中所用的 HTTP 協議版本。

 

一些常見的http狀態返回代碼為:
200 – 服務器成功返回網頁
404 – 請求的網頁不存在
503 – 服務不可用



* 在用Java語言實現HTTP服務器時,首先啟動一個java.net.ServerSocket在提供服務的端口上監聽連接.向客戶返回文本時,可以用 PrintWriter,但是如果返回二進制數據,則必須使用OutputStream.write(byte[])方法,返回的應答消息字符串可以使用 String.getBytes()方法轉換為字節數組返回,或者使用PrintStream的print()方法寫入文本,用 write(byte[])方法寫入二進制數據.

* </pre>
* @author 劉長炯
* @version 1.0 2007-07-24 Sunday
*/
public class SimpleHttpServer implements Runnable {
    /**
     * 
     */
    ServerSocket serverSocket;//服務器Socket
    
    /**
     * 服務器監聽端口, 默認為 80.
     */
    public static int PORT=80;//標准HTTP端口
    
    /**
     * 開始服務器 Socket 線程.
     */
    public SimpleHttpServer() {
        try {
            serverSocket=new ServerSocket(PORT);
        } catch(Exception e) {
            System.out.println("無法啟動HTTP服務器:"+e.getLocalizedMessage());
        }
        if(serverSocket==null) System.exit(1);//無法開始服務器
        new Thread(this).start();
        System.out.println("HTTP服務器正在運行,端口:"+PORT);
    }
    
    /**
     * 運行服務器主線程, 監聽客戶端請求並返回響應.
     */
    public void run() {
        while(true) {
            try {
                Socket client=null;//客戶Socket
                client=serverSocket.accept();//客戶機(這里是 IE 等瀏覽器)已經連接到當前服務器
                if(client!=null) {
                    System.out.println("連接到服務器的用戶:"+client);
                    try {
                        // 第一階段: 打開輸入流
                        BufferedReader in=new BufferedReader(new InputStreamReader(
                                client.getInputStream()));
                        
                        System.out.println("客戶端發送的請求信息: ***************");
                        // 讀取第一行, 請求地址
                        String line=in.readLine();
                        System.out.println(line);
                        String resource=line.substring(line.indexOf('/'),line.lastIndexOf('/')-5);
                        //獲得請求的資源的地址
                        resource=URLDecoder.decode(resource, "UTF-8");//反編碼 URL 地址
                        String method = new StringTokenizer(line).nextElement().toString();// 獲取請求方法, GET 或者 POST

                        // 讀取所有瀏覽器發送過來的請求參數頭部信息
                        while( (line = in.readLine()) != null) {
                            System.out.println(line);
                            
                            if(line.equals("")) break;
                        }
                        
                        // 顯示 POST 表單提交的內容, 這個內容位於請求的主體部分
                        if("POST".equalsIgnoreCase(method)) {
                            System.out.println(in.readLine());
                        }
                        
                        System.out.println("請求信息結束 ***************");
                        System.out.println("用戶請求的資源是:"+resource);
                        System.out.println("請求的類型是: " + method);

                        // GIF 圖片就讀取一個真實的圖片數據並返回給客戶端
                        if(resource.endsWith(".gif")) {
                            fileService("images/test.gif", client);
                            closeSocket(client);
                            continue;
                        }
                        
                        // 請求 JPG 格式就報錯 404
                        if(resource.endsWith(".jpg")) {
                                                    PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                        out.println("HTTP/1.0 404 Not found");//返回應答消息,並結束應答
                        out.println();// 根據 HTTP 協議, 空行將結束頭信息
                        out.close();
                        closeSocket(client);
                        continue;
                        } else {
                            // 用 writer 對客戶端 socket 輸出一段 HTML 代碼
                            PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                            out.println("HTTP/1.0 200 OK");//返回應答消息,並結束應答
                            out.println("Content-Type:text/html;charset=GBK");
                            out.println();// 根據 HTTP 協議, 空行將結束頭信息

                            out.println("<h1> Hello Http Server</h1>");
                            out.println("你好, 這是一個 Java HTTP 服務器 demo 應用.<br>");
                            out.println("您請求的路徑是: " + resource + "<br>");
                            out.println("這是一個支持虛擬路徑的圖片:<img src='abc.gif'><br>" +
                                    "<a href='abc.gif'>點擊打開abc.gif, 是個服務器虛擬路徑的圖片文件.</a>");
                            out.println("<br>這是個會反饋 404 錯誤的的圖片:<img src='test.jpg'><br><a href='test.jpg'>點擊打開test.jpg</a><br>");
                            out.println("<form method=post action='/'>POST 表單 <input name=username value='用戶'> <input name=submit type=submit value=submit></form>");
                            out.close();

                            closeSocket(client);
                        }
                    } catch(Exception e) {
                        System.out.println("HTTP服務器錯誤:"+e.getLocalizedMessage());
                    }
                }
                //System.out.println(client+"連接到HTTP服務器");//如果加入這一句,服務器響應速度會很慢
            } catch(Exception e) {
                System.out.println("HTTP服務器錯誤:"+e.getLocalizedMessage());
            }
        }
    }
    
    /**
     * 關閉客戶端 socket 並打印一條調試信息.
     * @param socket 客戶端 socket.
     */
    void closeSocket(Socket socket) {
        try {
            socket.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
                            System.out.println(socket + "離開了HTTP服務器");        
    }
    
    /**
     * 讀取一個文件的內容並返回給瀏覽器端.
     * @param fileName 文件名
     * @param socket 客戶端 socket.
     */
        void fileService(String fileName, Socket socket)
    {
            
        try
        {
            PrintStream out = new PrintStream(socket.getOutputStream(), true);
            File fileToSend = new File(fileName);
            if(fileToSend.exists() && !fileToSend.isDirectory())
            {
                out.println("HTTP/1.0 200 OK");//返回應答消息,並結束應答
                out.println("Content-Type:application/binary");
                out.println("Content-Length:" + fileToSend.length());// 返回內容字節數
                out.println();// 根據 HTTP 協議, 空行將結束頭信息
                
                FileInputStream fis = new FileInputStream(fileToSend);
                byte data[] = new byte[fis.available()];
                fis.read(data);
                out.write(data);
                out.close();
                fis.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("傳送文件時出錯:" + e.getLocalizedMessage());
        }
    }
    
    /**
     * 打印用途說明.
     */
    private static void usage() {
        System.out.println("Usage: java HTTPServer <port> Default port is 80.");
    }
    
    
    /**
     * 啟動簡易 HTTP 服務器
     * @param args 
     */
    public static void main(String[] args) {
        try {
            if(args.length != 1) {
                usage();
            } else if(args.length == 1) {
                PORT = Integer.parseInt(args[0]);
            }
        } catch (Exception ex) {
            System.err.println("Invalid port arguments. It must be a integer that greater than 0");
        }
        
        new SimpleHttpServer();
    }
    
}

</HTML>
* 
* 在用Java語言實現HTTP服務器時,首先啟動一個java.net.ServerSocket在提供服務的端口上監聽連接.向客戶返回文本時,可以用PrintWriter,但是如果返回二進制數據,則必須使用OutputStream.write(byte[])方法,返回的應答消息字符串可以使用String.getBytes()方法轉換為字節數組返回,或者使用PrintStream的print()方法寫入文本,用write(byte[])方法寫入二進制數據.
* 
* </pre>
* @author 劉長炯
* @version 1.0 2007-07-24 Sunday
*/
public class SimpleHttpServer implements Runnable {
    /**
     * 
     */
    ServerSocket serverSocket;//服務器Socket
    
    /**
     * 服務器監聽端口, 默認為 80.
     */
    public static int PORT=80;//標准HTTP端口
    
    /**
     * 開始服務器 Socket 線程.
     */
    public SimpleHttpServer() {
        try {
            serverSocket=new ServerSocket(PORT);
        } catch(Exception e) {
            System.out.println("無法啟動HTTP服務器:"+e.getLocalizedMessage());
        }
        if(serverSocket==null) System.exit(1);//無法開始服務器
        new Thread(this).start();
        System.out.println("HTTP服務器正在運行,端口:"+PORT);
    }
    
    /**
     * 運行服務器主線程, 監聽客戶端請求並返回響應.
     */
    public void run() {
        while(true) {
            try {
                Socket client=null;//客戶Socket
                client=serverSocket.accept();//客戶機(這里是 IE 等瀏覽器)已經連接到當前服務器
                if(client!=null) {
                    System.out.println("連接到服務器的用戶:"+client);
                    try {
                        // 第一階段: 打開輸入流
                        BufferedReader in=new BufferedReader(new InputStreamReader(
                                client.getInputStream()));
                        
                        System.out.println("客戶端發送的請求信息: ***************");
                        // 讀取第一行, 請求地址
                        String line=in.readLine();
                        System.out.println(line);
                        String resource=line.substring(line.indexOf('/'),line.lastIndexOf('/')-5);
                        //獲得請求的資源的地址
                        resource=URLDecoder.decode(resource, "UTF-8");//反編碼 URL 地址
                        String method = new StringTokenizer(line).nextElement().toString();// 獲取請求方法, GET 或者 POST

                        // 讀取所有瀏覽器發送過來的請求參數頭部信息
                        while( (line = in.readLine()) != null) {
                            System.out.println(line);
                            
                            if(line.equals("")) break;
                        }
                        
                        // 顯示 POST 表單提交的內容, 這個內容位於請求的主體部分
                        if("POST".equalsIgnoreCase(method)) {
                            System.out.println(in.readLine());
                        }
                        
                        System.out.println("請求信息結束 ***************");
                        System.out.println("用戶請求的資源是:"+resource);
                        System.out.println("請求的類型是: " + method);

                        // GIF 圖片就讀取一個真實的圖片數據並返回給客戶端
                        if(resource.endsWith(".gif")) {
                            fileService("images/test.gif", client);
                            closeSocket(client);
                            continue;
                        }
                        
                        // 請求 JPG 格式就報錯 404
                        if(resource.endsWith(".jpg")) {
                                                    PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                        out.println("HTTP/1.0 404 Not found");//返回應答消息,並結束應答
                        out.println();// 根據 HTTP 協議, 空行將結束頭信息
                        out.close();
                        closeSocket(client);
                        continue;
                        } else {
                            // 用 writer 對客戶端 socket 輸出一段 HTML 代碼
                            PrintWriter out=new PrintWriter(client.getOutputStream(),true);
                            out.println("HTTP/1.0 200 OK");//返回應答消息,並結束應答
                            out.println("Content-Type:text/html;charset=GBK");
                            out.println();// 根據 HTTP 協議, 空行將結束頭信息

                            out.println("<h1> Hello Http Server</h1>");
                            out.println("你好, 這是一個 Java HTTP 服務器 demo 應用.<br>");
                            out.println("您請求的路徑是: " + resource + "<br>");
                            out.println("這是一個支持虛擬路徑的圖片:<img src="abc.gif" mce_src="abc.gif"><br>" +
                                    "<a href="abc.gif" mce_href="abc.gif">點擊打開abc.gif, 是個服務器虛擬路徑的圖片文件.</a>");
                            out.println("<br>這是個會反饋 404 錯誤的的圖片:<img src="test.jpg" mce_src="test.jpg"><br><a href="test.jpg" mce_href="test.jpg">點擊打開test.jpg</a><br>");
                            out.println("<form method=post action='/'>POST 表單 <input name=username value='用戶'> <input name=submit type=submit value=submit></form>");
                            out.close();

                            closeSocket(client);
                        }
                    } catch(Exception e) {
                        System.out.println("HTTP服務器錯誤:"+e.getLocalizedMessage());
                    }
                }
                //System.out.println(client+"連接到HTTP服務器");//如果加入這一句,服務器響應速度會很慢
            } catch(Exception e) {
                System.out.println("HTTP服務器錯誤:"+e.getLocalizedMessage());
            }
        }
    }
    
    /**
     * 關閉客戶端 socket 並打印一條調試信息.
     * @param socket 客戶端 socket.
     */
    void closeSocket(Socket socket) {
        try {
            socket.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
                            System.out.println(socket + "離開了HTTP服務器");        
    }
    
    /**
     * 讀取一個文件的內容並返回給瀏覽器端.
     * @param fileName 文件名
     * @param socket 客戶端 socket.
     */
        void fileService(String fileName, Socket socket)
    {
            
        try
        {
            PrintStream out = new PrintStream(socket.getOutputStream(), true);
            File fileToSend = new File(fileName);
            if(fileToSend.exists() && !fileToSend.isDirectory())
            {
                out.println("HTTP/1.0 200 OK");//返回應答消息,並結束應答
                out.println("Content-Type:application/binary");
                out.println("Content-Length:" + fileToSend.length());// 返回內容字節數
                out.println();// 根據 HTTP 協議, 空行將結束頭信息
                
                FileInputStream fis = new FileInputStream(fileToSend);
                byte data[] = new byte[fis.available()];
                fis.read(data);
                out.write(data);
                out.close();
                fis.close();
            }
        }
        catch(Exception e)
        {
            System.out.println("傳送文件時出錯:" + e.getLocalizedMessage());
        }
    }
    
    /**
     * 打印用途說明.
     */
    private static void usage() {
        System.out.println("Usage: java HTTPServer <port> Default port is 80.");
    }
    
    
    /**
     * 啟動簡易 HTTP 服務器
     * @param args 
     */
    public static void main(String[] args) {
        try {
            if(args.length != 1) {
                usage();
            } else if(args.length == 1) {
                PORT = Integer.parseInt(args[0]);
            }
        } catch (Exception ex) {
            System.err.println("Invalid port arguments. It must be a integer that greater than 0");
        }
        
        new SimpleHttpServer();
    }
    
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM