java----使用socket模擬簡單的http請求服務器,響應簡單的文件請求操作


當瀏覽器向Web服務器發出請求時,它向服務器傳遞了一個數據塊,也就是請求信息,HTTP請求信息由3部分組成:

l   請求行(包括:請求方式 /URI地址 協議/版本)

l   請求頭(Request Header)/

l   請求正文

下面是一個HTTP請求的例子:

GET /sample.jsp HTTP/1.1

 

Accept:image/gif.image/jpeg,*/*

Accept-Language:zh-cn

Connection:Keep-Alive

Host:localhost

User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)

Accept-Encoding:gzip,deflate

 

username=jinqiao&password=1234

1)請求頭

請求的第一行是“方法 /URL /議版本”:GET /sample.jsp HTTP/1.1

以上代碼中“GET”代表請求方法,“/sample.jsp”表示URI,“HTTP/1.1代表協議和協議的版本。

根據HTTP標准,HTTP請求可以使用多種請求方法。例如:HTTP1.1目前支持7種請求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。

GET      請求獲取由Request-URI所標識的資源。
POST Request-URI所標識的資源后附加新的數據。
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭。

OPTIONS 請求查詢服務器的性能,或查詢與資源相關的選項和需求。
PUT 請求服務器存儲一個資源,並用Request-URI作為其標識。
DELETE 請求服務器刪除由Request-URI所標識的資源。
TRACE 請求服務器回送收到的請求信息,主要用語測試或診斷。
Internet應用中,最常用的方法是GET和POST。

URI完整地指定了要訪問的網絡資源,通常只要給出相對於服務器的根目錄的相對目錄即可,因此總是以“/”開頭,最后,協議版本聲明了通信過程中使用HTTP的版本。

 

2)請求頭(Request Header)

請求頭包含許多有關的客戶端環境和請求正文的有用信息。例如,請求頭可以聲明瀏覽器所用的語言,請求正文的長度等。

Accept:image/gif.image/jpeg.*/*

Accept-Language:zh-cn

Connection:Keep-Alive

Host:localhost

User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)

Accept-Encoding:gzip,deflate.

 

3)請求正文

請求頭和請求正文之間是一個空行,這個行非常重要,它表示請求頭已經結束,接下來的是請求正文。請求正文中可以包含客戶提交的查詢字符串信息:

username=jinqiao&password=1234

在以上的例子的HTTP請求中,請求的正文只有一行內容。當然,在實際應用中,HTTP請求正文可以包含更多的內容。

HTTP請求方法我這里只討論GET方法與POST方法

l   GET方法

GET方法是默認的HTTP請求方法,我們日常用GET方法來提交表單數據,然而用GET方法提交的表單數據只經過了簡單的編碼,同時它將作為URL的一部分向Web服務器發送,因此,如果使用GET方法來提交表單數據就存在着安全隱患上。例如

Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30&Submit=%cc%E+%BD%BB

從上面的URL請求中,很容易就可以辯認出表單提交的內容。(?之后的內容)另外由於GET方法提交的數據是作為URL請求的一部分所以提交的數據量不能太大

 

l  POST方法

POST方法是GET方法的一個替代方法,它主要是向Web服務器提交表單數據,尤其是大批量的數據。POST方法克服了GET方法的一些缺點。通過POST方法提交表單數據時,數據不是作為URL請求的一部分而是作為標准數據傳送給Web服務器,這就克服了GET方法中的信息無法保密和數據量太小的缺點。因此,出於安全的考慮以及對用戶隱私的尊重,通常表單提交時采用POST方法。

  從編程的角度來講,如果用戶通過GET方法提交數據,則數據存放在QUERY_STRING環境變量中,而POST方法提交的數據則可以從標准輸入流中獲取。

 

http響應格式

HTTP應答與HTTP請求相似,HTTP響應也由3個部分構成,分別是:

l  狀態行

l  響應頭(Response Header)

l  響應正文

在接收和解釋請求消息后,服務器會返回一個HTTP響應消息。

狀態行由協議版本、數字形式的狀態代碼、及相應的狀態描述,各元素之間以空格分隔。

格式:    HTTP-Version Status-Code Reason-Phrase CRLF

例如:    HTTP/1.1 200 OK \r\n

 

狀態代碼:

狀態代碼由3位數字組成,表示請求是否被理解或被滿足。

狀態描述:

狀態描述給出了關於狀態代碼的簡短的文字描述。

狀態代碼的第一個數字定義了響應的類別,后面兩位沒有具體的分類。

第一個數字有五種可能的取值:

- 1xx:   指示信息—表示請求已接收,繼續處理。

- 2xx:   成功—表示請求已經被成功接收、理解、接受。

- 3xx:   重定向—要完成請求必須進行更進一步的操作。

- 4xx:   客戶端錯誤—請求有語法錯誤或請求無法實現。

- 5xx: 服務器端錯誤—服務器未能實現合法的請求。

狀態代碼 狀態描述    說明

   200      OK    客戶端請求成功

   400         Bad Request   由於客戶端請求有語法錯誤,不能被服務器所理解。

   401         Unauthonzed   請求未經授權。這個狀態代碼必須和WWW-Authenticate報頭域一起使用

   403   Forbidden   服務器收到請求,但是拒絕提供服務。服務器通常會在響應正文中給出不提供服務的原因

   404   Not Found   請求的資源不存在,例如,輸入了錯誤的URL。

   500     Internal Server Error 服務器發生不可預期的錯誤,導致無法完成客戶端的請求。

   503      Service Unavailable   服務器當前不能夠處理客戶端的請求,在一段時間之后,服務器可能會恢復正常。

 

響應頭

響應頭可能包括:

Location: 

Location響應報頭域用於重定向接受者到一個新的位置。例如:客戶端所請求的頁面已不存在原先的位置,為了讓客戶端重定向到這個頁面新的位置,服務 器端可以發回Location響應報頭后使用重定向語句,讓客戶端去訪問新的域名所對應的服務器上的資源。當我們在JSP中使用重定向語句的時候,服務器 端向客戶端發回的響應報頭中,就會有Location響應報頭域。

Server:  

Server響應報頭域包含了服務器用來處理請求的軟件信息。它和User-Agent請求報頭域是相對應的,前者發送服務器端軟件的信息,后者發送客戶 端軟件(瀏覽器)和操作系統的信息。下面是Server響應報頭域的一個例子:Server: Apache-Coyote/1.1

WWW-Authenticate:

WWW-Authenticate響應報頭域必須被包含在401(未授權的)響應消息中,這個報頭域和前面講到的Authorization請求報頭域是 相關的,當客戶端收到401響應消息,就要決定是否請求服務器對其進行驗證。如果要求服務器對其進行驗證,就可以發送一個包含了 Authorization報頭域的請求,下面是WWW-Authenticate響應報頭域的一個例子:WWW-Authenticate: Basic realm="Basic Auth Test!"

從這個響應報頭域,可以知道服務器端對我們所請求的資源采用的是基本驗證機制。

 Content-Encoding:

Content-Encoding實體報頭域被使用作媒體類型的修飾符,它的值指示了已經被應用到實體正文的附加內容編碼,因而要獲得Content- Type報頭域中所引用的媒體類型,必須采用相應的解碼機制。Content-Encoding主要用語記錄文檔的壓縮方法,下面是它的一個例子: Content-Encoding: gzip。如果一個實體正文采用了編碼方式存儲,在使用之前就必須進行解碼。

Content-Language:

Content-Language實體報頭域描述了資源所用的自然語言。Content-Language允許用戶遵照自身的首選語言來識別和區分實體。 如果這個實體內容僅僅打算提供給丹麥的閱讀者,那么可以按照如下的方式設置這個實體報頭域:Content-Language: da。

如果沒有指定Content-Language報頭域,那么實體內容將提供給所以語言的閱讀者。

 Content-Length:

  Content-Length實體報頭域用於指明正文的長度,以字節方式存儲的十進制數字來表示,也就是一個數字字符占一個字節,用其對應的ASCII碼存儲傳輸。

       要注意的是:這個長度僅僅是表示實體正文的長度,沒有包括實體報頭的長度。

