1. 程式人生 > >Java網路程式設計-HTTP協議

Java網路程式設計-HTTP協議

HTTP協議的定義

這篇文章暫時不研究HTTP底層的TCP/IP的握手和揮手過程,只從表面的互動流程分析HTTP協議。

HTTP英文全稱是Hypertext Transfer Protpcol,也就是超文字傳輸協議。HTTP是一個標準,定義了Web客戶端如何與伺服器對話以及資料如何從伺服器傳回到客戶端。在日常開發和使用過程中,HTTP經常被認為是一種用於傳輸HTML檔案和檔案中內嵌的圖片的協議或者手段,實際上HTTP是一種通用的網路資料傳輸格式,它的傳輸內容不僅僅侷限於HTML檔案或者圖片,也可以用來傳輸Microsoft Word文件甚至是Windows的exe檔案等等,所有可以用位元組序列表示的資料都可以使用HTTP進行傳輸。

HTTP通過TCP/IP進行資料傳輸,如果忽略底層的TCP協議的握手和揮手的細節,對於從客戶端到伺服器的每一個請求和請求的響應,在HTTP1.0有下面幾個步驟:

  • 1、預設情況下,客戶端在埠80開啟與伺服器的一個TCP連線,當然也可以指定其他的埠。
  • 2、客戶端向伺服器傳送訊息,請求指定路徑上的資源。一個HTTP請求包括一個首部,可選項包括一個空行和這次請求的資料。
  • 3、伺服器向客戶端傳送響應。響應以響應碼開頭,接著是包含元資料的首部,可選項包括一個空行以及所請求的文件資料或者錯誤資訊。
  • 4、伺服器關閉TPC連線。

在HTTP1.1(目前最常用的就是HTTP1.1)以及以後的HTTP版本中,可以通過一個TCP連線連續傳送多個請求和接收多個響應。也就是說,上面的1和4步驟中間的2和3步驟可以反覆執行多次。另外,HTTP1.1中,請求資料和響應資料可以分塊傳送,提高了擴充套件性。

HTTP請求方法

HTTP中定義了多種請求方法,用於標識當次請求需要完成什麼型別的操作,常用的HTTP請求方法有GET、HEAD、PUT、POST、PATCH、TRACE、OPTIONS、DELETE。

HTTP請求方法 描述 是否安全 是否冪等
GET 通常用於請求伺服器獲取某個資源
HEAD 類似於GET,但是響應結果中不包含響應體,只包含協議資訊和首部,通常用於測試資源是否存在或者是否被修改 -
POST 客戶端向伺服器提交資料(支援HTML的表單資料),可能會導致新的資源的建立或者已有資源的修改
PUT 從客戶端向伺服器傳送的資料取代指定的文件的內容(全部取代)
PATCH 客戶端向伺服器傳送的資料取代指定的文件的內容(部分取代)
TRACE 回顯客戶端請求伺服器的原始請求報文,用於"迴環"診斷 -
OPTIONS 請求伺服器獲取伺服器支援的各種功能,可以詢問伺服器支援什麼型別的HTTP方法,一般用於效能測試 -
DELETE 請求伺服器刪除指定的資源

上面說到的"是否安全"的選項是"是",意味著使用該種HTTP請求方法不會發生任何資料的修改或者更新動作,也就是請求多次也不會影響到資源的狀態。如果"是否冪等"的選項是"是",意味著使用該HTTP請求方法請求多次HTTP呼叫,無論呼叫多少次,請求結果或者資源的狀態是一樣的(可以理解為只有首次呼叫是真正修改了資源的狀態,從第二次呼叫開始後面的呼叫只獲取到第一次呼叫的結果)。HTTP方法的安全性和冪等性是我們在設計HTTP介面時候需要重點考慮的兩個因素。

值得注意的是:上面提到的POST和PUT方法的功能可以理解為相同的,兩者的主要區別在於POST不是冪等的,而PUT是冪等的。在目前的Web開發中,POST方法已經被濫用,一般很少人會使用PUT,除非是推崇RESTFUL風格程式設計。PUT方法和PATCH方法的功能類似,都是用客戶端請求的資料去替換掉伺服器中指定文件中的內容,不過PUT方法是全部替換,而PATCH方法是部分替換。

PS:上面的方法只是HTTP協議中的請求方法的一些規範,沒有硬性規定一定要遵循。

常見的HTTP狀態碼

JDK中常見的HTTP狀態碼可以在類java.net.HttpURLConnection中找到,總結一下如下:

狀態碼 狀態碼訊息 含義 HttpURLConnection中的常量 簡單描述
1xx - 資訊狀態碼。 - 不常見,暫不考慮
100 Continue 伺服器準備接受請求主體,客戶端傳送請求主體;這允許客戶端在請求傳送大量資料之前詢問伺服器是否接受請求。 - 不常見,暫不考慮
101 Switching Protocols 伺服器接受客戶端在Upgrade首部欄位中要求改變應用的協議請求,如從HTTP轉換為WebSockets。 - 不常見,暫不考慮
2xx - 表示請求成功。 - -
200 OK 最常見的響應碼,代表請求成功。如果請求方法是GET或者POST,所請求的資料與正常的首部都包含在響應體中。如果請求方法是HEAD,則只包含首部資訊。 HTTP_OK 處理請求成功
201 Created 伺服器已經在響應體中指定的URL建立了對應的資源。客戶端現在應當嘗試載入該URL。這個響應碼只在響應POST請求時傳送。 HTTP_CREATED 建立成功
202 Accepted 表示請求已經被處理,但是處理尚未結束,所以不會返回任何響應資料。 HTTP_ACCEPTED 接受請求
203 Non-Authoritative Information 由快取代理或者其他本地源返回資源的表示,不能保證是最新的。 HTTP_NOT_AUTHORITATIVE 無權威的返回結果
204 No Content 伺服器已經成功處理了該請求,但是沒有資訊發回給客戶端。一般是由於伺服器上的表單處理邏輯的問題,只接收資料不返回資料。 HTTP_NO_CONTENT 無返回內容
205 Reset Content 伺服器已經成功處理了該請求,但是沒有資訊發回給客戶端。客戶端應該清除傳送請求的表單資訊。 HTTP_RESET 重置內容
206 Partial Content 伺服器返回客戶端請求的資源的部分內容,而不是整個文件。 HTTP_PARTIAL 部分內容
3xx - 重定向。 - -
300 Multiple Choices 伺服器為所請求的文件提供一組不同的表示。 HTTP_MULT_CHOICE 多重選擇
301 Moved Permanently 資源已經移動到一個新的URL。客戶端應當自動載入這個URL的資源。 HTTP_MOVE_PERM 永久移動
302 Moved Temporarity 資源暫時移動到一個新的URL,但其位置在不久的將來還會再次改變。 HTTP_MOVE_TEMP 臨時移動
4xx - 客戶端錯誤 - -
400 Bad Request 客戶端向伺服器發出的請求使用了不正確的語法。 HTTP_BAD_REQUEST 錯誤請求
401 Unauthorized 訪問這個URL需要身份驗證,一般是使用者名稱和口令。 HTTP_UNAUTHORIZED 未授權
403 Forbidden 伺服器理解請求,但是有意拒絕進行處理。 HTTP_FORBIDDEN 禁止訪問
404 Not Found 最常見的錯誤響應,指示伺服器找不到所請求的資源。 HTTP_NOT_FOUND 未找到資源
405 Method Not Allowed 請求方法不支援用於請求指定的資源。 HTTP_BAD_METHOD 方法禁用
406 Not Acceptable 所請求的資源不能以客戶端希望的格式提供,客戶端期望的格式由請求HTTP首部Accept欄位指定。 HTTP_NOT_ACCEPTABLE 不接受
5xx - 服務端錯誤 - -
500 Internale Server Error 伺服器內部異常。 HTTP_SERVER_ERROR 伺服器異常
501 Not Implemented 伺服器不具備完成請求的功能。 HTTP_NOT_IMPLEMENTED 尚未實現
502 Bad Gateway 伺服器作為閘道器或代理,從上游伺服器收到無效響應。 HTTP_BAD_GATEWAY 錯誤閘道器
503 Service Unavailable 伺服器暫時無法處理請求,可能是超負荷或者維護等原因。 HTTP_UNAVAILABLE 服務不可用

簡單概括如下:

  • 響應碼100-199表示一個提供資訊的響應。
  • 響應碼200-299表示請求成功。
  • 響應碼300-399表示重定向。
  • 響應碼400-499表示一個客戶端引發的錯誤。
  • 響應碼500-599表示一個伺服器引發的錯誤。

常見的HTTP首部

下面簡單列舉一些比較常用的首部以及它們的作用。

User-Agent

User-Agent一般作為請求首部,用於告知伺服器當前客戶端使用的是什麼瀏覽器,翻譯過來就是使用者代理,作用是允許伺服器響應請求時候針對客戶端使用者代理的型別優化返回的資料或者檔案。例如使用Chrome傳送請求時,User-Agent如下:

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36

Host

Host一般作為請求首部,用於指定接收該請求的伺服器的主機名和埠號。例如:

Host: www.importnew.com

Accept

Accept一般作為請求首部,它的作用是告知伺服器它可以使用或者想要什麼已經不能使用或者不想要什麼。下面是幾個Accept首部以及它們的作用:

首部 作用
Accept 告知伺服器客戶端可以接收和處理哪些媒體型別
Accept-Charset 告知伺服器客戶端可以接收和處理哪些字符集
Accept-Encoding 告知伺服器客戶端可以接收和處理哪些編碼方式
Accept-Language 告知伺服器客戶端可以接收和處理哪些語言

Accept首部用於指定接收媒體類型別的時候,需要指定型別和子型別,這是因為媒體型別(MIME)本來就是按二級分類的,例如JPEG影象的媒體型別是image/jpeg,型別是image,子型別是jpeg。MIME已經定義了八種頂級的型別:

  • text/*表示人可讀的文字。
  • image/*表示圖片。
  • model/*表示3D模型,如VRML檔案。
  • audio/*表示音訊。
  • video/*表示多媒體圖片、視訊,也可能是音訊。
  • application/*表示二進位制資料。
  • message/*表示協議特定的信封,如Email訊息和HTTP響應。
  • muitipart/*表示多個文件和資源的容器。

舉個例子,如果客戶端只接收JSON資料:

Accept: application/json

Referer

Referer一般作為請求首部,它提供了包含當前請求的URL的文件的URL,也就是當前請求的上一個來源的文件,一般用作防盜鏈。例如www.baidu.com/search?name=doge,伺服器在處理此請求的時候,需要判斷Referer是否為www.baidu.comwww.baidu.com/search的上一個文件來源必須是www.baidu.com,否則伺服器應該拒絕該請求。

Cookie一般作為請求首部,客戶端通過它向伺服器傳送一個或者多個令牌,原則上Cookie並不是安全的首部,Cookie的內容也會快取在客戶端。一般在Servlet應用中,Cookie是識別當前使用者,實現持久會話的最佳方式。從過期時間分類來看,Cookie分為會話Cookie和持久Cookie,會話Cookie的過期時間比較短,持久Cookie的過期時間比較長或者不會過期,Cookie的過期策略等控制應該由服務端控制。由於Cookie是直接暴露在客戶端,一般不能使用Cookie存放敏感的資料,需要存放敏感資料可以考慮使用資料加密處理。

Cookie: uid=10086; domain="localhost"

Set-Cookie一般作為響應首部,和Cookie對應,表示伺服器設定成功的Cookie。

Cache-Control

Cache-Control一般作為請求首部,告知伺服器對當前的請求的響應結果進行快取相關操作。Cache-Control支援的值比較多,這裡不展開細節,常見的如no-cache表示在沒有成功通過源站校驗的情況下不得使用快取,如max-age表示響應結果需要快取到指定的最大時間。

Content-Type

Content-Type是通用首部,可以作為請求首部或者響應首部,它的作用是告知伺服器或者客戶端當前請求或者響應結果的內容(媒體)型別。

Content-Length

Content-Length是通用首部,可以作為請求首部或者響應首部,它的作用是告知伺服器或者客戶端當前請求或者響應資料體的長度。

Content-Encoding

Content-Encoding一般作為響應首部,與Accept-Encoding對應,用於伺服器告知客戶端當前響應結果的內容編碼。

Content-Language

Content-Language一般作為響應首部,與Accept-Language對應,用於伺服器告知客戶端當前響應結果的內容語言。

Connection

Connection一般作為請求首部,表示是否需要持久連線。在HTTP1.1中,如果指定為Keep-Alive,可以提供持久連線,提高Socket的複用率從而降低多次連線的效能消耗。下面有一個小節專門介紹Keep-Alive。

Orgin

Origin一般作為請求首部,指明當前的請求是一個針對跨域資源共享的請求(該請求要求伺服器在響應中加入一個Access-Control-Allow-Origin的訊息頭,表示訪問控制所允許的來源)。

Origin: http://www.baidu.com

Access-Control-Allow-Origin

Access-Control-Allow-Origin一般作為響應首部,和Origin對應,表示伺服器允許的該跨域資源共享的請求來源。

Access-Control-Allow-Origin: http://www.baidu.com

Server

Server一般作為響應首部,用於告知客戶端伺服器的相關資訊。

HTTP請求體

如果採用GET請求方法,只需要向遠處伺服器提供URL,URL中的路徑和查詢字串就可以匹配到需要查詢的資源。但是URL中無法提供詳細的客戶端資訊。另外,像POST和PUT這些請求方法所攜帶的資料體有可能比較大,無法放在URL的查詢字串。因此HTTP需要請求體。HTTP請求體包括下面四個部分:

  • 1、一個起始請求行,包括HTTP方法、路徑、查詢字串以及HTTP版本。
  • 2、HTTP請求的首部。
  • 3、一個空行(兩個連續的回車或者換行對)。
  • 4、請求資料體。

文字描述可能比較抽象,用圖表示如下:

http-1

PS:space代表空格,\r\n代表換行。

舉個例子:

GET /wp-admin/admin-ajax.php?postviews_id=23996&action=postviews&_=1538708851063 HTTP/1.1
Host: www.importnew.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
Referer: http://www.importnew.com/23996.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

postviews_id=23996&action=postviews&_=1538708851063

HTTP響應體

響應體和請求體的格式類似,主要是返回伺服器的響應資料到客戶端,包括伺服器的一些資訊和響應資料體。HTTP響應體主要包括下面的四個部分:

  • 1、一個起始響應行,包括HTTP版本、狀態碼、狀態碼描述。
  • 2、HTTP響應的首部。
  • 3、一個空行(兩個連續的回車或者換行對)。
  • 4、響應資料體。

文字描述可能比較抽象,用圖表示如下:

http-2

PS:space代表空格,\r\n代表換行。

舉個例子:

HTTP/1.1 200 OK
Server: nginx
Date: Fri, 05 Oct 2018 03:07:37 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=2
Vary: Accept-Encoding
X-Powered-By: PHP/5.3.3
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Content-Encoding: gzip

2995

Keep-Alive

在使用HTTP1.0的時候會為每個請求開啟一個新的TCP連線,實際上,這導致了一個典型Web會話中開啟和關閉所有連線所花費的事件遠遠大於實際傳輸資料所消耗的時間,特別是響應結果包含很多小文件的會話。對於使用SSL或者TLS加密的HTTPS連線,這個問題更加嚴重,因為建立一個安全的Socket的握手過程遠比建立常規的Socket需要更多的工作。

在HTTP1.1和後面的版本中,伺服器不必在返送響應之後就關閉連線。已經建立的連線可以保持開啟,在同一個Socket上等待來自客戶端的新請求。簡單來說,就是可以在一個TCP連線上連續傳送多個請求和連續進行多個請求的響應。

客戶端可以在HTTP請求首部中新增一個Connection請求頭,指定值為Keep-Alive,這樣就能實現Socket的重用:

Connection: Keep-Alive

HTTP1.1或者之後的版本,Keep-Alive是預設開啟的,不需要顯式指定,如果需要關閉可以設定為close:

Connection: close

一旦開啟了Keep-Alive,伺服器在關閉一個Socket連線之前,如果有新的客戶端再次連線到伺服器,那麼就是重用Socket。在JDK中可以通過系統屬性來控制如果使用HTTP的Keep-Alive:

  • http.keepAlive:預設值為true,預設開啟HTTP的Keep-Alive。
  • http.maxConnections:同時保持開啟的Socket數量的最大值,預設值為5。
  • http.keepAlive.remainingData:預設值為false,如果設定為true,則JDK在丟棄連線之後會完成剩餘資料的清理。
  • sun.net.http.errorstream.enableBuffering:預設值為false,如果設定為true,則嘗試快取400和500狀態碼的相對小的錯誤流,從而能釋放連線以備後續使用。
  • sun.net.http.errorstream.bufferSize:為快取錯誤流的緩衝區的位元組大小,預設值為4096位元組,只有上一項為true的時候才有意義。
  • sun.net.http.errorstream.timeout:預設值為300ms,讀取錯誤流超時的毫秒數。

Cookie和Cookie管理

很多網站使用一些小文字串在連線之間儲存持久的客戶端狀態,這些小文字串稱為Cookie(中文翻譯為:小甜點)。Cookie在請求和響應的首部從伺服器傳到客戶端,再從客戶端傳回伺服器,伺服器使用Cookie來指示sessionID、購物車內容、登入憑據等。 除了簡單的name=value對,Cookie可以有多個屬性來控制它們的作用域,包括過期日期、路徑、域、埠、版本和安全選項。

JDK中java.net.CookieStore類提供了對Cookie的增刪查操作,它的預設實現是java.net.InMemoryCookieStore,如果實現CookieStore,JDK中的Cookie預設是存放在記憶體中的。另外,java.net.CookieManager內部持有CookiePolicy和CookieStore,定義了一系列管理Cookie的方法,一般通過CookieManager操作Cookie,當然也可以通過實現CookieStore,覆蓋預設的CookieManager來實現Cookie的自定義管理。

小結

(本文完 c-2-d e-20181005)