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.com
,www.baidu.com/search
的上一個文檔來源必須是www.baidu.com
,否則服務器應該拒絕該請求。
Cookie
Cookie一般作為請求首部,客戶端通過它向服務器傳送一個或者多個令牌,原則上Cookie並不是安全的首部,Cookie的內容也會緩存在客戶端。一般在Servlet應用中,Cookie是識別當前用戶,實現持久會話的最佳方式。從過期時間分類來看,Cookie分為會話Cookie和持久Cookie,會話Cookie的過期時間比較短,持久Cookie的過期時間比較長或者不會過期,Cookie的過期策略等控制應該由服務端控制。由於Cookie是直接暴露在客戶端,一般不能使用Cookie存放敏感的數據,需要存放敏感數據可以考慮使用數據加密處理。
Cookie: uid=10086; domain="localhost"
Set-Cookie
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、請求數據體。
文字描述可能比較抽象,用圖表示如下:
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、響應數據體。
文字描述可能比較抽象,用圖表示如下:
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)
技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):
娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力: