日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

关于 HTTP 的一切(HTTP/1.1,HTTP/2,HTTP/3,HTTPS, CORS, 缓存 ,无状态)

發(fā)布時間:2023/12/20 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于 HTTP 的一切(HTTP/1.1,HTTP/2,HTTP/3,HTTPS, CORS, 缓存 ,无状态) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

HTTP

為什么會出現(xiàn) HTTP 協(xié)議,從 HTTP1.0 到 HTTP3 經(jīng)歷了什么?HTTPS 又是怎么回事?

HTTP 是一種用于獲取類似于 HTML 這樣的資源的 應(yīng)用層通信協(xié)議, 他是萬維網(wǎng)的基礎(chǔ),是一種 CS 架構(gòu)的協(xié)議,通常來說,HTTP 協(xié)議一般由瀏覽器等 “客戶端” 發(fā)起,發(fā)起的這個請求被稱為 Request, 服務(wù)端接受到客戶端的請求后,會返回給客戶端所請求的資源,這一過程被稱為 Response,在大部分情況下,客戶端和服務(wù)器之間還可能存在許多 proxies,他們的作用可能各不相同,有些可能作為網(wǎng)關(guān)存在,有些可能作為緩存存在。

HTTP 協(xié)議有三個基本的特性:

  • 簡單:HTTP 的協(xié)議和報文是簡單,易于理解和閱讀的(HTTP/2 已經(jīng)改用二進制傳輸數(shù)據(jù),但 HTTP 整體還是簡單的)
  • 可拓展的:請求和響應(yīng)都包括 “Header” 和 “Body” 兩部分,我們可以通過添加頭部字段輕松的拓展 HTTP 的功能
  • 無狀態(tài)的:服務(wù)端不保存客戶端狀態(tài),也就是說每一次請求的服務(wù)端來說都是唯一無差別的,我們只能通過 Cookie 等技術(shù)創(chuàng)建有狀態(tài)的會話。
  • HTTP 的歷史

    HTTP 的歷史可以追溯到萬維網(wǎng)剛被發(fā)明的時候,1989年, Tim Berners-Lee 博士寫了一份關(guān)于建立一個通過網(wǎng)絡(luò)傳輸超文本系統(tǒng)的報告。該系統(tǒng)起初被命名為 Mesh,在隨后的1990年項目實施期間被更名為萬維網(wǎng)(*World Wide Web)。他以現(xiàn)有的 TCP IP 協(xié)議為基礎(chǔ)建造, 由四個部分組成:

    • 用來表示超文本文檔的文本格式,即超文本標記語言(HTML)
    • 一個用來傳輸超文本的簡單應(yīng)用層協(xié)議,即超文本傳輸協(xié)議(HTTP)
    • 一個用來顯示或編輯超文本文檔的客戶端,即網(wǎng)絡(luò)瀏覽器,而第一個瀏覽器則被稱為 WorldWideWeb
    • 一個用于提供可訪問文檔的服務(wù),httpd 的前身.

    這四部分在 1990 年底完成,這時候的 HTTP 協(xié)議還很簡單,后來為了于其他版本的協(xié)議區(qū)分,最初的 HTTP 協(xié)議被記為 HTTP/0.9,

    后來,隨著計算機技術(shù)的發(fā)展,HTTP 協(xié)議也隨著 HTTP/1.0, HTTP/1.1, HTTP/2 等關(guān)鍵版本更迭變得更加高效實用。

    HTTP/0.9 on-line

    最初的 0.9 版本也被稱為單行協(xié)議(on-line), 基于 TCP 協(xié)議,該版本下只有一個可用的請求方法:GET, 請求格式也相當簡單:

    GET /index.html

    它表示客戶端請求 index.html 的內(nèi)容,0.9 版本的 HTTP 響應(yīng)也同樣簡單,他只允許響應(yīng) HTML 格式的字符串,如:

    <html><h1> ..... </h1> </html>

    這一階段的響應(yīng)甚至沒有響應(yīng)頭,也沒有響應(yīng)碼或錯誤代碼,一旦出現(xiàn)問題,服務(wù)端會響應(yīng)一段特殊的 HTML 字符串以便客戶端查看。 服務(wù)端在發(fā)送完數(shù)據(jù)后,就會立刻關(guān)閉 TCP 連接。

    HTTP/1.0

    0.9 版本的 HTTP 協(xié)議太過于簡單甚至是簡陋,而隨著瀏覽器和服務(wù)器的應(yīng)用被擴展到越來越多的領(lǐng)域,0.9 版本的協(xié)議已經(jīng)不能適應(yīng),直到 1996年11月,RFC 1945 定義了 HTTP/1.0, 但他并不是官方標準,該版本的 HTTP 協(xié)議較 0.9 版本有了一下改變:

  • 版本號被添加到了請求頭上,像下面這樣:

    GET /mypage.html HTTP/1.0
  • 引入了 HTTP頭的概念,無論是請求還是響應(yīng),允許傳輸元數(shù)據(jù),這使得協(xié)議更加靈活和具有拓展性。

  • 請求方法拓展到了 GET,HEAD,POST

  • 在新 HTTP 頭(Content-Type)的幫助下,可以傳輸不止 HTML 的任意格式的數(shù)據(jù)。

  • 響應(yīng)時帶上了狀態(tài)碼,使得瀏覽器能夠知道響應(yīng)的狀態(tài)并作出響應(yīng)的處理。

  • HTTP/1.1

    同 0.9 版本一樣,1.0 版本下,TCP 連接是不能復(fù)用的,數(shù)據(jù)發(fā)送完后服務(wù)端會立刻關(guān)閉連接,但由于建立 TCP 連接的代價較大,所以 1.0 版本的 HTTP 協(xié)議并不是足夠高效,加上 HTTP/1.0 多種不同的實現(xiàn)方式在實際運用中顯得有些混亂,自1995年就開始了 HTTP 的第一個標準化版本的修訂工作,到1997年初,HTTP1.1 標準發(fā)布。

    1.1 版本的改進包括:

  • 支持長連接:在 HTTP1.1 中默認開啟 Connection: keep-alive,允許在一個 TCP 連接上傳輸多個 HTTP 請求和響應(yīng),減少了建立和關(guān)閉連接造成的性能消耗。

  • 支持 pipline: HTTP/1.1 還支持流水線(pipline)工作,流水線是指在同一條長連接上發(fā)出連續(xù)的請求,而不用等待應(yīng)答返回。這樣可以避免連接延遲。

  • 支持響應(yīng)分塊:對于比較大的響應(yīng),HTTP/1.2 通過 Transfer-Encoding 首部支持將其分割成多個任意大小的分塊,每個數(shù)據(jù)塊在發(fā)送時都會附上塊的長 度,最后用一個零長度的塊作為消息結(jié)束的標志。

  • 新的緩存控制機制:HTTP/1.1定義的 Cache-Control 頭用來區(qū)分對緩存機制的支持情況,同時,還提供 If-None-Match, ETag , Last-Modified, If-Modified-Since 等實現(xiàn)緩存的驗證等工作。

  • 允許不同域名配置到同一IP的服務(wù)器上:在 HTTP/1.0 時,認為每臺服務(wù)器綁定一個唯一的 IP,但隨著技術(shù)的進步,一臺服務(wù)器的多個虛擬主機會共享一個IP,為了區(qū)分同一服務(wù)器上的不同服務(wù),HTTP/1.1 在請求頭中加入了 HOST 字段,它指明了請求將要發(fā)送到的服務(wù)器主機名和端口號,這是一個必須字段,請求缺少該字段服務(wù)端將會返回 400.

  • 引入內(nèi)容協(xié)商機制,包括語言,編碼,類型等,并允許客戶端和服務(wù)器之間約定以最合適的內(nèi)容進行交換。

  • 使用了 100 狀態(tài)碼:HTTP/1.0 中,定義:

    o 1xx: Informational - Not used, but reserved for future use

    在 2.0 版本時,使用了這個保留的狀態(tài)碼,用來表示臨時響應(yīng)。

  • HTTPS

    HTTP/1.1 之后,對 HTTP 協(xié)議的拓展變得更加簡單,但 HTTP 依然存在一個天然的缺陷就是明文傳輸數(shù)據(jù),直到 1994 年底,網(wǎng)景公司在 TCP/IP 協(xié)議棧的基礎(chǔ)上添加了 SSL 層用來加密傳輸,后來,在標準化的過程中, SSL 成了 TLS (Transport Layer Security 傳輸層安全協(xié)議),基于 HTTPS 通信的客戶端和服務(wù)器在建立完 TCP 連接之后會協(xié)商通信密鑰,在之后的通信過程中, 客戶端和服務(wù)器會使用該密鑰對數(shù)據(jù)進行對稱加密,以防數(shù)據(jù)被竊取或篡改。(密鑰協(xié)商階段會使用非對稱加密)。

    HTTP/2

    HTTP/1.1 雖然允許連接復(fù)用和以流水線方式運作,但在一個 TCP 連接里面,所有數(shù)據(jù)依然還是按序發(fā)送的,服務(wù)器只能處理完一個請求再去處理另一個請求,如果第一個請求非常慢,就會造成后面的請求長時間阻塞,這被稱為 隊頭阻塞(Head-of-line blocking),2009 年,谷歌公開了自行研發(fā)的 SPDY 協(xié)議,它基于 HTTPS,并采用多路復(fù)用解決了隊頭阻塞的問題,同時,它還使用了 Header 壓縮等技術(shù)大大降低了延時并提高了帶寬利用率,在之后的 2015 到 2019 年間,谷歌在自家瀏覽器上實踐和證明了這個協(xié)議,SPDY 也成了 HTTP/2 的基石。

    2015 年 5 月, HTTP/2 正式標準化,他與 1.x 版本 不同在于:

  • 1.x 版的 HTTP 協(xié)議傳輸?shù)氖俏谋拘畔?#xff0c;這對開發(fā)者很友好,但卻浪費了計算機的性能,HTTP/2 改成了基于二進制而不再是基于文本的協(xié)議,
  • 這是一個復(fù)用協(xié)議。并行的請求能在同一個鏈接中處理,移除了HTTP/1.x中順序和阻塞的約束。
  • 壓縮了headers。因為headers在一系列請求中常常是相似的,其移除了重復(fù)和傳輸重復(fù)數(shù)據(jù)的成本。
  • 其允許服務(wù)器在客戶端緩存中填充數(shù)據(jù),通過一個叫服務(wù)器推送的機制來提前請求。
  • 雖然 HTTP/2 2015 年就被標準化,在到目前為止,HTTP/1.1 任然被廣泛使用,據(jù) MySSL 的最新統(tǒng)計,截至 2020 年 12 月,已有 65.84% 的站點支持了 HTTP/2.

    HTTP/3

    HTTP/3 是即將到來的第三個主要版本的 HTTP 協(xié)議,在 HTTP/3 中,將棄用 TCP 協(xié)議,改為使用基于 UDP 的 QUIC 協(xié)議實現(xiàn)。QUIC(快速UDP網(wǎng)絡(luò)連接)是一種實驗性的網(wǎng)絡(luò)傳輸協(xié)議,由Google開發(fā),該協(xié)議旨在使網(wǎng)頁傳輸更快。

    在2018年10月28日的郵件列表討論中,IETF(互聯(lián)網(wǎng)工程任務(wù)組) HTTP和QUIC工作組主席 Mark Nottingham 提出了將 HTTP-over-QUIC 更名為 HTTP/3 的正式請求,以“明確地將其標識為HTTP語義的另一個綁定……使人們理解它與 QUIC 的不同”,并在最終確定并發(fā)布草案后,將 QUIC 工作組繼承到 HTTP 工作組, 在隨后的幾天討論中,Mark Nottingham 的提議得到了 IETF 成員的接受,他們在2018年11月給出了官方批準,認可 HTTP-over-QUIC 成為 HTTP/3。

    2019年9月,HTTP/3支持已添加到 CloudFlare 和 Chrome 上。Firefox Nightly 也將在2019年秋季之后添加支持。

    HTTP/1.1 細節(jié)

    HTTP報文

    HTTP 的報文都由消息頭和消息體兩部分組成,兩者之間以 CRLF(回車換行) 分割。

    請求頭格式

    請求頭第一行為請求行,其余為請求頭字段:如下:

    POST /api/article/list HTTP/1.1 Host: junebao.top:8888 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0 Accept: application/json, text/plain, */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate, br Content-Type: application/json;charset=utf-8 Content-Length: 32 Origin: https://junebao.top Connection: keep-alive Referer: https://junebao.top/ Cache-Control: max-age=0

    請求行由三部分組成:

  • 請求方法
  • 請求資源的 url
  • 協(xié)議版本
  • 他們以空格分隔,RFC2068 定義了其中不同的請求方法,他們分別為 OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE,除此之外,后來還添加了一個 PATCH 方法。

    方法基本用法請求響應(yīng)冪等性緩存安全性
    OPTIONS獲取目的資源所支持的通信選項,
    如檢測服務(wù)器所支持的請求方法或CORS預(yù)檢請求
    不能攜帶請求體或數(shù)據(jù)可以攜帶響應(yīng)體,但一般有效數(shù)據(jù)被放在頭部如 Allow 等字段冪等不可緩存安全
    GET用于獲取某個資源參數(shù)一般攜帶在 URL 后面,沒有請求體有響應(yīng)體冪等可緩存安全
    HEAD用于請求資源的頭部信息,如下載前獲取大文件的大小沒有請求體沒有響應(yīng)體,響應(yīng)頭應(yīng)該與使用 GET 請求時的一樣冪等可緩存安全
    POST將數(shù)據(jù)發(fā)送給服務(wù)器數(shù)據(jù)放在請求體中有響應(yīng)體不冪等可緩存(包含新鮮信息時)不安全
    PUT使用請求中的負載創(chuàng)建或替換目標資源數(shù)據(jù)放在請求體中有響應(yīng)體冪等不可緩存不安全
    DELETE刪除指定資源可以由請求體可以由響應(yīng)體冪等不可緩存不安全
    TRACE回顯服務(wù)器收到的請求,主要用于測試或診斷。無請求體無響應(yīng)體冪等不可緩存不安全
    PATCH作為 PUT 的補充,用于修改已知資源的部分有請求體無響應(yīng)體非冪等不可緩存不安全
    請求頭字段

    RFC 2068 提供了 17 種請求頭字段,但 HTTP 協(xié)議是易于拓展的,我們可以根據(jù)自己的需要添加自己的請求頭,常見的請求頭字段包括:

    字段作用示例
    HOST指明了要發(fā)送到的服務(wù)器的主機號和端口號,這是一個必須字段,缺失服務(wù)器一般會返回 400,
    端口號默認 80 和 443
    Host: www.baidu.com
    ACCEPT告知服務(wù)器客戶端可以處理的內(nèi)容類型,用MIME類型來表示。Accept: text/html
    User-Agent用戶代理標識
    Cookies用于維持會話

    響應(yīng)頭格式

    Response = Status-Line*( general-header| response-header| entity-header )CRLF[ message-body ]

    類似于請求頭,響應(yīng)頭包括狀態(tài)行和響應(yīng)頭字段兩部分組成。

    狀態(tài)行包括協(xié)議版本,狀態(tài)碼,狀態(tài)描述三部分組成,類似:

    http/2 200 ok

    目前 http 使用的狀態(tài)碼分為 5 類:

    • 1xx: 信息響應(yīng)類
    • 2xx: 正常響應(yīng)類
    • 3xx: 重定向類
    • 4xx: 客戶端錯誤類
    • 5xx: 服務(wù)端錯誤類
    常見狀態(tài)碼
    狀態(tài)碼描述作用
    100Continue迄今為止的所有內(nèi)容都是可行的,客戶端應(yīng)該繼續(xù)請求
    200Ok請求成功
    201Created該請求已成功,并因此創(chuàng)建了一個新的資源。這通常是在POST請求,或是某些PUT請求之后返回的響應(yīng)。
    301Moved Permanently永久重定向
    302Found臨時重定向
    400Bad Request請求參數(shù)錯誤或語義錯誤
    401Unauthorized請求未認證
    403Forbidden拒絕服務(wù)
    404Not Found資源不存在
    429Too Many Requests超過請求速率限制(節(jié)流)
    500Internal Server Error服務(wù)端未知異常
    501Not Implemented此請求方法不被服務(wù)端支持
    502Bad Gateway網(wǎng)關(guān)錯誤
    503Service Unavailable服務(wù)不可用
    504Gateway Timeout網(wǎng)關(guān)超時
    505HTTP Version Not SupportedHTTP 版本不被支持

    無狀態(tài)的 HTTP

    HTTP 是一個無狀態(tài)的協(xié)議,為了維持會話,每客戶端請求時,都應(yīng)該攜帶一個 “憑證”,證明 who am i, 目前維持會話常用的技術(shù)有:cookie, session, token, 等

    cookie

    RFC 6265 定義了 Cookie 的工作方式, Cookie 是服務(wù)器發(fā)送給客戶端并存儲在本地的一小段數(shù)據(jù),在用戶第一次登錄時,服務(wù)器生成 Cookie 并在響應(yīng)頭里添加 Set-Cookie 字段,客戶端收到響應(yīng)后,將 Set-Cookie 字段的值(Cookie)存儲在本地,以后每次請求時,客戶端會自動通過 Cookie 字段攜帶 Cookie。

    Cookie 以鍵值的形式儲存,除了必須的 Name 和 Value,還可以為 Cookie 設(shè)置以下屬性:

    • Domain:指定了哪寫主機可以接收該 Cookie,默認為 Origin, 不包含子域名。
    • Path:規(guī)定了請求主機下的哪些路徑時要攜帶該 Cookie。
    • Expires/Max-Age: 規(guī)定該 Cookie 過期時間或最大生存時間,該時間只與客戶端有關(guān)。
    • HttpOnly: JavaScript Document.cookie API 無法訪問帶有 HttpOnly 屬性的cookie,用于預(yù)防 XSS 攻擊;用于持久化會話的 Cookie 一般應(yīng)該設(shè)置 HttpOnly 。
    • Secure:標記為 Secure 的 Cookie 只能使用 HTTPS 加密傳輸給服務(wù)器,因此可以防止中間人攻擊,但 Cookie 天生具有不安全性,任何敏感數(shù)據(jù)都不應(yīng)該使用 Cookie 傳輸,哪怕標記了 secure.
    • Priority:
    • SameSite:要求該 Cookie 在跨站請求時不會被發(fā)送,用來阻止 CSRF 攻擊,它有三種可選的值:
      • None:在同站請求和跨站請求時都會攜帶上 Cookie
      • Strict:只會在訪問同站請求時帶上 Cookie
      • Lex:與 Strict 類似,但用戶從外部站點導(dǎo)航至URL時(例如通過鏈接)除外,新版瀏覽器一般以 Lex 為默認選項。

    Cookie 被完全保存在客戶端,對客戶端用戶來說是透明的,用戶可以自己創(chuàng)建和修改 Cookie,所以將敏感信息(如用于持久化會話的用戶身份信息等)存放在 Cookie 中是十分危險的,如果不得已需要使用 Cookie 來存儲和傳遞這類信息,應(yīng)該考慮使用 JWT 等類似機制。

    由于 Cookie 的不安全性,絕大部分 Web 站點已經(jīng)開始停止使用 Cookie 持久化會話,但 Cookie 在一些對安全性要求不高的場景下依然被廣泛使用,如:

    • 個性化設(shè)置
    • 瀏覽器用戶行為跟蹤。

    了解更多:

    超級 Cookie 和僵尸 Cookie

    決戰(zhàn)僵尸 Cookie

    SESSION

    Cookie 不安全的根源在于它將會話信息保存在了客戶端,為此,就有了使用 Session 持久化會話的方案,用戶在第一次登錄時,服務(wù)器會將用戶會話狀態(tài)信息保存在服務(wù)器內(nèi)存中,同時會為這段信息生成一串唯一索引,將這個索引作為 Cookie (Name 一般為 SESSION_IDSESSION_ID)返回給客戶端,客戶端下一次請求時,會自動攜帶這個 SESSION_ID, 服務(wù)器只需要根據(jù) SESSION_ID 的值找到對應(yīng)的狀態(tài)信息就可以知道這次請求是誰發(fā)起的。

    SESSION 很大程度上還是依賴于 Cookie,但這時 Cookie 中保存的已經(jīng)是一段對客戶端來說無意義的字符串了,因此使用 Session 能安全的實現(xiàn)會話持久化,但 Session 信息被保存在服務(wù)器內(nèi)存中,可能造成服務(wù)器壓力過大,并且在分布式和前后端分離的環(huán)境下,Session 并不容易拓展。

    TOKEN

    Cookie 和 Session 都是開箱即用的 API,因此,他們不可避免地缺少靈活性,在一般開發(fā)中,往往采用更靈活地 Token,Token 與 Session 原理一致,都是將會話信息保存到服務(wù)器,然后向客戶端返回一個該信息的索引(token),但 Token 完全由開發(fā)者實現(xiàn),可以根據(jù)需要將會話信息存儲在內(nèi)存,數(shù)據(jù)庫,文件等地方,而對于該信息的索引,也可以根據(jù)具體需要選擇使用請求頭,請求體或者 Cookie 傳遞,也不必拘束于只 Cookie 傳遞。

    JWT

    全稱 json web token, 是一種客戶端存儲會話狀態(tài)的技術(shù),它使用數(shù)字簽名技術(shù)防止了負荷信息被篡改,jwt 包含三部分信息:

    • Header:包含 token 類型和算法名稱
    • Payload:存儲的負載信息(敏感信息不應(yīng)該明文存放在此)
    • Signature:服務(wù)端使用私鑰對 Header 和 Payload 的簽名,防止信息被篡改。

    這三部分原本都是 json 字符串,最終他們會經(jīng)過 Base64 編碼后拼接到一起,使用 . 分割。

    分布式解決方案

    在分布式場景下,同一用戶的不同次請求可能會被打到不同的服務(wù)器上,這時如果還像單機時那樣存儲,就會出問題,一般的解決方案包括:

    • 粘性 session:將用戶綁定到一臺服務(wù)器上,如 Nginx 負載均衡策略使用 ip_hash, 但這樣如果當前服務(wù)器發(fā)生故障,可能導(dǎo)致分配到這臺服務(wù)器上的用戶登錄信息失效,容錯度低。
    • session 復(fù)制:一臺服務(wù)器的 session 改變,就廣播給所有服務(wù)器,但會影響服務(wù)器性能
    • session 共享:把所有服務(wù)器的 session 放在一起,如使用 redis 等分布式緩存做 session 集群。
    • 客戶端記錄狀態(tài):使用諸如 JWT 之類的方法。

    連接管理

    連接管理是一個 HTTP 的關(guān)鍵話題:打開和保持連接在很大程度上影響著網(wǎng)站和 Web 應(yīng)用程序的性能。在 HTTP/1.x 里有多種模型:短連接長連接HTTP 流水線

    短連接

    HTTP 最早期的模型,也是 HTTP/1.x 的默認模型,是短連接。每發(fā)起一個HTTP請求都會通過三次握手建立一個TCP連接,在接受到數(shù)據(jù)之后再通過四次揮手釋放連接,因為TCP連接的建立和釋放都是一個耗時操作,加之現(xiàn)代網(wǎng)頁可能需要多次連續(xù)請求才能渲染完成,這就顯得這種簡單的模型效率低下。

    TCP 協(xié)議握手本身就是耗費時間的,所以 TCP 可以保持更多的熱連接來適應(yīng)負載。短連接破壞了 TCP 具備的能力,新的冷連接降低了其性能。

    為此,HTTP/1.1 時新增加了兩種連接管理模式,分別是長連接和流水線,在HTTP/2 中,又基于數(shù)據(jù)流采用了新的連接管理模式。

    長連接

    長連接是指在客戶端接受完數(shù)據(jù)后,不立刻關(guān)閉這個 TCP 連接,這個連接還可以用來發(fā)送和接收其他 HTTP 數(shù)據(jù),這樣一來可以減少部分連接建立和釋放的耗時,但這個連接也并不會一直保持,服務(wù)端可以設(shè)置 Keep-alive 標頭來指定一個最小的連接保持時間(單位秒)和最大請求數(shù):

    HTTP/1.1 200 OK Connection: Keep-Alive Keep-Alive: timeout=5, max=1000

    HTTP/1.0 里默認并不使用長連接。把 Connection 設(shè)置成 close 以外的其它參數(shù)都可以讓其保持長連接,通常會設(shè)置為 retry-after。

    在 HTTP/1.1 里,默認就是長連接的,協(xié)議頭都不用再去聲明它(但我們還是會把它加上,萬一某個時候因為某種原因要退回到 HTTP/1.0 呢)。

    長連接并不總是好的,比如,他在空閑狀態(tài)下仍會消耗服務(wù)器資源,而在網(wǎng)絡(luò)重負載時,還有可能遭受 DoS 攻擊。這種場景下,可以使用非長連接,即盡快關(guān)閉那些空閑的連接,也能對性能有所提升。

    流水線

    默認情況下,HTTP 請求是按順序發(fā)出的。下一個請求只有在當前請求收到應(yīng)答過后才會被發(fā)出。由于會受到網(wǎng)絡(luò)延遲和帶寬的限制,在下一個請求被發(fā)送到服務(wù)器之前,可能需要等待很長時間。

    流水線是在同一條長連接上發(fā)出連續(xù)的請求,而不用等待應(yīng)答返回。這樣可以避免連接延遲。理論上講,性能還會因為兩個 HTTP 請求有可能被打包到一個 TCP 消息包中而得到提升,就算 HTTP 請求不斷的繼續(xù)導(dǎo)致 TCP 包的尺寸增加,通過設(shè)置 TCP 的 MSS(Maximum Segment Size) 選項,流水線方式仍然足夠包含一系列簡單的請求。

    使用流水線的另一個需要注意的問題是錯誤重傳,因此,只有冪等的方法,如 GET,HEAD,PUT, DELETE 等方法能夠安全地使用流水線。

    流水線只是針對客戶端來說的,服務(wù)器依然和非流水線方式那樣工作,這就導(dǎo)致如果第一個請求非常耗時,那流水線上后面的請求就會被阻塞住,這種現(xiàn)象被稱為Head-of-line blocking(隊頭阻塞),除此之外,復(fù)雜的網(wǎng)絡(luò)環(huán)境和代理服務(wù)器也可能會導(dǎo)致流水線不能像預(yù)期的那樣高效工作,因此,現(xiàn)代瀏覽器都沒有默認啟用流水線,在 HTTP/2 里,有更高效的算法代替了流水線。

    三者比較

    CORS

    在前后端分離開發(fā)時,你也許遇到過類似這樣的報錯:

    Access to XMLHttpRequest at '*' from origin '*' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    這就是 CORS 的問題了,所謂 CORS (Cross-Origin Resource Sharing,跨域資源共享),它首先是一個系統(tǒng),由一系列 HTTP 頭組成,這些 HTTP 頭決定了瀏覽器是否阻止前端 JavaScript 代碼獲取跨域請求的響應(yīng)。

    之所以需要 CORS,是由于瀏覽器的同源安全策略:

    同源安全策略

    同源安全策略用來限制一個源(origin)的文檔或者它加載的腳本如何能與另一個源的資源進行交互。它能幫助阻隔惡意文檔,減少可能被攻擊的媒介。

    只有兩個 URL 的協(xié)議,主機,端口都相同時,他們才被認為是“同源的”,反之,如:http://www.a.com 和 https://www.a.com 則會被認為是不同源的(協(xié)議不同),在默認情況下,同源策略會阻止通過不同源的URL獲取資源,而 CORS 就是提供了一種機制,以允許不同源的資源進行共享。

    原理

    CORS 的原理很簡單,它通過添加一組 HTTP 頭,允許服務(wù)器聲明哪些源站通過瀏覽器有權(quán)限訪問哪些資源。另外,規(guī)范要求,對那些可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法(非簡單請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預(yù)檢請求,從而獲知服務(wù)端是否允許該跨源請求。服務(wù)器確認允許之后,才發(fā)起實際的 HTTP 請求。在預(yù)檢請求的返回中,服務(wù)器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 或 HTTP 認證相關(guān)數(shù)據(jù))。

    上面說到的 “可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求” 就是非簡單請求(not-so-simple request),與之對應(yīng)的是簡單請求(simple request),同時滿足以下幾個條件的,屬于簡單請求。

  • 請求方法是以下三種方法之一:

    • HEAD
    • GET
    • POST
  • 首部字段只包含被用戶代理自動設(shè)置的首部字段(例如 Connection ,User-Agent)和允許人為設(shè)置的字段為 Fetch 規(guī)范定義的 對 CORS 安全的首部字段集合。該集合為:

    • Accept

    • Accept-Language

    • Content-Language

    • Content-Type, 只限于三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

    • DPR

    • Downlink

    • Save-Data

    • Width

    • Viewport-Width

  • 請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監(jiān)聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問。

  • 請求中沒有使用 ReadableStream 對象。

  • 只要有其一不滿足,就是費簡單請求,非簡單請求在正式請求之前會先使用 OPTION 方法像服務(wù)器發(fā)起一個 預(yù)檢請求,如下面這個請求:

    var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/post-here/'; var body = '<?xml version="1.0"?><person><name>Arun</name></person>';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);} }

    當前域為 foo.example.com,請求 bar.other, 屬于跨域請求,并且請求時自己添加了一個請求頭 X-PINGOTHER ,并且 Content-Type 類型為 application/xml, 所以它屬于一個非簡單請求,在實際請求之前需要使用 OPTION 方法發(fā)一個預(yù)檢請求:

    OPTIONS /resources/post-here/ 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 Origin: http://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-TypeHTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain

    預(yù)檢請求頭頭中最重要的部分有下面幾個:

    • Host: 要請求的域
    • Origin: 發(fā)起請求的域,Host 和 Origin 不一樣,說明是跨域請求
    • Access-Control-Request-Method: 正式的請求將要使用的方法
    • Access-Control-Request-Headers: 正式請求將攜帶的自定義字段

    服務(wù)器在收到這樣的預(yù)檢請求后就可以根據(jù)請求頭決定是否允許即將發(fā)送的實際請求,在服務(wù)器的響應(yīng)中,最重要的字段有以下幾個:

    • Access-Control-Allow-Origin: 服務(wù)器允許的域,允許所有域該值設(shè)置為 *
    • Access-Control-Allow-Methods: 服務(wù)器允許的請求方法,允許所有方法設(shè)置為 *
    • Access-Control-Allow-Headers: 服務(wù)器允許的請求頭
    • Access-Control-Max-Age: 該響應(yīng)的有效時間為 86400 秒,也就是 24 小時。在有效時間內(nèi),瀏覽器無須為同一請求再次發(fā)起預(yù)檢請求。

    接受到響應(yīng)后,瀏覽器會自動判斷實際請求是否被允許,如果不被允許,將會報上面的錯誤。

    對于簡單請求,通過請求中的 Origin 和響應(yīng)中的 Access-Control-Allow-Origin 就可以實現(xiàn)簡單的訪問控制,如果請求的 Origin 不在許可范圍內(nèi),服務(wù)器會返回一個正常的響應(yīng),瀏覽器發(fā)現(xiàn)這個響應(yīng)的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲。注意,這種錯誤無法通過狀態(tài)碼識別,因為HTTP回應(yīng)的狀態(tài)碼有可能是200。

    HTTP 緩存

    緩存是一種保存資源副本并在下次請求時直接使用該副本的技術(shù)。當 web 緩存發(fā)現(xiàn)請求的資源已經(jīng)被存儲,它會攔截請求,返回該資源的拷貝,而不會去源服務(wù)器重新下載。這樣帶來的好處有:緩解服務(wù)器端壓力,提升性能(獲取資源的耗時更短了)。對于網(wǎng)站來說,緩存是達到高性能的重要組成部分。緩存需要合理配置,因為并不是所有資源都是永久不變的:重要的是對一個資源的緩存應(yīng)截止到其下一次發(fā)生改變(即不能緩存過期的資源)。

    緩存有很多種,以服務(wù)對象分類,緩存可以分為私有緩存共享緩存,以行為分類,又可以把它分為強制緩存對比緩存

    • 私有緩存:又叫瀏覽器緩存,只能用于單獨用戶。瀏覽器緩存擁有用戶通過HTTP下載的所有文檔。這些緩存為瀏覽過的文檔提供向后/向前導(dǎo)航,保存網(wǎng)頁,查看源碼等功能,可以避免再次向服務(wù)器發(fā)起多余的請求。它同樣可以提供緩存內(nèi)容的離線瀏覽。
    • 共享緩存:又叫代理緩存,共享緩存可以被多個用戶使用。例如,ISP 或你所在的公司可能會架設(shè)一個 web 代理來作為本地網(wǎng)絡(luò)基礎(chǔ)的一部分提供給用戶。這樣熱門的資源就會被重復(fù)使用,減少網(wǎng)絡(luò)擁堵與延遲。
    • 強制緩存:緩存數(shù)據(jù)未失效時,都可以使用緩存。
    • 對比緩存:使用數(shù)據(jù)前需要請求服務(wù)器驗證緩存是否失效。

    緩存控制

    緩存的原理很簡單:客戶端在從服務(wù)器獲取到數(shù)據(jù)后,可以選擇將這些數(shù)據(jù)存儲下來,下一次請求同樣的數(shù)據(jù)時,就可以不請求服務(wù)器直接返回先前存儲的數(shù)據(jù)了,正確使用緩存可以提高響應(yīng)速度,降低服務(wù)器壓力;

    這里的客戶端可以是瀏覽器(如私有緩存),也可以是請求鏈路上的中間代理(如共享緩存),但對服務(wù)器來說,他們都是一樣的,而服務(wù)器并沒有辦法主動向客戶端推送數(shù)據(jù),這就導(dǎo)致必須有一種機制去保證緩存是“新鮮”的,HTTP 協(xié)議通過一些列的頭字段實現(xiàn)了緩存控制,其中最重要的字段是 Cache-Control,他有以下幾種值:

  • Cache-Control: no-store: 禁用緩存,緩存不會存儲任何響應(yīng)數(shù)據(jù),每次請求都從服務(wù)器獲取最新的數(shù)據(jù)。
  • Cache-Control: no-cache: 使用緩存,但需要服務(wù)器重新驗證,此方式下,每次有請求發(fā)出時,緩存會將此請求發(fā)到服務(wù)器,服務(wù)器端會驗證請求中所描述的緩存是否過期,若未過期(返回304),則緩存才使用本地緩存副本。
  • Cache-Control: private: 私有緩存,表示該響應(yīng)是專用于某單個用戶的,中間人不能緩存此響應(yīng),該響應(yīng)只能應(yīng)用于瀏覽器私有緩存中。
  • Cache-Control: public:表示該響應(yīng)可以被任何中間人(比如中間代理、CDN等)緩存。若指定了"public",則一些通常不被中間人緩存的頁面(默認是private)(比如 帶有HTTP驗證信息(帳號密碼)的頁面 或 某些特定狀態(tài)碼的頁面),將會被其緩存。
  • Cache-Control: max-age=31536000: 表示資源能被緩存的最大時間,單位秒。
  • Cache-Control: must-revalidate: 緩存在考慮使用一個陳舊的資源時,必須先驗證它的狀態(tài),已過期的緩存將不被使用。
  • 強制緩存

    在某個資源的響應(yīng)中,如果 Cache-Control:max-age=31536000, 則表明這個資源在未來一年內(nèi)再次請求可以直接從緩存中拿,如第一次請求 avatar.png 時,響應(yīng)里標明最大有效時間為 600s (10 分鐘),第二次再次請求該資源時,從 size 列就可以看倒該資源直接從緩存返回。

    第一次,未使用緩存第二次,使用強制緩存
    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GR4bBSEk-1613829654718)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210115164159878.png)]

    這里的緩存就是強制緩存,只要在10分鐘內(nèi),都可以使用緩存的資源。

    如果過了10分鐘,緩存中的這個資源就可能是過期了的,這時就需要詢問服務(wù)器這個資源是不是“新鮮”的,具體客戶端會向服務(wù)器發(fā)起一個攜帶 [If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) 頭的請求:

    如果這個資源是“新鮮”的,服務(wù)器會返回 304 (Not Modified)(該響應(yīng)不會有帶有實體信息),如果服務(wù)器發(fā)現(xiàn)這個資源已經(jīng)過期了,則會返回新的資源。

    對比緩存

    與強制緩存不同,對比緩存每次使用緩存數(shù)據(jù)前都會向服務(wù)器查詢該資源是否有效,但由于查詢和響應(yīng)大部分情況下都只包含頭部,所以比起不使用緩存,對比緩存也可以大大提高響應(yīng)速度和降低服務(wù)器壓力。它依賴于下面幾個頭部字段:

    • Last-Modified: 響應(yīng)頭字段,告訴客戶端這個資源最后更新的時間
    • If-Modified-Since: 請求頭字段,如果請求頭中攜帶了這個字段,服務(wù)器會將該字段的值和資源最后修改的時間做對比,如果最后修改的時間大于字段值,說明數(shù)據(jù)已經(jīng)被修改,則響應(yīng) 200, 返回最新的資源,否則,響應(yīng) 304 告訴客戶端資源未修改,可以使用緩存。

    上面兩個頭部字段是根據(jù)修改時間判斷資源是否是新鮮的,這樣做的準確度不是很高,還有一組頭部字段 ETag 和 If-None-Match 使用資源的唯一標識來判斷資源是否被修改:

    • ETag: 響應(yīng)頭字段,用于服務(wù)器告訴客戶端資源的唯一標識(標識的生成規(guī)則由服務(wù)端確定)
    • If-None-Match: 請求頭字段,如果請求頭中包含此字段,服務(wù)端會對比該字段的值與最新的資源的標識,如果不相同,說明資源被修改,響應(yīng) 200, 返回最新的資源,否則,響應(yīng) 304.

    ETag 和 If-None-Match 的優(yōu)先級高于 Last-Modified 和 If-Modified-Since

    除此之外,與緩存相關(guān)的還有一個請求頭:Vary, 用來決定客戶端使用新資源還是緩存資源,使用vary頭有利于內(nèi)容服務(wù)的動態(tài)多樣性。例如,使用Vary: User-Agent頭,緩存服務(wù)器需要通過UA判斷是否使用緩存的頁面。如果需要區(qū)分移動端和桌面端的展示內(nèi)容,利用這種方式就能避免在不同的終端展示錯誤的布局。另外,它可以幫助 Google 或者其他搜索引擎更好地發(fā)現(xiàn)頁面的移動版本,并且告訴搜索引擎沒有引入Cloaking。

    使用緩存

    說了這么多,我們應(yīng)該怎么通過使用緩存來提高站點的性能呢?

    首先,對于私有緩存,開發(fā)者一般是不需要關(guān)注的,瀏覽器會自動緩存請求成功的 GET 數(shù)據(jù),用來支持后退等功能。我們一般關(guān)注的是共有緩存,也就是在代理服務(wù)器上緩存數(shù)據(jù),客戶端請求到代理服務(wù)器上后,就可以直接返回了,下面以 Nginx 為例,簡單說明如何使用緩存。

    http {# 緩存配置proxy_cache_path /usr/share/nginx/cache levels=1:2 keys_zone=server_cache:10m max_size=5g inactive=60m use_temp_path=off;# 博客后端反代server {listen 8888 ssl http2;access_log /var/log/nginx/admin/access_fd.log smail;error_log /var/log/nginx/admin/error_fd.log;location ~ /api/ {proxy_cache server_cache;proxy_cache_valid 200 304 302 1h;proxy_cache_methods GET HEAD POST;add_header X-Proxy-Cache $upstream_cache_status;proxy_pass http://39.106.168.39:8888;}error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}}} }

    如上,proxy_cache_path 用來配置緩存數(shù)據(jù)保存的路徑,里面的主要字段含義如下:

    • levels: 在單個目錄中包含大量文件會降低文件訪問速度,因此我們建議對大多數(shù)部署使用兩級目錄層次結(jié)構(gòu)。如果未包含 levels Nginx會將所有文件放在同一目錄中。
    • keys_zone: 設(shè)置共享內(nèi)存區(qū)域,用于存儲緩存鍵和元數(shù)據(jù),后面的參數(shù)表示該區(qū)域的大小,一般來說,1 MB區(qū)域可以存儲大約8,000個 key 數(shù)據(jù)。
    • max-size: 緩存能占的最大內(nèi)存。
    • inactive:指定項目在未被訪問的情況下可以保留在緩存中的時間長度。在此示例中,緩存管理器進程會自動從緩存中刪除1分鐘未請求的文件,無論其是否已過期。默認值為10分鐘(10m)。非活動內(nèi)容與過期內(nèi)容不同。Nginx 不會自動刪除緩存header定義為已過期內(nèi)容(例如 Cache-Control:max-age=120)。過期(陳舊)內(nèi)容僅在指定時間內(nèi)未被訪問時被刪除。訪問過期內(nèi)容時,Nginx 會從原始服務(wù)器刷新它并重置inactive計時器。

    其次,我們在 Location 塊中配置了幾個值:

    • proxy_cache:定義用于緩存的共享內(nèi)存區(qū)域。
    • proxy_cache_valid: 指定哪些狀態(tài)的響應(yīng)可以被緩存。
    • proxy_cache_methods: 哪些方法的請求可以被緩存。

    除此之外,我們添加了一個響應(yīng)頭部字段 X-Proxy-Cache 用來查看緩存是否生效。

    這里只是簡單對數(shù)據(jù)進行了緩存,服務(wù)端沒有提供緩存驗證的功能,所以可能出現(xiàn)服務(wù)端數(shù)據(jù)已經(jīng)改變但緩存沒更新的情況。

    HTTPS 細節(jié)

    HTTPS 的原理

    密碼學(xué)基礎(chǔ)

    對稱加密和非對稱加密
    • 對稱加密: 加密和解密時使用的密鑰是一樣的,比如 DES, 優(yōu)點是速度快,缺點是在協(xié)商密鑰時,可能會泄露密鑰

    • 非對稱加密:有兩個密鑰,公鑰和私鑰,使用公鑰加密,私鑰解密,公鑰是公開的,比如 RSA。

      非對稱加密有一個形象的比喻:

      A有一份機密文件,想要發(fā)給B,發(fā)之前先向B要一個打開的保險柜,把文件裝進保險柜鎖住后再發(fā)給B,整個過程中保險柜密碼只有B知道,這里的保險柜相當于公鑰,保險柜密碼相當于私鑰。

    CA機構(gòu)和證書

    公鑰是公開的,那怎么證明這個公鑰是屬于你的呢?接上面的比喻,如果有一個中間人 C,在 B 向 A 發(fā)保險柜的時候?qū)?B 的保險柜換成自己的發(fā)給 A,這樣 C 就可以竊取到文件,A 如果想要驗證這個保險柜是不是 B 的,就需要一個 A、B 都信任的第三方機構(gòu),B 在發(fā)給 A 之前請求第三方機構(gòu)在保險箱上蓋一個戳,A 收到后再請求第三方機構(gòu)檢查戳是不是真的就可以了,這里的第三方機構(gòu)就是 CA 機構(gòu),這個戳就是證書,具體來說:

    每個CA機構(gòu)都會有自己的一組密鑰對(CA 的公鑰是通信雙方都信任的),現(xiàn)在 B 有一個公鑰,他要證明這個公鑰是他的,就需要向 CA 機構(gòu)請求一份該公鑰的證書,請求時,B 需要向 CA 機構(gòu)提供自己的信息以及要認證的公鑰(這些信息會組成CSR文件),CA 機構(gòu)收到請求后,會檢查 CSR 的真實性,檢查無誤后,CA 會將 CSR 的內(nèi)容哈希后用自己的私鑰簽名,然后將 CSR 中的信息和簽名組合成證書發(fā)給 B。

    所以一份證書中包含的典型內(nèi)容包括:

  • 明文的證書持有者信息
  • 明文的公鑰
  • CA 的簽名
  • 證書用途,使用的算法,證書過期時間,頒發(fā)者信息,CRL分發(fā)點等
  • B 有了 CA 機構(gòu)的證書,在向 A 發(fā)送公鑰時就只需要發(fā)送證書了,A 收到證書,用 CA 機構(gòu)的公鑰解密簽名,然后對證書中的明文數(shù)據(jù)以同樣的算法做哈希,只需要對比兩個哈希值就可以判斷證書有沒有被篡改了,如果證書沒被篡改,則可以放心使用證書中的公鑰與 B 通信了。

    HTTPS 通信流程

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8HD5SxsD-1613829654732)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210122163508612.png)]

    看上面的截圖,前三行 [SYN], [SYN, ACK], [ACK] 是典型的 TCP 三次握手,那么在三次握手后,客戶端向服務(wù)端以 TLSV1.2 協(xié)議向服務(wù)端發(fā)送了一個 client Hello 包,通過 Client Hello, 客戶端會生成一個隨機數(shù),并告訴服務(wù)端自己支持的加密,哈希等算法,我們可以在這個報文里看到這些內(nèi)容:

    其中,Random 就是客戶端選取的隨機數(shù),Cipher Suites 中就是客戶端支持的算法。

    接下來就是 Server Hello, 在這一步,服務(wù)端同樣會生成一個隨機數(shù),并且會從客戶端支持的算法中選取一種,通過 Server Hello 的方式告訴客戶端:

    可以看到 Server Hello 和 Client Hello 的報文內(nèi)容區(qū)別不大,只是 Client Hello 中的 Cipher Suite 有許多項,而 Server Hello 的只有一項,因為服務(wù)端會選擇安全性最高的加密方式,需要注意的是這里選擇的是一組算法,以這里選擇的 0xc02f 為例,它的名字叫 TLS_ECDHE_RSA_WITH_AES_128_GCM_ SHA256 (0xc02f) 其中包括:

    • 密鑰交換算法:ECDHE
    • 身份驗證算法:RSA
    • 對稱加密算法:AES_128_GCM
    • 摘要算法:SHA256

    在這之后,服務(wù)端在一個 TLS 包里進行了三個負載:

    • Certificate:發(fā)送證書
    • Server Key Exchange:包含密鑰交換算法 DHE/ECDHE 所需要的額外參數(shù)。
    • Server Hello Done:表明服務(wù)端相關(guān)信息發(fā)送結(jié)束,這之后服務(wù)端會等待客戶端響應(yīng)。

    到這一步,客戶端已經(jīng)拿到了服務(wù)器的證書,會檢查證書是否有效,如果證書失效,客戶端瀏覽器會阻止后續(xù)操作,反之,客戶端會繼續(xù)與服務(wù)端協(xié)商對稱加密密鑰:

    客戶端向服務(wù)端發(fā)送一個響應(yīng)(id = 67)包含三個負載:

    • Client Key Exchange:類似 Server Key Exchange,客戶端生成一個新的隨機數(shù)(Premaster secret),并使用數(shù)字證書中的公鑰加密后發(fā)給服務(wù)端。
    • Change cipher Spec: 已被廢棄,不攜帶數(shù)據(jù)
    • Encrypted handshake message:這個步驟客戶端和服務(wù)器在握手完后都會進行,以告訴對方自己在整個握手過程中收到了什么數(shù)據(jù),發(fā)送了什么數(shù)據(jù),保證中間沒人篡改報文。

    到現(xiàn)在為止,我們總結(jié)一下客戶端和服務(wù)器做了什么:

  • 客戶端生成了一個隨機數(shù) Client Random 發(fā)給了服務(wù)端
  • 服務(wù)端也生成了一個隨機數(shù) Server Random 發(fā)給了客戶端,同時,雙方還協(xié)商了以后要用到的哈希,加密算法。
  • 服務(wù)端把自己的證書發(fā)給了客戶端。
  • 客戶端又生成了一個新隨機數(shù),并用服務(wù)端證書中的公鑰進行加密后發(fā)給了服務(wù)端。
  • 客戶端和服務(wù)端通過互發(fā) Encrypted handshake message 確保了數(shù)據(jù)沒被 中間人篡改。
  • 現(xiàn)在,根據(jù)三個隨機數(shù),客戶端和服務(wù)器就會根據(jù)約定好的對稱加密算法生成最終的對稱加密密鑰,后續(xù)的數(shù)據(jù)傳輸就會使用該密鑰加密。

    總結(jié)

    HTTPS 建立連接的過程總結(jié)如下:

  • TCP 三次握手
  • 客戶端向服務(wù)器發(fā)送 Client Hello 包,包含 TLS 版本,客戶端生成的隨機數(shù) Client Random, 客戶端支持的算法等信息
  • 服務(wù)器向客戶端發(fā)送 Server Hello 包,包含服務(wù)端生成的隨機數(shù) Server Random,服務(wù)端選擇的算法等信息。
  • 服務(wù)端向客戶端發(fā)送證書。
  • 客戶端檢查證書有效后,生成一個新的隨機數(shù) Premaster secret 并用證書中的公鑰加密發(fā)給服務(wù)端。
  • 服務(wù)端使用自己的私鑰解密 Premaster secret。
  • 客戶端和服務(wù)端分別使用三個隨機數(shù),依照約定的算法生成對話密鑰 session key。
  • 客戶端和服務(wù)端使用 session key 加密后續(xù)的會話。
  • 使用 HTTPS

    nginx 配置示例:

    server {listen 443 ssl http2;server_name *.junebao.top;# 證書ssl_certificate 1_www.junebao.top_bundle.crt;# 私鑰ssl_certificate_key 2_www.junebao.top.key;ssl_session_timeout 5m;ssl_protocols TLSv1 TLSv1.1 TLSv1.2;ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;ssl_prefer_server_ciphers on;error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}}

    HTTP/2 細節(jié)

    實現(xiàn)

    2015 年,HTTP/2 發(fā)布。HTTP/2 是現(xiàn)行 HTTP 協(xié)議(HTTP/1.x)的替代,但它不是重寫,HTTP 方法/狀態(tài)碼/語義都與 HTTP/1.x 一樣。HTTP/2 基于 SPDY3,專注于性能,最大的一個目標是在用戶和網(wǎng)站間只用一個連接(connection)。

    那么SPDY3是什么呢?

    SPDY是谷歌自行研發(fā)的 SPDY 協(xié)議,主要解決 HTTP/1.1 效率不高的問題。谷歌推出 SPDY,才算是正式改造 HTTP 協(xié)議本身。降低延遲,壓縮 header 等等,SPDY 的實踐證明了這些優(yōu)化的效果,也最終帶來 HTTP/2 的誕生。

    HTTP/2 由兩個規(guī)范(Specification)組成:

  • Hypertext Transfer Protocol version 2 - RFC7540
  • HPACK - Header Compression for HTTP/2 - RFC7541
  • 那么HTTP2在HTTP1.1的基礎(chǔ)上做了哪些改進

    • 二進制傳輸
    • 請求和響應(yīng)復(fù)用
    • Header壓縮
    • Server Push(服務(wù)端推送)

    二進制傳輸

    HTTP/2 采用二進制格式傳輸數(shù)據(jù),而非 HTTP 1.x 的文本格式,二進制協(xié)議解析起來更高效。 HTTP / 1 的請求和響應(yīng)報文,都是由起始行,首部和實體正文(可選)組成,各部分之間以文本換行符分隔。HTTP/2 將請求和響應(yīng)數(shù)據(jù)分割為更小的幀,并且它們采用二進制編碼

    新的二進制分幀機制改變了客戶端與服務(wù)器之間交換數(shù)據(jù)的方式。 為了說明這個過程,我們需要了解 HTTP/2 的三個概念:

    • 數(shù)據(jù)流:已建立的連接內(nèi)的雙向字節(jié)流,可以承載一條或多條消息。
    • 消息:與邏輯請求或響應(yīng)消息對應(yīng)的完整的一系列幀。
    • 幀:HTTP/2 通信的最小單位,每個幀都包含幀頭,至少也會標識出當前幀所屬的數(shù)據(jù)流。

    這些概念的關(guān)系總結(jié)如下:

    • 所有通信都在一個 TCP 連接上完成,此連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。
    • 每個數(shù)據(jù)流都有一個唯一的標識符和可選的優(yōu)先級信息,用于承載雙向消息。
    • 每條消息都是一條邏輯 HTTP 消息(例如請求或響應(yīng)),包含一個或多個幀。
    • 幀是最小的通信單位,承載著特定類型的數(shù)據(jù),例如 HTTP 標頭、消息負載等等。 來自不同數(shù)據(jù)流的幀可以交錯發(fā)送,然后再根據(jù)每個幀頭的數(shù)據(jù)流標識符重新組裝。

    請求和響應(yīng)復(fù)用

    在 HTTP/1.x 中,如果客戶端要想發(fā)起多個并行請求以提升性能,則必須使用多個 TCP 連接,這是 HTTP/1.x 交付模型的直接結(jié)果,該模型可以保證每個連接每次只交付一個響應(yīng)(響應(yīng)排隊)。 更糟糕的是,這種模型也會導(dǎo)致隊首阻塞,從而造成底層 TCP 連接的效率低下。

    HTTP/2 中新的二進制分幀層突破了這些限制,實現(xiàn)了完整的請求和響應(yīng)復(fù)用:客戶端和服務(wù)器可以將 HTTP 消息分解為互不依賴的幀,然后交錯發(fā)送,最后再在另一端把它們重新組裝起來。

    在 HTTP/2 中,有了二進制分幀之后,HTTP /2 不再依賴 TCP 鏈接去實現(xiàn)多流并行了,在 HTTP/2 中:

    • 同域名下所有通信都在單個連接上完成。
    • 單個連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。
    • 數(shù)據(jù)流以消息的形式發(fā)送,而消息又由一個或多個幀組成,多個幀之間可以亂序發(fā)送,因為根據(jù)幀首部的流標識可以重新組裝。

    這一特性,使性能有了極大提升:

    • 同個域名只需要占用一個 TCP 連接,使用一個連接并行發(fā)送多個請求和響應(yīng),消除了因多個 TCP 連接而帶來的延時和內(nèi)存消耗。
    • 并行交錯地發(fā)送多個請求,請求之間互不影響。
    • 并行交錯地發(fā)送多個響應(yīng),響應(yīng)之間互不干擾。
    • 在 HTTP/2 中,每個請求都可以帶一個 31bit 的優(yōu)先值,0 表示最高優(yōu)先級, 數(shù)值越大優(yōu)先級越低。有了這個優(yōu)先值,客戶端和服務(wù)器就可以在處理不同的流時采取不同的策略,以最優(yōu)的方式發(fā)送流、消息和幀。

    Header壓縮

    在 HTTP/1 中,我們使用文本的形式傳輸 header,在 header 攜帶 cookie 的情況下,可能每次都需要重復(fù)傳輸幾百到幾千的字節(jié)。為了減少這塊的資源消耗并提升性能,HTTP/2 使用 HPACK 壓縮格式壓縮請求和響應(yīng)標頭元數(shù)據(jù),這種格式采用兩種強大的技術(shù):

  • 這種格式支持通過靜態(tài)霍夫曼代碼對傳輸?shù)臉祟^字段進行編碼,從而減小了各個傳輸?shù)拇笮 ?/li>
  • 這種格式要求客戶端和服務(wù)器同時維護和更新一個包含之前見過的標頭字段的索引列表(換句話說,它可以建立一個共享的壓縮上下文),此列表隨后會用作參考,對之前傳輸?shù)闹颠M行有效編碼。
  • 利用霍夫曼編碼,可以在傳輸時對各個值進行壓縮,而利用之前傳輸值的索引列表,我們可以通過傳輸索引值的方式對重復(fù)值進行編碼,索引值可用于有效查詢和重構(gòu)完整的標頭鍵值對。

    作為一種進一步優(yōu)化方式,HPACK 壓縮上下文包含一個靜態(tài)表和一個動態(tài)表:靜態(tài)表在規(guī)范中定義,并提供了一個包含所有連接都可能使用的常用 HTTP 標頭字段(例如,有效標頭名稱)的列表;動態(tài)表最初為空,將根據(jù)在特定連接內(nèi)交換的值進行更新。 因此,為之前未見過的值采用靜態(tài) Huffman 編碼,并替換每一側(cè)靜態(tài)表或動態(tài)表中已存在值的索引,可以減小每個請求的大小。

    注:在 HTTP/2 中,請求和響應(yīng)標頭字段的定義保持不變,僅有一些微小的差異:所有標頭字段名稱均為小寫,請求行現(xiàn)在拆分成各個 :method、:scheme、:authority 和 :path 偽標頭字段。

    如需了解有關(guān) HPACK 壓縮算法的完整詳情,請參閱 IETF HPACK - HTTP/2 的標頭壓縮。

    Server Push

    HTTP/2 新增的另一個強大的新功能是,服務(wù)器可以對一個客戶端請求發(fā)送多個響應(yīng)。 換句話說,除了對最初請求的響應(yīng)外,服務(wù)器還可以向客戶端推送額外資源如下圖所示,而無需客戶端明確地請求。

    為什么在瀏覽器中需要一種此類機制呢?一個典型的網(wǎng)絡(luò)應(yīng)用包含多種資源,客戶端需要檢查服務(wù)器提供的文檔才能逐個找到它們。 那為什么不讓服務(wù)器提前推送這些資源,從而減少額外的延遲時間呢? 服務(wù)器已經(jīng)知道客戶端下一步要請求什么資源,這時候服務(wù)器推送即可派上用場。

    事實上,如果您在網(wǎng)頁中內(nèi)聯(lián)過 CSS、JavaScript,或者通過數(shù)據(jù) URI 內(nèi)聯(lián)過其他資產(chǎn)(請參閱資源內(nèi)聯(lián)),那么您就已經(jīng)親身體驗過服務(wù)器推送了。 對于將資源手動內(nèi)聯(lián)到文檔中的過程,我們實際上是在將資源推送給客戶端,而不是等待客戶端請求。 使用 HTTP/2,我們不僅可以實現(xiàn)相同結(jié)果,還會獲得其他性能優(yōu)勢。 推送資源可以進行以下處理:

    • 由客戶端緩存
    • 在不同頁面之間重用
    • 與其他資源一起復(fù)用
    • 由服務(wù)器設(shè)定優(yōu)先級
    • 被客戶端拒絕
    服務(wù)端推送如何實現(xiàn)

    所有服務(wù)器推送數(shù)據(jù)流都由 PUSH_PROMISE 幀發(fā)起,表明了服務(wù)器向客戶端推送所述資源的意圖,并且需要先于請求推送資源的響應(yīng)數(shù)據(jù)傳輸。 這種傳輸順序非常重要:客戶端需要了解服務(wù)器打算推送哪些資源,以免為這些資源創(chuàng)建重復(fù)請求。 滿足此要求的最簡單策略是先于父響應(yīng)(即,DATA 幀)發(fā)送所有 PUSH_PROMISE 幀,其中包含所承諾資源的 HTTP 標頭。

    在客戶端接收到 PUSH_PROMISE 幀后,它可以根據(jù)自身情況選擇拒絕數(shù)據(jù)流(通過 RST_STREAM 幀)。 (例如,如果資源已經(jīng)位于緩存中,便可能會發(fā)生這種情況。) 這是一個相對于 HTTP/1.x 的重要提升。 相比之下,使用資源內(nèi)聯(lián)(一種受歡迎的 HTTP/1.x“優(yōu)化”)等同于“強制推送”:客戶端無法選擇拒絕、取消或單獨處理內(nèi)聯(lián)的資源。

    使用 HTTP/2,客戶端仍然完全掌控服務(wù)器推送的使用方式。 客戶端可以限制并行推送的數(shù)據(jù)流數(shù)量;調(diào)整初始的流控制窗口以控制在數(shù)據(jù)流首次打開時推送的數(shù)據(jù)量;或完全停用服務(wù)器推送。 這些優(yōu)先級在 HTTP/2 連接開始時通過 SETTINGS 幀傳輸,可能隨時更新。

    過渡到 HTTP/2

    上面說了這么多,我們要如何啟用HTTP2呢?

    如果你使用的是 nginx,那么你只需要加一個 http2 即可:

    server {listen 8888 ssl http2;server_name *.junebao.top;# ... }

    如果你使用 Golang 的 Gin 框架,他默認支持 HTTP/2,你可以使用 RunTLS() 使用 HTTP/2,如下:

    package mainimport ("github.com/gin-gonic/gin" )func main() {engine := gin.Default()engine.GET("./", func(context *gin.Context) {context.JSON(200, map[string]string{"msg": "ok"})})// 服務(wù)端推送engine.Static("/static", "./static")engine.GET("/push", func(context *gin.Context) {pusher := context.Writer.Pusher()if pusher != nil {err := pusher.Push("/static/test.js", nil)if err != nil {log.Println("push fail", err)}}context.JSON(200, map[string]string{"msg": "ok"})})engine.RunTLS(":8888", "./root_cer.cer", "./root_private_key.pem") }

    如果你使用的是 spring boot 內(nèi)置的 Tomcat 服務(wù)器,那么只需要在配置文件中添加配置:

    server:http2:enabled: on

    只有 Tomcat 9 版本之后版本才支持 HTTP/2 協(xié)議。在 conf/server.xml 中增加內(nèi)容:

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true"> <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/> <SSLHostConfig honorCipherOrder="false"> <Certificate certificateKeyFile="conf/ca.key" certificateFile="conf/ca.crt"/> </SSLHostConfig> </Connector>

    HTTP/3 細節(jié)

    為什么要出現(xiàn)HTTP3

    雖然 HTTP/2 解決了很多之前舊版本的問題,但是它還是存在一個巨大的問題,主要是底層支撐的 TCP 協(xié)議造成的。

    上文提到 HTTP/2 使用了多路復(fù)用,一般來說同一域名下只需要使用一個 TCP 連接。但當這個連接中出現(xiàn)了丟包的情況,那就會導(dǎo)致 HTTP/2 的表現(xiàn)情況反倒不如 HTTP/1 了。

    因為在出現(xiàn)丟包的情況下,整個 TCP 都要開始等待重傳,也就導(dǎo)致了后面的所有數(shù)據(jù)都被阻塞了。但是對于 HTTP/1.1 來說,可以開啟多個 TCP 連接,出現(xiàn)這種情況反到只會影響其中一個連接,剩余的 TCP 連接還可以正常傳輸數(shù)據(jù)。

    那么可能就會有人考慮到去修改 TCP 協(xié)議,其實這已經(jīng)是一件不可能完成的任務(wù)了。因為 TCP 存在的時間實在太長,已經(jīng)充斥在各種設(shè)備中,并且這個協(xié)議是由操作系統(tǒng)實現(xiàn)的,更新起來不大現(xiàn)實。

    基于這個原因,Google 就更起爐灶搞了一個基于 UDP 協(xié)議的 QUIC 協(xié)議,并且使用在了 HTTP/3 上,HTTP/3 之前名為 HTTP-over-QUIC,從這個名字中我們也可以發(fā)現(xiàn),HTTP/3 最大的改造就是使用了 QUIC(快速 UDP Internet 連接)。

    QUIC

    QUIC(Quick UDP Internet Connection)是谷歌制定的一種基于UDP的低時延的互聯(lián)網(wǎng)傳輸層協(xié)議。在2016年11月國際互聯(lián)網(wǎng)工程任務(wù)組(IETF)召開了第一次QUIC工作組會議,受到了業(yè)界的廣泛關(guān)注。這也意味著QUIC開始了它的標準化過程,成為新一代傳輸層協(xié)議 。

    優(yōu)勢:

  • 高效地建立連接: 將 TLS 協(xié)商密鑰和協(xié)議作為打開連接握手地一部分,加上對 client Hello 地緩存,在大部分情況下,QUIC 建立連接只需要 0RTT,而普通的 HTTPS 則至少需要 2RTT
  • 改進地擁塞控制方案:TCP 擁塞控制包括:慢啟動,擁塞避免,快速重傳,快速恢復(fù),QUIC 在 UDP 基礎(chǔ)上實現(xiàn)了相關(guān)算法并做了改進,使之具有可插拔,高效地特點。
  • 基于 stream 和 connecton 級別的流量控制。
  • 沒有 TCP 隊頭阻塞地多路復(fù)用:HTTP/2 通過二進制分幀層避免了 HTTP 隊頭阻塞,但由于依然使用 TCP 協(xié)議,就避免不了 TCP 隊頭阻塞,QUIC 基于 UDP,多個 stream 之間沒有依賴。這樣假如 stream2 丟了一個 udp packet,也只會影響 stream2 的處理。不會影響 stream2 之前及之后的 stream 的處理,這也就在很大程度上緩解甚至消除了隊頭阻塞的影響。
  • 加密認證地報文:TCP 協(xié)議頭部沒有經(jīng)過任何加密和認證,所以在傳輸過程中很容易被中間網(wǎng)絡(luò)設(shè)備篡改,注入和竊聽。比如修改序列號、滑動窗口。這些行為有可能是出于性能優(yōu)化,也有可能是主動攻擊。但是 QUIC 的 packet 可以說是武裝到了牙齒。除了個別報文比如 PUBLIC_RESET 和 CHLO,所有報文頭部都是經(jīng)過認證的,報文 Body 都是經(jīng)過加密的.
  • 連接遷移:一條 TCP 連接是由四元組標識的(源 IP,源端口,目的 IP,目的端口)。什么叫連接遷移呢?就是當其中任何一個元素發(fā)生變化時,這條連接依然維持著,能夠保持業(yè)務(wù)邏輯不中斷。當然這里面主要關(guān)注的是客戶端的變化,因為客戶端不可控并且網(wǎng)絡(luò)環(huán)境經(jīng)常發(fā)生變化,而服務(wù)端的 IP 和端口一般都是固定的,而 QUIC 連接不再以 IP 及端口四元組標識,而是以一個 64 位的隨機數(shù)作為 ID 來標識,這樣就算 IP 或者端口發(fā)生變化時,只要 ID 不變,這條連接依然維持著,上層業(yè)務(wù)邏輯感知不到變化,不會中斷,也就不需要重連。由于這個 ID 是客戶端隨機產(chǎn)生的,并且長度有 64 位,所以沖突概率非常低。
  • 向前糾錯: 每個數(shù)據(jù)包除了它本身的內(nèi)容之外,還包括了部分其他數(shù)據(jù)包的數(shù)據(jù),因此少量的丟包可以通過其他包的冗余數(shù)據(jù)直接組裝而無需重傳。向前糾錯犧牲了每個數(shù)據(jù)包可以發(fā)送數(shù)據(jù)的上限,但是減少了因為丟包導(dǎo)致的數(shù)據(jù)重傳,因為數(shù)據(jù)重傳將會消耗更多的時間(包括確認數(shù)據(jù)包丟失、請求重傳、等待新數(shù)據(jù)包等步驟的時間消耗);假如說這次我要發(fā)送三個包,那么協(xié)議會算出這三個包的異或值并單獨發(fā)出一個校驗包,也就是總共發(fā)出了四個包。當出現(xiàn)其中的非校驗包丟包的情況時,可以通過另外三個包計算出丟失的數(shù)據(jù)包的內(nèi)容。當然這種技術(shù)只能使用在丟失一個包的情況下,如果出現(xiàn)丟失多個包就不能使用糾錯機制了,只能使用重傳的方式了
  • 證書壓縮等.
  • 可見HTTP3在效率上和安全性上都有了很大程度上的修改,但是由于目前這個標準還在論證中,Nginx等也只是在測試版中加入了對HTTP3的支持,等到技術(shù)真正的論證實現(xiàn)完成,我們就可以使用上快速且安全的HTTP3協(xié)議了,期待著這一天的到來。

    參考

    mozilla 開發(fā)文檔

    谷歌開發(fā)文檔 HTTP2 簡介

    Nginx緩存最佳實踐

    讓互聯(lián)網(wǎng)更快:新一代QUIC協(xié)議在騰訊的技術(shù)實踐分享

    總結(jié)

    以上是生活随笔為你收集整理的关于 HTTP 的一切(HTTP/1.1,HTTP/2,HTTP/3,HTTPS, CORS, 缓存 ,无状态)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。