理解HTTP

2020-09-14 16:17 更新

理解 HTTP

什么是 HTTP?

超文本傳輸協(xié)議(Hyper Text Transfer Protocol)。

HTTP 是做什么的?

規(guī)范了瀏覽器和服務(wù)器交互的數(shù)據(jù)格式。

HTTP 的特點(diǎn)?

  1. 簡(jiǎn)單快捷: 客戶(hù)向服務(wù)器請(qǐng)求服務(wù)時(shí), 只需傳送請(qǐng)求方法和路徑。請(qǐng)求方法常用的有 GET、HEAD、POST。每種方法規(guī)定了客戶(hù)與服務(wù)器聯(lián)系的類(lèi)型不同。由于 HTTP 協(xié)議簡(jiǎn)單,使得 HTTP 服務(wù)器的程序規(guī)模小,因而通信速度很快。
  2. 靈活:HTTP 允許傳輸任意類(lèi)型的數(shù)據(jù)對(duì)象。正在傳輸?shù)念?lèi)型由 Content-Type 加以標(biāo)記。
  3. 無(wú)連接:無(wú)連接的含義是限制每次連接只處理一個(gè)請(qǐng)求。服務(wù)器處理完客戶(hù)的請(qǐng)求,并收到客戶(hù)的應(yīng)答后,即斷開(kāi)連接。采用這種方式可以節(jié)省傳輸時(shí)間。
  4. 無(wú)狀態(tài):HTTP 協(xié)議是無(wú)狀態(tài)協(xié)議。無(wú)狀態(tài)是指協(xié)議對(duì)于事務(wù)處理沒(méi)有記憶能力。缺少狀態(tài)意味著如果后續(xù)處理需要前面的信息,則它必須重傳,這樣可能導(dǎo)致每次連接傳送的數(shù)據(jù)量增大。另一方面,在服務(wù)器不需要先前信息時(shí)它的應(yīng)答就較快。 1.1版本之后支持可持續(xù)性連接。

HTTP 交互流程

  1. 客戶(hù)端和服務(wù)器端建立連接
  2. 客戶(hù)端發(fā)送請(qǐng)求數(shù)據(jù)到服務(wù)器端(HTTP 協(xié)議)
  3. 服務(wù)器端接收到請(qǐng)求后, 進(jìn)行處理, 然后將處理結(jié)果響應(yīng)客戶(hù)端(HTTP協(xié)議)
  4. 關(guān)閉客戶(hù)端和服務(wù)器端的連接(HTTP1.1 后不會(huì)立即關(guān)閉)

HTTP 協(xié)議之請(qǐng)求格式

請(qǐng)求格式的結(jié)構(gòu): 請(qǐng)求頭:請(qǐng)求方式、 請(qǐng)求的地址和 HTTP 協(xié)議版本 請(qǐng)求行: 消息報(bào)頭, 一般用來(lái)說(shuō)明客戶(hù)端要使用的一些附加信息 空行:位于請(qǐng)求行和請(qǐng)求數(shù)據(jù)之間, 空行是必須的。 請(qǐng)求數(shù)據(jù): 非必須。

HTTP協(xié)議的請(qǐng)求方式

  • 根據(jù) HTTP 標(biāo)準(zhǔn), HTTP 請(qǐng)求可以使用多種請(qǐng)求方法。
  • HTTP1.0 定義了三種請(qǐng)求方法: GET, POST 和 HEAD 方法。
  • HTTP1.1 新增了五種請(qǐng)求方法: OPTIONS, PUT, DELETE, TRACE 和CONNECT 方法。
get 和 post 請(qǐng)求方式的區(qū)別:
  • get 請(qǐng)求方式: 請(qǐng)求數(shù)據(jù)會(huì)以? 的形式隔開(kāi)拼接在請(qǐng)求頭中, 不安全, 沒(méi)有請(qǐng)求實(shí) 體部分。HTTP 協(xié)議雖然沒(méi)有規(guī)定請(qǐng)求數(shù)據(jù)的大小, 但是瀏覽器對(duì) URL 的長(zhǎng)度是有限制的, 所以 get 請(qǐng)求不能攜帶大量的數(shù)據(jù)。
  • post 請(qǐng)求方式: 請(qǐng)求數(shù)據(jù)在請(qǐng)求實(shí)體中進(jìn)行發(fā)送, 在 URL 中看不到具體的請(qǐng)求數(shù)據(jù),安全。 適合數(shù)據(jù)量大的數(shù)據(jù)發(fā)送。

HTTP響應(yīng)

響應(yīng)格式的幀結(jié)構(gòu)

響應(yīng)行(狀態(tài)行): HTTP 版本、 狀態(tài)碼、 狀態(tài)消息 響應(yīng)頭: 消息報(bào)頭, 客戶(hù)端使用的附加信息 空行: 響應(yīng)頭和響應(yīng)實(shí)體之間的, 必須的。 響應(yīng)實(shí)體: 正文, 服務(wù)器返回給瀏覽器的信息

常見(jiàn)的狀態(tài)碼

  • 200 OK //客戶(hù)端請(qǐng)求成功 
  • 400 Bad Request //客戶(hù)端請(qǐng)求有語(yǔ)法錯(cuò)誤, 不能被服務(wù)器所理 解 
  • 401 Unauthorized //請(qǐng)求未經(jīng)授權(quán), 這個(gè)狀態(tài)代碼必須和 WWW-Authenticate 報(bào)頭域一起使用 
  • 403 Forbidden //服務(wù)器收到請(qǐng)求, 但是拒絕提供服務(wù) 404 Not Found //請(qǐng)求資源不存在, eg: 輸入了錯(cuò)誤的 URL
  •  500 Internal Server Error //服務(wù)器發(fā)生不可預(yù)期的錯(cuò)誤 
  • 503 Server Unavailable //服務(wù)器當(dāng)前不能處理客戶(hù)端的請(qǐng)求, 一段時(shí)間后可能恢復(fù)正常。

