TCP/IP 網絡分層模型 | 說明 | OSI 網絡分層模型 | 說明 |
---|---|---|---|
應用層 (application layer) | 因爲下面的三層把基礎打得很是好,因此在這一層就百花齊放了,有各類面向具體應用的協議。例如 Telnet、SSH、FTP、SMTP 等等,固然還有咱們的 HTTP。傳輸單位則是消息或報文(message)。 | 應用層 (application layer) | 面向具體的應用傳輸數據。 |
- | - | 表示層(presentation layer) | 把數據轉換爲合適、可理解的語法和語義。 |
- | - | 會話層(session layer) | 維護網絡中的鏈接狀態,即保持會話和同步。 |
傳輸層(transport layer) | 這個層次協議的職責是保證數據在 IP 地址標記的兩點之間可靠地傳輸,是 TCP 協議工做的層次,另外還有它的一個小夥伴 UDP。傳輸單位是段(segment)。 | 傳輸層(transport layer) | 至關於 TCP/IP 裏的傳輸層。 |
網際層(internet layer) | IP 協議就處在這一層。由於 IP 協議定義了 IP 地址 的概念,因此就能夠在 連接層 的基礎上,用 IP 地址取代 MAC 地址 ,把許許多多的局域網、廣域網鏈接成一個虛擬的巨大網絡,在這個網絡裏找設備時只要把 IP 地址再「翻譯」成 MAC 地址就能夠了。傳輸單位是包(packet)。 | 網絡層(network layer) | 至關於 TCP/IP 裏的網際層。 |
連接層 (link layer/MAC) | 負責在以太網、WiFi 這樣的底層網絡上發送原始數據包,工做在網卡這個層次,使用 MAC 地址來標記網絡上的設備 ,因此有時候也叫 MAC 層。傳輸單位是幀(frame)。 | 數據鏈路層(data link layer) | 至關於 TCP/IP 的連接層。 |
物理層(physical layer) | 網絡的物理形式,例如電纜、光纖、網卡、集線器等等。 |
HTTP 是什麼?
超文本傳輸協議(HTTP)是一個用於傳輸超媒體文檔(例如 HTML)的應用層協議。javascript
它是爲 Web 瀏覽器與 Web 服務器之間的通訊而設計的,但也能夠用於其餘目的。css
HTTP 遵循經典的客戶端-服務端模型,客戶端打開一個鏈接以發出請求,而後等待直到收到服務器端響應。html
HTTP 是無狀態協議,這意味着服務器不會在兩個請求之間保留任何數據(狀態)。java
HTTP 協議的請求報文和響應報文的結構基本相同,由三大部分組成:git
請求報文的起始行叫作請求行(request line),它簡要地描述了 客戶端想要如何操做服務器端的資源 。github
請求方法(Method -> GET/POST)+ 請求目標(Path -> URI)+ 版本號(Version of the protocol -> HTTP 1.0 / 1.1 / 2.0)
這三個部分一般使用空格(space)來分隔,最後要用 CRLF
換行表示結束。web
目前 HTTP/1.1 規定了八種方法,單詞 都必須是大寫的形式 ,我先簡單地列把它們列出來,後面再詳細講解。面試
GET 和 POST 的區別
做用算法
GET 用於獲取資源,而 POST 用於傳輸實體主體。數據庫
參數
GET 和 POST 的請求都能使用額外的參數,可是 GET 的參數是以查詢字符串出如今 URL 中,而 POST 的參數存儲在實體主體中。
由於 URL 只支持 ASCII 碼,所以 GET 的參數中若是存在中文等字符就須要先進行編碼。
http://www.baidu.com/?百度一下,你就知道 http://www.baidu.com/?%E7%99%BE%E5%BA%A6%E4%B8%80%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%B0%B1%E7%9F%A5%E9%81%93
安全
在 HTTP 協議裏,所謂的 安全 是指請求方法不會「破壞」服務器上的資源,即不會對服務器上的資源形成實質的修改。
GET 方法是安全的,而 POST 卻不是,由於 POST 的目的是傳送實體主體內容,這個內容多是用戶上傳的表單數據,上傳成功以後,服務器可能把這個數據存儲到數據庫中,所以狀態也就發生了改變。
冪等性
所謂的 冪等(Idempotent) 其實是一個數學用語,被借用到了 HTTP 協議裏,意思是屢次執行相同的操做,結果也都是相同的,即屢次冪後結果相等。
GET /pageX HTTP/1.1 是冪等的,連續調用屢次,客戶端接收到的結果都是同樣的:
GET /pageX HTTP/1.1 GET /pageX HTTP/1.1 GET /pageX HTTP/1.1 GET /pageX HTTP/1.1
POST /add_row HTTP/1.1 不是冪等的,若是調用屢次,就會增長多行記錄:
POST /add_row HTTP/1.1 -> Adds a 1nd row POST /add_row HTTP/1.1 -> Adds a 2nd row POST /add_row HTTP/1.1 -> Adds a 3rd row
緩存
GET是可緩存的,POST不可緩存。
XMLHttpRequest
XMLHttpRequest 是一個 API,它爲客戶端提供了在客戶端和服務器之間傳輸數據的功能。它提供了一個經過 URL 來獲取數據的簡單方式,而且不會使整個頁面刷新。這使得網頁只更新一部分頁面而不會打擾到用戶。XMLHttpRequest 在 AJAX 中被大量使用。
URI(Uniform Resource Identifier) 本質上是一個字符串,這個字符串的做用是 惟一地標記資源的位置或者名字 。
http
、https
。後面必須是 三個特定的字符 ://
。host:port
,端口可省略。/
開始,也就是必須包含 /
。https://search.jd.com/Search?keyword=openresty&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=openresty&psort=3&click=0 // scheme -> https , authority -> search.jd.com , path -> /Search... , query -> keyword=openresty...
URI只能支持ASCII,對於ASCII 碼之外的字符集和特殊字符,URI經過百分號編碼( Percent Encoding)進行轉義,轉義規則是
直接把非 ASCII 碼或特殊字符轉換成十六進制字節值 ,而後前面再加上一個
%
。
http://www.baidu.com/?百度一下,你就知道 http://www.baidu.com/?%E7%99%BE%E5%BA%A6%E4%B8%80%E4%B8%8B%EF%BC%8C%E4%BD%A0%E5%B0%B1%E7%9F%A5%E9%81%93
響應報文的起始行叫作狀態行(status line),意思是 服務器響應的狀態。
版本號(Version of the protocol -> HTTP 1.0 / 1.1 / 2.0)+ 狀態碼(Status code -> 200 / 404)+ 緣由(Status message -> 數字狀態碼補充)
這三個部分一樣使用空格(space)來分隔,最後要用 CRLF
換行表示結束。
狀態碼 | 類別 | 含義 |
---|---|---|
1XX | Informational(信息性狀態碼) | 接收的請求正在處理 |
2XX | Success(成功狀態碼) | 請求正常處理完畢 |
3XX | Redirection(重定向狀態碼) | 須要進行附加操做以完成請求 |
4XX | Client Error(客戶端錯誤狀態碼) | 服務器沒法處理請求 |
5XX | Server Error(服務器錯誤狀態碼) | 服務器處理請求出錯 |
1XX 信息
2XX 成功
HEAD
請求,服務器返回的響應頭都會有 body 數據。3XX 重定向
302 Found :臨時重定向,意思是請求的資源還在,但須要暫時用另外一個 URI 來訪問。
301 和 302 都會在響應頭裏使用字段 Location
,指明後續要跳轉的 URI,瀏覽器會重定向到新的 URI。
304 Not Modified :若是請求報文首部包含一些條件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,若是不知足條件,則服務器會返回 304 狀態碼。
4XX 客戶端錯誤
剩下的錯誤代碼較明確地說明了錯誤的緣由:
5XX 服務器錯誤
請求行 + 頭部字段 = 請求頭 ; 狀態行 + 頭部字段 = 響應頭。
請求頭和響應頭的結構是基本同樣的,惟一的區別是起始行,因此請求頭和響應頭裏的字段能夠放在一塊兒介紹。
頭部字段是 key-value
的形式,key
和 value
之間用 :
分隔,最後用 CRLF
換行表示字段結束。
HTTP 頭字段很是靈活,不只能夠使用標準裏的 Host、Connection 等已有頭,也能夠 任意添加自定義頭 ,這就給 HTTP 協議帶來了無限的擴展可能。
不過使用頭字段須要注意下面幾點:
Host
也能夠寫成 host
,但首字母大寫的可讀性更好;-
,但不能使用下劃線 _
。例如,test-name
是合法的字段名,而 test name
、test_name
是不正確的字段名;:
,不能有空格,而 :
後的字段值前能夠有多個空格;Set-Cookie
。頭部字段名 | 說明 |
---|---|
Cache-Control | 控制緩存的行爲 |
Connection | 控制再也不轉發給代理的首部字段、管理持久鏈接 |
Date | 建立報文的日期時間 |
Pragma | 報文指令 |
Trailer | 報文末端的首部一覽 |
Transfer-Encoding | 指定報文主體的傳輸編碼方式 |
Upgrade | 升級爲其餘協議 |
Via | 代理服務器的相關信息 |
Warning | 錯誤通知 |
頭部字段名 | 說明 |
---|---|
Accept | 用戶代理可處理的媒體類型 |
Accept-Charset | 優先的字符集 |
Accept-Encoding | 優先的內容編碼 |
Accept-Language | 優先的語言(天然語言) |
Authorization | Web 認證信息 |
Expect | 期待服務器的特定行爲 |
From | 用戶的電子郵箱地址 |
Host | 請求資源所在服務器 |
If-Match | 比較實體標記(ETag) |
If-Modified-Since | 比較資源的更新時間 |
If-None-Match | 比較實體標記(與 If-Match 相反) |
If-Range | 資源未更新時發送實體 Byte 的範圍請求 |
If-Unmodified-Since | 比較資源的更新時間(與 If-Modified-Since 相反) |
Max-Forwards | 最大傳輸逐跳數 |
Proxy-Authorization | 代理服務器要求客戶端的認證信息 |
Range | 實體的字節範圍請求 |
Referer | 對請求中 URI 的原始獲取方 |
TE | 傳輸編碼的優先級 |
User-Agent | HTTP 客戶端程序的信息 |
頭部字段名 | 說明 |
---|---|
Accept-Ranges | 是否接受字節範圍請求 |
Age | 推算資源建立通過時間 |
ETag | 資源的匹配信息 |
Location | 令客戶端重定向至指定 URI |
Proxy-Authenticate | 代理服務器對客戶端的認證信息 |
Retry-After | 對再次發起請求的時機要求 |
Server | HTTP 服務器的安裝信息 |
Vary | 代理服務器緩存的管理信息 |
WWW-Authenticate | 服務器對客戶端的認證信息 |
頭部字段名 | 說明 |
---|---|
Allow | 資源可支持的 HTTP 方法 |
Content-Encoding | 實體主體適用的編碼方式 |
Content-Language | 實體主體的天然語言 |
Content-Length | 實體主體的大小 |
Content-Location | 替代對應資源的 URI |
Content-MD5 | 實體主體的報文摘要 |
Content-Range | 實體主體的位置範圍 |
Content-Type | 實體主體的媒體類型 |
Expires | 實體主體過時的日期時間 |
Last-Modified | 資源的最後修改日期時間 |
空行,也就是 「CRLF」,十六進制的「0D0A」。
實際傳輸的數據,它不必定是純文本,能夠是圖片、視頻等二進制數據。與 header 對應,被稱爲 body 。
多用途互聯網郵件擴展(Multipurpose Internet Mail Extensions),簡稱爲 MIME。HTTP 取了其中的一部分,用來標記 body 的數據類型 ,這就是咱們日常總能聽到的 MIME type。
經常使用的MIME type:
text/html
(超文本文檔)、text/plain
(純文本)、text/css
(樣式表) 等。image/gif
、image/jpeg
、image/png
等。audio/video
:音頻和視頻數據,例如 audio/mpeg
、video/mp4
等。application/json
,application/javascript
、application/pdf
等,另外,若是實在是不知道數據是什麼類型,像剛纔說的黑盒,就會是 application/octet-stream
,即不透明的二進制數據 。Accept: text/html,application/xml,image/webp,image/png //客戶端可接收類型,","分隔符列出多個類型。 Content-Type: text/html //實體數據的真實類型。
HTTP 在傳輸時爲了節約帶寬,有時候還會 壓縮數據 ,經過Encoding type解壓縮。
經常使用的Encoding type只有下面三種:
Accept-Encoding: gzip, deflate, br //客戶端支持的壓縮格式,若是沒有就表示客戶端不支持壓縮數據。 Content-Encoding: gzip //實體數據使用的壓縮格式,若是沒有就表示相應數據沒被壓縮。
爲了解決語言文字國際化的問題,又引入了兩個概念:語言類型和字符集。
Accept-Language: zh-CN, zh, en //請求頭字段,表示客戶端可理解的天然語言,用`,` 作分隔符列出多個類型。 Content-Language: zh-CN //實體頭字段,告訴客戶端實體數據使用的語言類型。(不會發送) Accept-Charset: gbk, utf-8 //請求頭字段,表示瀏覽器請求的字符集。(不會發送) Content-Type: text/html; charset=utf-8 //通用頭字段裏面包含服務器返回的實體數據字符集。
瀏覽器都支持多種字符集,一般不會發送 Accept-Charset
,而服務器也不會發送 Content-Language
,由於使用的語言徹底能夠由字符集推斷出來,因此在請求頭裏通常只會有 Accept-Language
字段,響應頭裏只會有 Content-Type
字段。
Accept: text/html,application/xml;q=0.9,*/*;q=0.8 Vary: Accept-Encoding,User-Agent,Accept
在 HTTP 協議裏用 Accept、Accept-Encoding、Accept-Language 等請求頭字段進行內容協商的時候,還能夠用一種特殊的 q
參數表示權重來設定優先級,這裏的 q
是 quality factor
的意思。
權重的最大值是 1,最小值是 0.01,默認值是 1,若是值是 0 就表示拒絕。具體的形式是在數據類型或語言代碼後面加一個 ;
,而後是 q=value
。
這裏要提醒的是 ;
的用法,在大多數編程語言裏 ;
的斷句語氣要強於 ,
,而在 HTTP 的內容協商裏卻剛好反了過來,;
的意義是小於 ,
的。
這個 Vary 字段表示服務器依據了 Accept-Encoding、User-Agent 和 Accept 這三個頭字段,而後決定了發回的響應報文。
Vary 字段能夠認爲是響應報文的一個特殊的 版本標記 。每當 Accept 等請求頭變化時,Vary 也會隨着響應報文一塊兒變化。也就是說,同一個 URI 可能會有多個不一樣的「版本」,主要用在傳輸鏈路中間的代理服務器實現緩存服務 ,這個以後講 HTTP 緩存 時還會再提到。
Accept-Encoding: gzip, deflate, br //客戶端支持的壓縮格式,若是沒有就表示客戶端不支持壓縮數據。 Content-Encoding: gzip //實體數據使用的壓縮格式,若是沒有就表示相應數據沒被壓縮。
Transfer-Encoding: chunked //通用頭字段,body 分紅了許多的塊(chunk)逐個發送。
Transfer-Encoding: chunked
和 Content-Length
這兩個字段是 互斥的 ,也就是說響應報文裏這兩個字段不能同時出現,一個響應報文的傳輸要麼是長度已知,要麼是長度未知(chunked)。
CRLF
(回車換行,即 \r\n
)結尾的一行明文,用 16 進制數字表示長度;CRLF
結尾,但數據不包含 CRLF
;0\r\n\r\n
。HTTP/1.1 200 OK Content-Type: text/plain Transfer-Encoding: chunked 7\r\n //length 是以十六進制的形式表示。\r\n 是 CRLF Mozilla\r\n 9\r\n Developer\r\n 7\r\n Network\r\n 0\r\n \r\n
在視頻中拖動進度條,這其實是想獲取一個大文件其中的片斷數據 ,而分塊傳輸並無這個能力。
HTTP 協議爲了知足這樣的需求,提出了 範圍請求 (range requests)的概念,容許客戶端在請求頭裏使用專用字段來表示只獲取文件的一部分 。
Accept-Ranges: none //不支持範圍請求 Accept-Ranges: bytes = x - y //支持範圍請求, x 和 y 是字節爲單位的數據範圍,表示的是偏移量。 //假設文件是100字節 Accept-Ranges: bytes = 0- //表示從文檔起點到文檔終點,至關於 0-99 ,即整個文件; Accept-Ranges: bytes = 10- //是從第 10 個字節開始到文檔末尾,至關於 10-99; Accept-Ranges: bytes = -1 //是文檔最後一個字節,至關於 99-99; Accept-Ranges: bytes = -10 //是從文檔末尾倒數 10 個字節,至關於 90-99。
服務器收到 Range 字段後,須要作四件事:
HTTP/1.1 206 Partial Content Date: Wed, 15 Nov 2015 06:25:24 GMT Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT Content-Range: bytes 21010-47021/47022 Content-Length: 26012 Content-Type: image/gif ... 26012 bytes of partial image data ...
Range: bytes=0-9, 20-29, 30-39 //請求頭
響應頭使用的MIME是multipart/byteranges
,還有個參數boundary=xxx
是用來分隔字節序列的。
每一個字節序列還須要用 Content-Type
和 Content-Range
標記這段數據的類型和所在範圍。
HTTP/1.1 206 Partial Content Content-Type: multipart/byteranges; boundary=00001111 Content-Length: 189 Connection: keep-alive Accept-Ranges: bytes --00001111 Content-Type: text/plain Content-Range: bytes 0-9/96 // this is --00001111 Content-Type: text/plain Content-Range: bytes 20-29/96 ext json d --00001111--
當瀏覽器訪問一個包含多張圖片的 HTML 頁面時,除了請求訪問的 HTML 頁面資源,還會請求圖片資源。若是每進行一次 HTTP 通訊就要新建一個 TCP 鏈接,那麼開銷會很大。
長鏈接只須要創建一次 TCP 鏈接就能進行屢次 HTTP 通訊。
Connection : close
;Connection : Keep-Alive
。隊頭阻塞與短鏈接和長鏈接無關 ,而是由 HTTP 基本的 請求 - 應答 模型所致使的。
由於 HTTP 規定報文必須是 一發一收 ,這就造成了一個先進先出的 串行隊列 。隊列裏的請求沒有優先級,只有入隊的前後順序,排在最前面的請求被優先處理。若是隊首的請求處理的慢,後面的全部請求就要一直等待。這就致使隊頭阻塞。
同時對一個域名發起多個長鏈接,用數量來解決質量的問題 。但這種方式也存在缺陷。若是每一個客戶端都想本身快,創建不少個鏈接,用戶數×併發數就會是個天文數字。服務器的資源根本就扛不住,或者被服務器認爲是惡意攻擊,反而會形成拒絕服務。
HTTP 協議建議客戶端使用併發,但不能「濫用」併發。
HTTP 協議和瀏覽器不是限制併發鏈接數量嗎?好,那我就多開幾個域名,好比 shard1.chrono.com
、shard2.chrono.com
,而這些域名都指向同一臺服務器 www.chrono.com
,這樣實際長鏈接的數量就又上去了,也就解決了隊頭阻塞問題。
https://im.qq.com/download/
在 QQ 主頁點一下 下載 鏈接,會發生什麼呢?
瀏覽器解析文字裏的 URI -> 用這個 URI 發起一個新的 HTTP 請求 -> 得到響應報文,渲染出新 URI 指向的頁面。
這樣由瀏覽器使用者發起的跳轉叫 主動跳轉,由服務器發起的跳轉,被叫作 重定向(Redirection)。
Location:https://im.qq.com/download/ //絕對URI。 Location:/index.html //相對URI,Location是響應頭部字段。
重定向的相關問題
A=>B=>C=>A
的無限循環。瀏覽器必須具備檢測 循環跳轉 的能力,在發現這種狀況時應當中止發送請求並給出錯誤提示。HTTP 協議是無狀態的,主要是爲了讓 HTTP 協議儘量簡單,使得它可以處理大量事務。HTTP/1.1 引入 Cookie 來保存狀態信息。
Cookie 是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器以後向同一服務器再次發起請求時被攜帶上,用於告知服務端兩個請求是否來自同一瀏覽器。因爲以後每次請求都會須要攜帶 Cookie 數據,所以會帶來額外的性能開銷(尤爲是在移動環境下)。
Cookie 曾一度用於客戶端數據的存儲,由於當時並無其它合適的存儲辦法而做爲惟一的存儲手段,但如今隨着現代瀏覽器開始支持各類各樣的存儲方式,Cookie 漸漸被淘汰。新的瀏覽器 API 已經容許開發者直接將數據存儲到本地,如使用 Web storage API(本地存儲和會話存儲)或 IndexedDB。
Cookie: name=value; name2=value2; name3=value3 //請求頭 Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly...
Set-Cookie:Expires=Wed, 21 Oct 2015 07:28:00 GMT; Max-Age=10; //Max-Age優先級更高
Domain 標識指定了哪些主機能夠接受 Cookie。若是不指定,默認爲當前文檔的主機(不包含子域名)。若是指定了 Domain,則通常包含子域名。例如,若是設置 Domain=mozilla.org,則 Cookie 也包含在子域名中(如 developer.mozilla.org)。
Set-Cookie: Domain = mozilla.org;
Path 標識指定了主機下的哪些路徑能夠接受 Cookie(該 URL 路徑必須存在於請求 URL 中)。以字符 %x2F ("/") 做爲路徑分隔符,子路徑也會被匹配。例如,設置 Path=/docs,則如下地址都會匹配:
/docs /docs/Web/ /docs/Web/HTTP
大多數狀況只用一個/
或者直接省略,表示域名下任意路徑都容許使用Cookie。
document.cookie
等一切相關的 API。這樣就會阻止 跨站腳本(XSS)。Secure 表示這個 Cookie 僅能用 HTTPS 協議加密傳輸 ,明文的 HTTP 協議會禁止發送。但 Cookie 自己不是加密的,瀏覽器裏仍是以明文的形式存在。
Cookie 主要用於如下三個方面:
緩存(cache)是一種保存資源副本並在下次請求時直接使用該副本的技術。當 web 緩存發現請求的資源已經被存儲,它會攔截請求,返回該資源的拷貝,而不會去源服務器從新下載。
不須要發送請求到服務端,直接讀取瀏覽器本地緩存,在 Chrome 的 Network 中顯示的 HTTP 狀態碼是 200 ,在 Chrome 中,強緩存又分爲 Disk Cache(存放在硬盤中)和 Memory Cache(存放在內存中),存放的位置是由瀏覽器控制的。是否強緩存由 Expires
、Cache-Control
和 Pragma
3 個 Header 屬性共同來控制。
Pragma:no-cache; //禁用緩存,只用於HTTP1.0的狀況。響應頭字段不支持這個屬性。
Expires所定義的緩存時間是相對服務器上的時間而言的,其定義的是資源「失效時刻」,若是客戶端上的時間跟服務器上的時間不一致(特別是用戶修改了本身電腦的系統時間),那緩存時間可能就沒啥意義了。
Expires:Wed, 21 Oct 2015 07:28:00 GMT;//http1.0 指定緩存的過時時間。實體頭字段。
針對上述的「Expires時間是相對服務器而言,沒法保證和客戶端時間統一」的問題,HTTP1.1
新增了 Cache-Control
來定義緩存過時時間。
//可緩存性 Cache-Control:no-cache;//至關於`max-age=0,must-revalidate`即資源被緩存,可是緩存馬上過時。同時下次訪問時強制驗證資源有效性。 Cache-Control:no-store;//請求和響應都不緩存。通用頭字段。 Cache-Control:public;//代表響應能夠被髮送請求的客戶端,代理服務器等緩存。響應頭字段。 Cache-Control:private;//代表響應只能被單個用戶緩存,不能做爲共享緩存(即代理服務器不能緩存它)。響應頭字段。 //到期 Cache-Control:max-age=<seconds>;//緩存資源,可是在指定時間(單位爲秒)後過時。時間是相對於請求的時間。通用頭字段。 Cache-Control:s-maxage=<seconds>;//覆蓋max-age或者Expires頭,可是僅適用於共享緩存(好比各個代理),私有緩存會忽略它。響應頭字段。 Cache-Control:max-stale[=<seconds>];//指定時間內,即便緩存過時,資源依然有效。請求頭字段。 Cache-Control:min-fresh=<seconds>;//緩存的資源至少要保持指定時間的新鮮期。請求頭字段。 //從新驗證和從新加載 Cache-Control:must-revalidate;//一旦資源過時(好比已經超過max-age),在成功向原始服務器驗證以前,緩存不能用該資源響應後續請求。響應頭字段。 Cache-Control:proxy-revalidate;//與must-revalidate做用相同,但它僅適用於共享緩存(例如代理),並被私有緩存忽略。響應頭字段。 //其餘 Cache-Control:no-transform;//強制要求代理服務器不要對資源進行轉換,禁止代理服務器對 Content-Encoding、Content-Range、Content-Type 字段的修改(所以代理的 gzip 壓縮將不被容許)。請求頭字段。 Cache-Control:only-if-cached;//僅僅返回已經緩存的資源,不訪問網絡,若無緩存則返回504。請求頭字段。
假設所請求資源於4月5日緩存, 且在4月12日過時。
當max-age
與 max-stale
和 min-fresh
同時使用時, 它們的設置相互之間獨立生效, 最爲保守的緩存策略老是有效。
這意味着,若是max-age = 10 days
,max-stale = 2 days
,min-fresh = 3 days
, 那麼:
max-age
的設置,覆蓋原緩存週期,緩存資源將在4月15日失效( 5 + 10 = 15);max-stale
的設置, 緩存過時後兩天依然有效,此時響應將返回110(Response is stale)狀態碼, 緩存資源將在4月14日失效( 12 + 2 = 14);min-fresh
的設置,至少要留有3天的新鮮期,緩存資源將在4月9日失效( 12 - 3 = 9);因爲客戶端老是採用最保守的緩存策略, 所以, 4月9往後,對於該資源的請求將從新向服務器發起驗證。
當客戶端上某個資源保存的緩存時間過時了,但這時候服務器並無更新過這個資源,若是這個資源很大,咱們有必要再把這個資源從新發一遍嗎?
答案是否認的。爲了讓客戶端與服務器之間能實現緩存文件是否更新的驗證、提高緩存的複用率,HTTP1.1新增了幾個首部字段來作這件事情。
Last-Modifie:GMT
的形式加在實體頭字段上一塊兒返回給客戶端。客戶端爲資源標記上該信息,下次再次請求時,會把該信息附帶在請求報文中一併帶給服務器去作檢查。
Last-Modified
用於標記請求資源的最後一次修改時間, 格式爲GMT(格林尼治標準時間)。
If-Modified-Since
緩存校驗字段, 其值爲上次響應頭的Last-Modified
值,若與請求資源當前的Last-Modified
值相同,那麼將返回304狀態碼的響應,反之, 將返回200狀態碼響應。請求頭字段。最多見的應用場景:
ETag
標籤的緩存實體。If-Unmodified-Since
緩存校驗字段,其值爲上次響應頭的Last-Modified值,表示資源在指定的時間以後未修改則正常執行更新,不然返回412(Precondition Failed)狀態碼的響應。請求頭字段。經常使用於以下兩種場景:
Wiki
文檔,文檔未修改時才執行更新。If-Range
字段同時使用時, 能夠用來保證新的片斷請求來自一個未修改的文檔。Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT;//精確度比ETag低,備用機制。HTTP1.0。 If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT;//當與 If-None-Match 一同出現時,它(If-Modified-Since)會被忽略掉。 If-Unmodified-Since: Wed, 21 Oct 2015 07:28:00 GMT;
Last-Modified
存在的問題:
Last-Modified
標註的最後修改只能精確到秒級,若是某些文件在 1 秒鐘之內,被修改屢次的話,它將不能準確標註文件的新鮮度;Last-Modified
卻改變了,致使文件無法使用緩存;爲了解決上述Last-Modified
可能存在的的問題,HTTP1.1還推出了 ETag 實體頭字段。
服務器經過算法,給資源計算得出一個惟一標識符,在把資源響應給客戶端的時候,會在實體頭字段加上ETag:惟一標識符
一塊兒返回給客戶端。例如:
ETag: "x234dff";
客戶端會保留該ETag
字段,並在下一次請求時將其一併帶過去給服務器。服務器只要對比客戶端(做爲If-None-Match字段的值一塊兒發送)傳來的ETag
和本身服務器上該資源的ETag
是否一致,就能很好的判斷資源相對客戶端而言是否被修改過了。
ETag
匹配不上,那麼直接以常規GET
200回包形式將新資源(固然也包括了新的ETag
)發給客戶端。ETag
一致,則直接返回304知會客戶端直接使用本地緩存便可。ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"//強緩存 ETag 要求資源在字節級別必須徹底相符。 ETag: W/"0815"//弱ETag 只要求資源在語義上沒變化,但內部可能會發生了變化(HTML裏的標籤順序調整,多出了幾個空格)。 If-None-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"//請求頭字段。 If-None-Match: W/"67ab43", "54ed21", "7892dd" If-None-Match: * //星號是一個特殊值,能夠表明任意資源。它只用在進行資源上傳時,一般是採用 PUT 方法,來檢測擁有相同識別ID的資源是否已經上傳過了。 If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"//請求頭字段。 If-Match: W/"67ab43", "54ed21", "7892dd" If-Match: * //星號指代任意資源。
If-None-Match
經常使用於判斷緩存資源是否有效
在請求方法爲 GET 和 HEAD 的狀況下
ETag
列表不匹配,服務器返回狀態碼200的響應。ETag
列表匹配,服務器返回狀態碼304的響應。PUT
,將If-None-Match
used 的值設置爲 *,用來生成事先並不知道是否存在的文件,能夠確保先前並無進行過相似的上傳操做,防止以前操做數據的丟失。當與 If-Modified-Since
一同使用的時候,If-None-Match
優先級更高(假如服務器支持的話)。
If-Match
經常使用於判斷條件是否知足
GET
和 HEAD
方法,搭配 Range頭字段 使用,能夠用來保證新請求的範圍與以前請求的範圍是對同一份資源的請求。若是 ETag
沒法匹配,那麼須要返回 416 (Range Not Satisfiable,範圍請求沒法知足) 響應。PUT
, If-Match
頭字段能夠用來避免更新丟失問題。它能夠用來檢測用戶想要上傳的不會覆蓋獲取原始資源以後作出的更新。若是請求的條件不知足,那麼須要返回 412 (Precondition Failed,先決條件失敗) 響應。頭部 | 優點和特色 | 劣勢和問題 |
---|---|---|
Expires | 一、HTTP 1.0 產物,能夠在HTTP 1.0和1.1中使用,簡單易用。 二、以時刻標識失效時間。 | 一、時間是由服務器發送的(UTC),若是服務器時間和客戶端時間存在不一致,可能會出現問題。 二、存在版本問題,到期以前的修改客戶端是不可知的。 |
Cache-Control | 一、HTTP 1.1 產物,以時間間隔標識失效時間,解決了Expires服務器和客戶端相對時間的問題。 二、比Expires多了不少選項設置。 | 一、HTTP 1.1 纔有的內容,不適用於HTTP 1.0 。 二、存在版本問題,到期以前的修改客戶端是不可知的。 |
Last-Modified | 一、不存在版本問題,每次請求都會去服務器進行校驗。服務器對比最後修改時間若是相同則返回304,不一樣返回200以及資源內容。 | 一、只要資源修改,不管內容是否發生實質性的變化,都會將該資源返回客戶端。例如週期性重寫,這種狀況下該資源包含的數據實際上同樣的。 二、以時刻做爲標識,沒法識別一秒內進行屢次修改的狀況。 三、某些服務器不能精確的獲得文件的最後修改時間。 |
ETag | 一、能夠更加精確的判斷資源是否被修改,能夠識別一秒內屢次修改的狀況。 二、不存在版本問題,每次請求都回去服務器進行校驗。 | 一、計算ETag值須要性能損耗。 二、分佈式服務器存儲的狀況下,計算ETag的算法若是不同,會致使瀏覽器從一臺服務器上得到頁面內容後到另一臺服務器上進行驗證時發現ETag不匹配的狀況。 |
Pragma > Cache-Control > Expires
。
ETag
> Last-Modified
(同時存在的時候,Last-Modified
是 ETag
備份)。
瀏覽器能夠在內存、硬盤中開闢一個空間用於保存請求資源副本。
請求一個資源時,會按照Service Worker
-> Memory Cache
-> Disk Cache
-> Push Cache
依次查找緩存。
200 from memory cache 表示不訪問服務器,直接從內存中讀取緩存。 儲存較小的文件,關閉進程後緩存資源隨之銷燬。
200 from disk cache 表示不訪問服務器,直接從硬盤中讀取緩存。存儲大文件,關閉進程後,緩存資源依然存在。
200 from prefetch cache 在 preload
(預加載)或 prefetch
(預先載入)的資源加載時,二者也是均存儲在 http cache
,當資源加載完成後,若是資源是能夠被緩存的,那麼其被存儲在 http cache
中等待後續使用;若是資源不可被緩存,那麼其在被使用前均存儲在 memory cache
。
代理在客戶端和服務器本來的通訊鏈路中插入的一箇中間環節 ,也是服務器,但提供的是代理服務。
所謂的代理服務是指服務自己不產生內容,而是處於中間位置 轉發上下游的請求和響應,具備雙重身份:面向下游的用戶時,表現爲服務器,表明源服務器響應客戶端的請求;而面向上游的源服務器時,又表現爲客戶端,表明客戶端發送請求。
這裏主要說的是最多見的反向代理。
Via 代理服務器用它標明代理的身份。Via 是一個通用頭字段,請求頭或響應頭裏均可以出現。
有多個代理節點的狀況下,客戶端發送請求和服務器返回響應via頭字段順序不同:
Via 字段只解決了 客戶端和源服務器判斷是否存在代理的問題,還不能知道對方的真實信息 。
但服務器的 IP 地址應該是保密的,關係到企業的內網安全,因此通常不會讓客戶端知道。不過反過來,一般服務器須要知道客戶端的真實 IP 地址,方便作訪問控制、用戶畫像、統計分析 。
惋惜的是 HTTP 標準裏並無爲此定義頭字段 ,但已經出現了不少 事實上的標準 ,最經常使用的兩個頭字段是 X-Forwarded-For 和 X-Real-IP 。
X-Forwarded-For
:鏈式存儲
字面意思是爲 誰而轉發 ,形式上和 Via
差很少,也是每通過一個代理節點就會在字段裏追加一個信息,但 Via 追加的是代理主機名(或者域名),而 X-Forwarded-For
追加的是請求方的 IP 地址。因此,在字段裏最左邊的 IP 地址就客戶端的地址。
X-Real-IP
:只有客戶端 IP 地址
是另外一種獲取客戶端真實 IP 的手段,它的做用很簡單,就是記錄客戶端 IP 地址,沒有中間的代理信息。
若是客戶端和源服務器之間只有一個代理,那麼這兩個字段的值就是相同的。
X-Forwarded-For缺點:
經過 X-Forwarded-For
操做代理信息 必需要解析 HTTP 報文頭 ,會下降代理的轉發性能 。
另外一個問題是 X-Forwarded-For
等頭 必需要修改原始報文 ,而有些狀況下是不容許甚至不可能的(好比使用 HTTPS 通訊被加密 )。
因此就出現了一個專門的 代理協議 (The PROXY protocol) ,它由知名的代理軟件 HAProxy 所定義,也是一個 事實標準 ,被普遍採用(注意並非 RFC)。
代理協議有 v1 和 v2 兩個版本,v1 和 HTTP 差很少,也是明文,而 v2 是二進制格式。
v1版本在HTTP 報文前增長了一行 ASCII 碼文本,這一行文本其實很是簡單,開頭必須是 PROXY
五個大寫字母,而後是 TCP4
或者 TCP6
,表示客戶端的 IP 地址類型,再後面是請求方地址、應答方地址、請求方端口號、應答方端口號,最後用一個回車換行(\r\n)結束。
PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\n GET / HTTP/1.1\r\n Host: www.xxx.com\r\n \r\n
你以爲代理有什麼缺點?實際應用時如何避免?
代理的缺點是增長鏈路長度,會增長響應耗時,應儘可能減小在代理商所作的的一些與業務無關的複雜耗時操做。
緩存控制 + 代理服務 = 緩存代理
加入了緩存後代理服務收到源服務器發來的響應數據後須要作兩件事:
Cache
裏。下一次再有相同的請求,代理服務器就能夠直接發送 304 或者緩存數據,沒必要再從源服務器那裏獲取。這樣就下降了客戶端的等待時間,同時節約了源服務器的網絡帶寬。
數據是否容許代理緩存:
private
:表示緩存只能在客戶端保存,是用戶 私有 的。public
:容許緩存失效後從新驗證:proxy-revalidate
代理緩存過時後必須驗證,對應的是客戶端的(must-revalidate
)。
緩存的生存時間:s-maxage
限制在代理服務器上能緩存多久。
private, max-age = 5; public, max-age = 5, s-maxage = 10; max-age = 30, proxy-revalidate, no-transform;
only-if-cached
表示 只接受代理緩存的數據 ,不接受源服務器的響應。若是代理上沒有緩存或者緩存過時,就應該給客戶端返回一個 504(Gateway Timeout)。
Vary: Accept-Encoding
對於服務器而言,資源文件可能不止一個版本,好比說壓縮和未壓縮,針對不一樣的客戶端,一般須要返回不一樣的資源版本。好比說老式的瀏覽器可能不支持解壓縮,這個時候,就須要返回一個未壓縮的版本; 對於新的瀏覽器,支持壓縮,返回一個壓縮的版本,有利於節省帶寬,提高體驗。 那麼怎麼區分這個版本呢, 這個時候就須要Vary了。
服務器經過指定Vary: Accept-Encoding
, 告知代理服務器, 對於這個資源, 須要緩存兩個版本: 壓縮和未壓縮。這樣老式瀏覽器和新的瀏覽器,經過代理,就分別拿到了未壓縮和壓縮版本的資源,避免了都拿同一個資源的尷尬 。
Vary:Accept-Encoding,User-Agent;
Age 和 Date
出現此字段, 表示命中代理服務器的緩存。它指的是代理服務器對於請求資源的已緩存時間, 單位爲秒。以下:
Age:2383321 Date:Wed, 08 Mar 2017 16:12:42 GMT
以上指的是,代理服務器在2017年3月8日16:12:42
時向源服務器發起了對該資源的請求, 目前已緩存了該資源2383321秒。
Date 指的是響應生成的時間。 請求通過代理服務器時,返回的Date未必是最新的, 一般這個時候,代理服務器將增長一個Age字段告知該資源已緩存了多久。
永遠不會修改的內容:JS 和 CSS 文件,圖片,和任何類型的二進制文件都屬於這個類目。
永遠,我確實說的是永遠。爲靜態資源指定版本號是很通用的作法。它們不管何時改動了,它們的 URL 就改變了。
這裏是一些針對靜態資源的簡單的規則:
<link rel = "Stylesheet" href="http://static.xxx.com/a_f02bc2.css"> <script src = "http://static.xxx.com/a_13cfa51.js"
Cache-Control: public, max-age=31536000 Expires: (一年後的今天) ETag: (基於內容生成) Last-Modified: (過去某個時間) Vary: Accept-Encoding
針對應用程序私密性和新鮮度方面需求的不一樣,咱們應該使用不一樣的緩存控制設置。
對於非私密性和常常性變更的資源(想像一下股票信息),咱們應該使用下面這些:
Cache-Control: public, max-age=0 Expires: (當前時間) ETag: (基於內容生成) Last-Modified: (過去某個時間) Vary: Accept-Encoding
這些設置的效果是:
你若是須要更嚴格的控制,須要告知瀏覽器即便當用戶點擊了「返回/前進」按鈕,也須要從新檢查這些資源文件,那麼能夠使用:
Cache-Control: public, no-cache, no-store
不是全部的動態資源都會立刻變成過期的資源。若是它們能夠保持至少5分鐘的時效,能夠使用:
Cache-Control: public, max-age=300
通過這樣的設置,瀏覽器只會在5分鐘以後才從新檢查。在這以前,緩存的內容會被直接使用。若是在5分鐘後,這些過期的內容須要嚴格控制,你能夠添加 must-revalidate
字段:
Cache-Control: public, max-age=300, must-revalidate
對於私密或者針對用戶的內容,須要把 public
替換爲 private
以免內容被代理緩存。
Cache-Control: private, …
簡單、靈活和易於擴展;
header+body
,頭部信息也是簡單的文本格式,用的也都是常見的英文單詞。HTTPS 實際上是一個「很是簡單」的協議,裏面規定了 新的協議名「https」,默認端口號 443 ,至於其餘的請求 - 應答模式、報文結構、請求方法、URI、頭字段、鏈接管理等等都徹底沿用 HTTP。
HTTPS = HTTP + SSL/TLS
對稱加密(Symmetric-Key Encryption),就是指加密和解密時使用的 密鑰都是同一個 ,是 對稱 的。只要保證了密鑰的安全,那整個通訊過程就能夠說具備了機密性。
目前經常使用的有:
對稱算法還有一個 分組模式 的概念,它可讓算法用固定長度的密鑰加密任意長度的明文 ,把小祕密(即密鑰)轉化爲大祕密(即密文)。
AEAD(Authenticated Encryption with Associated Data) ,在加密的同時增長了認證的功能,經常使用的是 GCM、CCM 和 Poly1305。
它有兩個密鑰,一個叫 公鑰(public key),一個叫 私鑰(private key)。兩個密鑰是不一樣的(不對稱) ,公鑰能夠公開給任何人使用,而私鑰必須嚴格保密。
公鑰和私鑰有個特別的 單向 性,雖然均可以用來加密解密,但 公鑰加密後只能用私鑰解密 ,反過來,私鑰加密後也只能用公鑰解密 。
非對稱加密能夠解決 密鑰交換 的問題。
目前經常使用的有:
雖然非對稱密匙沒有 密匙交換 問題,可是它的運行速度很慢。若是僅用非對稱加密,雖然保證了安全,但通訊速度有如蝸牛,實用性就變成了零。
因此就產生了 把對稱加密和非對稱加密結合起來 的 混合加密,二者互相取長補短,即能高效地加密解密,又能安全地密鑰交換。
咱們把消息加密了就能保證安全嗎?
考慮下面的狀況:
非對稱加密的算法都是公開的,全部人均可以本身生成一對公鑰私鑰。
當服務端向客戶端返回公鑰 A1 的時候,中間人將其替換成本身的公鑰 B1 傳送給瀏覽器。
而瀏覽器此時一無所知,傻乎乎地使用公鑰 B1 加密了密鑰 K 發送出去,又被中間人截獲,中間人利用本身的私鑰 B2 解密,獲得密鑰 K,再使用服務端的公鑰 A1 加密傳送給服務端,完成了通訊鏈路,而服務端和客戶端毫無感知。
要保證安全,咱們不只要加密,還要保證消息的完整性以及身份認證。
摘要算法(Digest Algorithm),也就是常說的散列函數、哈希函數(Hash Function)。把一個大空間映射到小空間,因爲對輸入具備 單向性 和 雪崩效應,能夠用來作數據的完整性校驗。
TLS 推薦的摘要算法是 SHA-2,SHA-2 其實是一系列摘要算法的統稱,總共有 6 種,經常使用的有 SHA22四、SHA25六、SHA384,分別可以生成 28 字節、32 字節、48 字節的摘要。
可是它不具有機密性,在混合加密系統裏用 會話密鑰加密消息和摘要,這個術語叫作 哈希消息認證碼(HMAC)。
如今還有個漏洞,通訊的兩個端點(endpoint) 也就是你怎麼證實是你?服務器怎麼證實是服務器?
非對稱加密裏的 私鑰 ,使用私鑰再加上摘要算法,就可以實現 數字簽名 ,同時實現 身份認證 和 不能否認 。
但又由於非對稱加密效率過低,因此私鑰只加密原文的摘要
這裏的私鑰是你本身須要有一個 私鑰 ,服務器也須要有一個 私鑰,大家互相交換公鑰,除非大家的私鑰被泄密,不然身份認證和不能否認就能保證。
到如今,綜合使用對稱加密、非對稱加密和摘要算法,咱們已經實現了安全的四大特性,是否是已經完美了呢?
不是的,這裏還有一個 公鑰的信任 問題。由於誰均可以發佈公鑰,如何保證公鑰不是僞造的?
找一個 公認的可信第三方 ,讓它做爲「信任的起點,遞歸的終點」,構建起公鑰的信任鏈。這就是 CA(Certificate Authority,證書認證機構),使用 CA 的私鑰對你的 公鑰進行簽名(包含序列號、用途、頒發者、有效時間等等和你的公鑰打包再簽名),造成 數字證書(Certificate)。
那麼 CA 怎麼證實本身呢?這仍是信任鏈的問題。小一點的 CA 可讓大 CA 簽名認證 ,但鏈條的最後,也就是 Root CA ,就只能本身證實本身了。
也就是說,個人公鑰是 CA 的私鑰簽名的,那麼我須要拿到該 CA 的公鑰進行解密,解密成功才能證實沒有被僞造,那麼最後仍是信任鏈的問題,最終解決辦法就是 Root CA,這就叫 自簽名證書(Self-Signed Certificate)或者 根證書(Root Certificate),有了這個證書體系,操做系統和瀏覽器都內置了各大 CA 的根證書
也就是說,若是你的公鑰不是 CA 頒發的,那麼想要瀏覽器認爲是安全的,就必須將它安裝到系統的根證書存儲區裏。
記錄協議(Record Protocol)規定了 TLS 收發數據的基本單位:記錄(record)。它有點像是 TCP 裏的 segment,全部的其餘子協議都須要經過記錄協議發出。但多個記錄數據能夠在一個 TCP 包裏一次性發出,也並不須要像 TCP 那樣返回 ACK。
警報協議(Alert Protocol)的職責是向對方發出警報信息,有點像是 HTTP 協議裏的狀態碼。好比,protocol_version 就是不支持舊版本,bad_certificate 就是證書有問題,收到警報後另外一方能夠選擇繼續,也能夠當即終止鏈接。
握手協議(Handshake Protocol)是 TLS 裏最複雜的子協議,要比 TCP 的 SYN/ACK 複雜的多,瀏覽器和服務器會在握手過程當中協商 TLS 版本號、隨機數、密碼套件等信息,而後交換證書和密鑰參數,最終雙方協商獲得會話密鑰,用於後續的混合加密系統。
最後一個是 變動密碼規範協議(Change Cipher Spec Protocol),它很是簡單,就是一個「通知」,告訴對方,後續的數據都將使用加密保護。那麼反過來,在它以前,數據都是明文的。 (在 TLS 1.3 中這個協議已經刪除,爲了兼容 TLS 老版本,可能還會存在)。
Heartbeat 擴展爲 TLS/DTLS 提供了一種新的協議,容許在不須要重協商的狀況下,使用 keep-alive 功能。Heartbeat 擴展也爲 path MTU (PMTU) 發現提供了基礎(這個是 TLS 1.3 新加的,TLS 1.3 以前的版本沒有這個協議。)。
在 TCP 創建鏈接以後,瀏覽器會首先發一個 Client Hello 消息,也就是跟服務器打招呼。裏面有客戶端的TLS版本號、支持的密碼套件,還有一個 隨機數(Client Random) ,用於後續生成會話密鑰。
服務器接收到,當即返回 server_random
,確認TLS版本號和使用的密碼套件 ECDHE。
瀏覽器接收,先驗證數字證書和簽名。
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random) //Master Secret 公式
這裏的 PRF
就是僞隨機數函數,它基於密碼套件裏的最後一個參數,好比此次的 SHA384,經過摘要算法來再一次強化 Master Secret
的隨機性。
主密鑰有 48 字節,但它也不是最終用於通訊的會話密鑰,還會再用 PRF 擴展出更多的密鑰,好比客戶端發送用的會話密鑰(client_write_key)、服務器發送用的會話密鑰(server_write_key)等等,避免只用一個密鑰帶來的安全隱患。
有了主密鑰和派生的會話密鑰,握手就快結束了。客戶端發一個 Change Cipher Spec ,而後再發一個 Finished 消息,把以前全部發送的數據作個摘要,再加密一下,讓服務器作個驗證。
密碼套件
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 //密鑰交換算法 + 簽名算法 + 對稱加密算法 + 摘要算法
大致的流程沒有變,只是 Pre-Master
再也不須要用算法生成,而是客戶端直接生成隨機數,而後用服務器的公鑰加密,經過 Client Key Exchange 消息發給服務器。服務器再用私鑰解密,這樣雙方也實現了共享三個隨機數,就能夠生成主密鑰。
服務端發送 CA 證書給客戶端。
發送的是一個 CA 鏈,不包含 ROOT 證書。
客戶端驗證該證書的可靠性。
解析 CA 鏈,用鏈條上的 CA 進行驗證,一層層地驗證,直到找到根證書,就可以肯定證書是可信的。
k*。
k*
發送給服務端。k*
後用本身的私鑰解密獲得 k。k*
加密信息。密碼套件名 | 代碼 |
---|---|
TLS_AES_128_GCM_SHA256 | {0x13,0x01} |
TLS_AES_256_GCM_SHA384 | {0x13,0x02} |
TLS_CHACHA20_POLY1305_SHA256 | {0x13,0x03} |
TLS_AES_128_GCM_SHA256 | {0x13,0x04} |
TLS_AES_128_GCM_8_SHA256 | {0x13,0x05} |
瀏覽器首先仍是發一個 Client Hello 。
服務器收到 Client Hello
一樣返回 Server Hello
消息,仍是要給出一個 隨機數(Server Random)和選定密碼套件。
這時只交換了兩條消息,客戶端和服務器就拿到了四個共享信息:Client Random 和 Server Random 、Client Params 和 Server Params ,兩邊就能夠各自用 ECDHE 算出 Pre-Master ,再用 HKDF 生成主密鑰 Master Secret ,效率比 TLS1.2 提升了一大截。
這裏 TLS1.3 還有一個安全強化措施,多了個 Certificate Verify 消息,用服務器的私鑰把前面的曲線、套件、參數等握手數據加了簽名,做用和 Finished 消息差很少。但因爲是私鑰簽名,因此強化了身份認證和和防竄改。
這兩個 Hello
消息以後,客戶端驗證服務器證書,再發 Finished
消息,就正式完成了握手,開始收發 HTTP 報文。
HTTP/1.x 實現簡單是以犧牲性能爲代價的:
HTTP/1.1 的首部帶有大量信息,並且每次都要重複發送。
HTTP/2.0 要求客戶端和服務器同時維護和更新一個包含以前見過的頭字段表,從而避免了重複傳輸。
HTTP/2 並無使用傳統的壓縮算法,而是開發了專門的 HPACK 算法,在客戶端和服務器兩端創建「字典」,用索引號表示重複的字符串,還釆用 Huffman 編碼來壓縮整數和字符串,能夠達到 50%~90% 的高壓縮率。
HTTP/2.0 將報文分紅 HEADERS 幀和 DATA 幀,它們都是二進制格式的。
在通訊過程當中,只會有一個 TCP 鏈接存在,它承載了任意數量的雙向數據流(Stream)。
HTTP/2.0 在客戶端請求一個資源時,會把相關的資源一塊兒發送給客戶端,客戶端就不須要再次發起請求了。例如客戶端請求 page.html 頁面,服務端就把 script.js 和 style.css 等與之相關的資源一塊兒發給客戶端。
當一個資源從與該資源自己所在的服務器不一樣的域或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。
出於安全緣由,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非使用CORS頭文件。
(譯者注:跨域並不必定是瀏覽器限制了發起跨站請求,也多是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。最好的例子是 CSRF 跨站攻擊原理,請求是發送到了後端服務器不管是否跨域!注意:有些瀏覽器不容許從 HTTPS 的域跨域訪問 HTTP,好比 Chrome 和 Firefox,這些瀏覽器在請求還未發出的時候就會攔截請求,這是一個特例。)
隸屬於 W3C 的 Web 應用工做組推薦了一種新的機制,即跨源資源共享(Cross-Origin Resource Sharing ) CORS。這種機制讓Web應用服務器能支持跨站訪問控制,從而使得安全地進行跨站數據傳輸成爲可能。須要特別注意的是,這個規範是針對API容器的(好比說XMLHttpReques 或者 Fetch),以減輕跨域HTTP請求的風險。CORS 須要客戶端和服務器同時支持。目前,全部瀏覽器都支持該機制。
跨域資源共享標準( cross-origin sharing standard )容許在下列場景中使用跨域 HTTP 請求:
把CORS分爲:簡單請求、預請求和附帶憑證信息的請求。
某些請求不會觸發 CORS 預檢請求。本文稱這樣的請求爲「簡單請求」,請注意,該術語並不屬於 Fetch (其中定義了 CORS)規範。若請求知足全部下述條件,則該請求可視爲「簡單請求」:
(1). 使用下列方法之一:
(2). Fetch 規範定義了對 CORS 安全的首部字段集合,不得人爲設置該集合以外的其餘首部字段。該集合爲:
Accept
Accept-Language
Content-Language
Content-Type (須要注意額外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width
(3). Content-Type 的值僅限於下列三者之一:
(4). 請求中的任意XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器;XMLHttpRequestUpload 對象能夠使用 XMLHttpRequest.upload 屬性訪問。
(5). 請求中沒有使用 ReadableStream 對象。
簡單來講,重點須要記住的就是兩點:
**(1)只使用 GET, HEAD 或者 POST 請求方法。若是使用 POST 向服務器端傳送數據,則數據類型(Content-Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一種。
(2)不會使用自定義請求頭(相似於 X-Modified 這種)。**
舉例:
//好比說,假如站點 http://foo.example 的網頁應用想要訪問 http://bar.other 的資源。如下的 JavaScript 代 //碼應該會在 foo.example 上執行: var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/public-data/'; function callOtherDomain() { if(invocation) { invocation.open('GET', url, true); invocation.onreadystatechange = handler; invocation.send(); } }
//讓咱們看看,在這個場景中,瀏覽器會發送什麼的請求到服務器,而服務器又會返回什麼給瀏覽器: GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example //該請求來自於 http://foo.exmaple。 //以上是瀏覽器發送請求 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * //這代表服務器接受來自任何站點的跨站請求。若是設置爲http://foo.example。其它站點就不能跨站訪問 http://bar.other 的資源了。 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml //以上是服務器返回信息給瀏覽器
如下狀況,請求會返回相關響應信息
與前述簡單請求不一樣,「需預檢的請求」要求必須首先使用 OPTIONS 方法發起一個預檢請求到服務器,以獲知服務器是否容許該實際請求。"預檢請求「的使用,能夠避免跨域請求對服務器的用戶數據產生未預期的影響。
當請求知足下述任一條件時,即應首先發送預檢請求:
(1). 使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
(2). 人爲設置了對 CORS 安全的首部字段集合以外的其餘首部字段。該集合爲:
Accept
Accept-Language
Content-Language
Content-Type (but note the additional requirements below)
DPR
Downlink
Save-Data
Viewport-Width
Width
(3). Content-Type 的值不屬於下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
(4). 請求中的XMLHttpRequestUpload 對象註冊了任意多個事件監聽器。
(5). 請求中使用了ReadableStream對象。
不一樣於上面討論的簡單請求,「預請求」要求必須先發送一個 OPTIONS 請求給目的站點,來查明這個跨站請求對於目的站點是否是安全可接受的。這樣作,是由於跨站請求可能會對目的站點的數據形成破壞。 當請求具有如下條件,就會被當成預請求處理:
**(1)請求以 GET, HEAD 或者 POST 之外的方法發起請求。或者,使用 POST,但請求數據爲 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 之外的數據類型。好比說,用 POST 發送數據類型爲 application/xml 或者 text/xml 的 XML 數據的請求。
(2)使用自定義請求頭(好比添加諸如 X-PINGOTHER)**
舉個例子:
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/post-here/'; var body = '{C}{C}{C}{C}{C}{C}{C}{C}{C}{C}Arun'; function callOtherDomain(){ if(invocation){ invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/xml'); invocation.onreadystatechange = handler; invocation.send(body); } }
如上,以 XMLHttpRequest 建立了一個 POST 請求,爲該請求添加了一個自定義請求頭(X-PINGOTHER: pingpong),並指定數據類型爲 application/xml。因此,該請求是一個「預請求」形式的跨站請求。瀏覽器使用一個 OPTIONS 發送了一個「預請求」。Firefox 3.1 根據請求參數,決定須要發送一個「預請求」,來探明服務器端是否接受後續真正的請求。 OPTIONS 是 HTTP/1.1 裏的方法,用來獲取更多服務器端的信息,是一個不該該對服務器數據形成影響的方法。 隨同 OPTIONS 請求,如下兩個請求頭一塊兒被髮送:
Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER
假設服務器成功響應返回部分信息以下:
Access-Control-Allow-Origin: http://foo.example //代表服務器容許http://foo.example的請求 Access-Control-Allow-Methods: POST, GET, OPTIONS //代表服務器能夠接受POST, GET和 OPTIONS的請求方法 Access-Control-Allow-Headers: X-PINGOTHER //傳遞一個可接受的自定義請求頭列表。服務器也須要設置一個與瀏覽器對應。不然會報 Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers in preflight response 的錯誤 Access-Control-Max-Age: 1728000 //告訴瀏覽器,本次「預請求」的響應結果有效時間是多久。在上面的例子裏,1728000秒錶明着20天內,瀏覽器在處理針對該服務器的跨站請求,均可以無需再發送「預請求」,只需根據本次結果進行判斷處理。
Fetch 與 CORS 的一個有趣的特性是,能夠基於 HTTP cookies 和 HTTP 認證信息發送身份憑證。通常而言,對於跨域 XMLHttpRequest 或 Fetch 請求,瀏覽器不會發送身份憑證信息。若是要發送憑證信息,須要設置 XMLHttpRequest 的某個特殊標誌位。
本例中,http://foo.example 的某腳本向 http://bar.other 發起一個GET 請求,並設置 Cookies:
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/credentialed-content/'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; invocation.onreadystatechange = handler; invocation.send(); } }
第 7 行將 XMLHttpRequest 的 withCredentials 標誌設置爲 true,從而向服務器發送 Cookies。由於這是一個簡單 GET 請求,因此瀏覽器不會對其發起「預檢請求」。可是,若是服務器端的響應中未攜帶 Access-Control-Allow-Credentials: true ,瀏覽器將不會把響應內容返回給請求的發送者。
假設服務器成功響應返回部分信息以下:
Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Credentials: true Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
若是 bar.other 的響應頭裏沒有 Access-Control-Allow-Credentials:true,則響應會被忽略.。特別注意: 給一個帶有withCredentials的請求發送響應的時候,服務器端必須指定容許請求的域名,不能使用「」。上面這個例子中,若是響應頭是這樣的 Access-Control-Allow-Origin: ,則響應會失敗。在這個例子裏,由於Access-Control-Allow-Origin的值是 http://foo.example 這個指定的請求域名,因此客戶端把帶有憑證信息的內容被返回給了客戶端。另外注意,更多的cookie信息也被建立了。