Content-Type

     Content-Type實體報頭域用語指明發送給接收者的實體正文的媒體類型。例如:

Content-Type: text/html;charset=ISO-8859-1

   Content-Type: text/html;charset=GB2312

Last-Modified

     Last-Modified實體報頭域用於指示資源最后的修改日期及時間。

Expires

     Expires實體報頭域給出響應過期的日期和時間。通常,代理服務器或瀏覽器會緩存一些頁面。當用戶再次訪問這些頁面時,直接從緩存中加載並顯示給用 戶,這樣縮短了響應的時間,減少服務器的負載。為了讓代理服務器或瀏覽器在一段時間后更新頁面,我們可以使用Expires實體報頭域指定頁面過期的時 間。當用戶又一次訪問頁面時,如果Expires報頭域給出的日期和時間比Date普通報頭域給出的日期和時間要早(或相同),那么代理服務器或瀏覽器就 不會再使用緩存的頁面而是從服務器上請求更新的頁面。不過要注意,即使頁面過期了,也並不意味着服務器上的原始資源在此時間之前或之后發生了改變。

      Expires實體報頭域使用的日期和時間必須是RFC 1123中的日期格式,例如:

 Expires: Thu, 15 Sep 2005 16:00:00 GMT

       HTTP1.1的客戶端和緩存必須將其他非法的日期格式(也包括0)看作已過期。例如,為了讓瀏覽器不要緩存頁面,我們也可以利用Expires實體報頭 域,設置它的值為0,如下(JSP):response.setDateHeader("Expires",0); 

 

下面是一個HTTP響應的例子:

HTTP/1.1 200 OK

 

Server:Apache Tomcat/5.0.12

Date:Mon,6Oct2003 13:23:42 GMT

Content-Length:112

介紹完基本的http協議內容后,下面就可以着手實現HttpServer了。因為這里主要是演示向服務器請求資源、接收資源的操作,所以此處比較重要的一步操作是獲取請求行里面的URI地址,這個地址代表了資源的在服務器內部的相對路徑。

因為是使用socket來實現,所以

第一步就是創建一個ServerSocket,即服務器socket,並為其指定一個端口:

server = new ServerSocket(PORT);
			if (server == null)
				System.exit(1);

第二步是要獲取向ServerSocket發送請求的客戶端Socket:client,並通過該client的輸入流獲取到請求行,得到請求資源的名稱:

Socket client = null;
                client = server.accept();
                if (client != null) {
                    try {
                        System.out.println("連接服務器成功!!...");

                        BufferedReader reader = new BufferedReader(
                                new InputStreamReader(client.getInputStream()));

                        // GET /test.jpg /HTTP1.1
                        String line = reader.readLine();

                        System.out.println("line: " + line);

                        String resource = line.substring(line.indexOf('/'),
                                line.lastIndexOf('/') - 5);

                        System.out.println("the resource you request is: "
                                + resource);

                        resource = URLDecoder.decode(resource, "UTF-8");

第三步是根據請求資源的后綴名,引導到你想要回傳的資源:

if (resource.endsWith(".mkv")) {

                            transferFileHandle("videos/test.mkv", client);
                            closeSocket(client);
                            continue;

                        } else if (resource.endsWith(".jpg")) {

                            transferFileHandle("images/test.jpg", client);
                            closeSocket(client);
                            continue;

                        } else if (resource.endsWith(".rmvb")) {

                            transferFileHandle("videos/test.rmvb", client);
                            closeSocket(client);
                            continue;

                        } else {
                            PrintStream writer = new PrintStream(
                                    client.getOutputStream(), true);
                            writer.println("HTTP/1.0 404 Not found");// 返回應答消息,並結束應答
                            writer.println();// 根據 HTTP 協議, 空行將結束頭信息
                            writer.close();
                            closeSocket(client);
                            continue;
                        }

具體的回傳資源的功能函數詳細代碼如下:

private void transferFileHandle(String path, Socket client) {

        File fileToSend = new File(path);

        if (fileToSend.exists() && !fileToSend.isDirectory()) {
            try {
                PrintStream writer = new PrintStream(client.getOutputStream());
                writer.println("HTTP/1.0 200 OK");// 返回應答消息,並結束應答
                writer.println("Content-Type:application/binary");
                writer.println("Content-Length:" + fileToSend.length());// 返回內容字節數
                writer.println();// 根據 HTTP 協議, 空行將結束頭信息

                FileInputStream fis = new FileInputStream(fileToSend);
                byte[] buf = new byte[fis.available()];
                fis.read(buf);
                writer.write(buf);
                writer.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

通過client的輸出流,就可以將資源返回給瀏覽器。在驗證過程中發現,圖片格式會直接顯示在瀏覽器中;視頻格式就會提示用瀏覽器下載。這里也只試驗了這幾中情況。

 

下面是全部代碼實現:

public class SocketHttpServer implements Runnable {

    private final static int PORT = 28081;
    private ServerSocket server = null;

    public static void main(String[] args) {
        new SocketHttpServer();
    }

    public SocketHttpServer() {
        try {
            server = new ServerSocket(PORT);
            if (server == null)
                System.exit(1);
            new Thread(this).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                Socket client = null;
                client = server.accept();
                if (client != null) {
                    try {
                        System.out.println("連接服務器成功!!...");

                        BufferedReader reader = new BufferedReader(
                                new InputStreamReader(client.getInputStream()));

                        // GET /test.jpg /HTTP1.1
                        String line = reader.readLine();

                        System.out.println("line: " + line);

                        String resource = line.substring(line.indexOf('/'),
                                line.lastIndexOf('/') - 5);

                        System.out.println("the resource you request is: "
                                + resource);

                        resource = URLDecoder.decode(resource, "UTF-8");

                        String method = new StringTokenizer(line).nextElement()
                                .toString();

                        System.out.println("the request method you send is: "
                                + method);

                        while ((line = reader.readLine()) != null) {
                            if (line.equals("")) {
                                break;
                            }
                            System.out.println("the Http Header is : " + line);
                        }

                        if ("post".equals(method.toLowerCase())) {
                            System.out.println("the post request body is: "
                                    + reader.readLine());
                        }

                        if (resource.endsWith(".mkv")) {

                            transferFileHandle("videos/test.mkv", client);
                            closeSocket(client);
                            continue;

                        } else if (resource.endsWith(".jpg")) {

                            transferFileHandle("images/test.jpg", client);
                            closeSocket(client);
                            continue;

                        } else if (resource.endsWith(".rmvb")) {

                            transferFileHandle("videos/test.rmvb", client);
                            closeSocket(client);
                            continue;

                        } else {
                            PrintStream writer = new PrintStream(
                                    client.getOutputStream(), true);
                            writer.println("HTTP/1.0 404 Not found");// 返回應答消息,並結束應答
                            writer.println();// 根據 HTTP 協議, 空行將結束頭信息
                            writer.close();
                            closeSocket(client);
                            continue;
                        }
                    } catch (Exception e) {
                        System.out.println("HTTP服務器錯誤:"
                                + e.getLocalizedMessage());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void closeSocket(Socket socket) {
        try {
            socket.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        System.out.println(socket + "離開了HTTP服務器");
    }

    private void transferFileHandle(String path, Socket client) {

        File fileToSend = new File(path);

        if (fileToSend.exists() && !fileToSend.isDirectory()) {
            try {
                PrintStream writer = new PrintStream(client.getOutputStream());
                writer.println("HTTP/1.0 200 OK");// 返回應答消息,並結束應答
                writer.println("Content-Type:application/binary");
                writer.println("Content-Length:" + fileToSend.length());// 返回內容字節數
                writer.println();// 根據 HTTP 協議, 空行將結束頭信息

                FileInputStream fis = new FileInputStream(fileToSend);
                byte[] buf = new byte[fis.available()];
                fis.read(buf);
                writer.write(buf);
                writer.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

這種功能的實現,代碼量還是比較少的。要驗證的話,只需在瀏覽器地址欄輸入:http://192.168.7.107:28081/test.mkv,IP地址和端口號根據實際情況更改即可。

參考博文:

http://www.cnblogs.com/loveyakamoz/archive/2011/07/22/2113614.html

 


免責聲明!

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



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