下面基于socket編寫(xiě)一個(gè)簡(jiǎn)單的HTTP server。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

class SocketHandler implements Runnable {

    final static String CRLF = "\r\n";   // 1

    private Socket clientSocket;

    public SocketHandler(Socket clientSocket) {
        this.clientSocket = clientSocket;
    }

    public void handleSocket(Socket clientSocket) throws IOException {

        BufferedReader in = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream())
                );
        PrintWriter out = new PrintWriter(
                new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())),
                true
                );

        String requestHeader = "";
        String s;
        while ((s = in.readLine()) != null) {
            s += CRLF;  // 2 很重要,默認(rèn)情況下in.readLine的結(jié)果中`\r\n`被去掉了
            requestHeader = requestHeader + s;
            if (s.equals(CRLF)){ // 3 此處HTTP請(qǐng)求頭我們都得到了;如果從請(qǐng)求頭中判斷有請(qǐng)求正文,則還需要繼續(xù)獲取數(shù)據(jù)
                break;
            }
        }
        System.out.println("客戶(hù)端請(qǐng)求頭:");
        System.out.println(requestHeader);

        String responseBody = "客戶(hù)端的請(qǐng)求頭是:\n"+requestHeader;

        String responseHeader = "HTTP/1.0 200 OK\r\n" +
                "Content-Type: text/plain; charset=UTF-8\r\n" +
                "Content-Length: "+responseBody.getBytes().length+"\r\n" +
                "\r\n";
        // 4 問(wèn)題來(lái)了:1、瀏覽器如何探測(cè)編碼 2、瀏覽器受到content-length后會(huì)按照什么方式判斷?漢字的個(gè)數(shù)?字節(jié)數(shù)?

        System.out.println("響應(yīng)頭:");
        System.out.println(responseHeader);

        out.write(responseHeader);
        out.write(responseBody);
        out.flush();

        out.close();
        in.close();
        clientSocket.close();

    }

    @Override
    public void run() {
        try {
            handleSocket(clientSocket);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }

}

public class MyHTTPServer {

    public static void main(String[] args) throws Exception {

        int port = 8000;
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("啟動(dòng)服務(wù),綁定端口: " + port);

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(30);  // 5

        while (true) {  // 6
            Socket clientSocket = serverSocket.accept();
            System.out.println("新的連接"
                    + clientSocket.getInetAddress() + ":" + clientSocket.getPort());
            try {
                fixedThreadPool.execute(new SocketHandler(clientSocket));
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }
}

這是一個(gè)實(shí)現(xiàn) HTTP 1.0 的服務(wù)器,對(duì)于所有的 HTTP 請(qǐng)求,會(huì)把 HTTP 請(qǐng)求頭響應(yīng)回去。 這個(gè)程序說(shuō)明了web服務(wù)器處理請(qǐng)求的基本流程,JSP、Servlet、Spring MVC 等只是在 這個(gè)基礎(chǔ)上嫁了許多方法,以讓我們更方面的編寫(xiě) web 應(yīng)用。web 服務(wù)器不僅可以基于多線程, 也可以基于多進(jìn)程、Reactor模型等。

測(cè)試程序:
運(yùn)行上面的程序。我們使用 curl 訪問(wèn)http://127.0.0.1(也可以使用瀏覽器):

$ curl -i http://127.0.0.1:8000
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

客戶(hù)端的請(qǐng)求頭是:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

Java 程序輸出:

啟動(dòng)服務(wù),綁定端口: 8000
新的連接/127.0.0.1:36463
新的連接/127.0.0.1:36463客戶(hù)端請(qǐng)求頭:
GET / HTTP/1.1
User-Agent: curl/7.35.0
Host: 127.0.0.1:8000
Accept: */*

響應(yīng)頭:
HTTP/1.0 200 OK
Content-Type: text/plain; charset=UTF-8
Content-Length: 106

程序解析:

// 1:定義了 HTTP 頭的換行符。

// 2:in.readLine() 的結(jié)果默認(rèn)不帶換行符,這里把它加上。(這不是強(qiáng)制的,主要看你的程序邏輯需不需要, 這個(gè)程序的目標(biāo)是把 HTTP 請(qǐng)求頭響應(yīng)回去)。

// 3:此時(shí)s是一個(gè)空行,根據(jù) HTTP 協(xié)議,整個(gè)請(qǐng)求頭都得到了。

// 4:Content-Length 的值是字節(jié)的數(shù)量。

// 5:線程池。

// 6:這個(gè)循環(huán)不停監(jiān)聽(tīng)socket連接,使用 SocketHandler 處理連入的 socket,而這個(gè)處理是放在線程池中的。

HTTP 1.1:
HTTP 1.1 也是在這個(gè)思路的基礎(chǔ)上實(shí)現(xiàn)的,即多個(gè) HTTP 請(qǐng)求都在一個(gè) TCP 連接中傳輸。對(duì)于 HTTP 1.1,如何區(qū)分出每個(gè)HTTP請(qǐng)求很重要, 比較簡(jiǎn)單的可以是用過(guò)Content-Length判斷一條請(qǐng)求是否結(jié)束。如果一個(gè) HTTP 請(qǐng)求數(shù)據(jù)較多,往往采用 Chunked 方式, 可以參考 Chunked transfer encoding。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)