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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

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

HTTP

為什么會(huì)出現(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ā)起的這個(gè)請(qǐng)求被稱為 Request, 服務(wù)端接受到客戶端的請(qǐng)求后,會(huì)返回給客戶端所請(qǐng)求的資源,這一過程被稱為 Response,在大部分情況下,客戶端和服務(wù)器之間還可能存在許多 proxies,他們的作用可能各不相同,有些可能作為網(wǎng)關(guān)存在,有些可能作為緩存存在。

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

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

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

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

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

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

    HTTP/0.9 on-line

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

    GET /index.html

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

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

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

    HTTP/1.0

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

  • 版本號(hào)被添加到了請(qǐng)求頭上,像下面這樣:

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

  • 請(qǐng)求方法拓展到了 GET,HEAD,POST

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

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

  • HTTP/1.1

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

    1.1 版本的改進(jìn)包括:

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

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

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

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

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

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

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

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

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

  • HTTPS

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

    HTTP/2

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

    2015 年 5 月, HTTP/2 正式標(biāo)準(zhǔn)化,他與 1.x 版本 不同在于:

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

    HTTP/3

    HTTP/3 是即將到來的第三個(gè)主要版本的 HTTP 協(xié)議,在 HTTP/3 中,將棄用 TCP 協(xié)議,改為使用基于 UDP 的 QUIC 協(xié)議實(shí)現(xiàn)。QUIC(快速UDP網(wǎng)絡(luò)連接)是一種實(shí)驗(yàn)性的網(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 的正式請(qǐng)求,以“明確地將其標(biāo)識(shí)為HTTP語義的另一個(gè)綁定……使人們理解它與 QUIC 的不同”,并在最終確定并發(fā)布草案后,將 QUIC 工作組繼承到 HTTP 工作組, 在隨后的幾天討論中,Mark Nottingham 的提議得到了 IETF 成員的接受,他們?cè)?018年11月給出了官方批準(zhǔn),認(rèn)可 HTTP-over-QUIC 成為 HTTP/3。

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

    HTTP/1.1 細(xì)節(jié)

    HTTP報(bào)文

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

    請(qǐng)求頭格式

    請(qǐng)求頭第一行為請(qǐng)求行,其余為請(qǐng)求頭字段:如下:

    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

    請(qǐng)求行由三部分組成:

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

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

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

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

    響應(yīng)頭格式

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

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

    無狀態(tài)的 HTTP

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

    cookie

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

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

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

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

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

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

    了解更多:

    超級(jí) Cookie 和僵尸 Cookie

    決戰(zhàn)僵尸 Cookie

    SESSION

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

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

    TOKEN

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

    JWT

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

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

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

    分布式解決方案

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

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

    連接管理

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

    短連接

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

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

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

    長(zhǎng)連接

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

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

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

    在 HTTP/1.1 里,默認(rèn)就是長(zhǎng)連接的,協(xié)議頭都不用再去聲明它(但我們還是會(huì)把它加上,萬一某個(gè)時(shí)候因?yàn)槟撤N原因要退回到 HTTP/1.0 呢)。

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

    流水線

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

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

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

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

    三者比較

    CORS

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

    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,跨域資源共享),它首先是一個(gè)系統(tǒng),由一系列 HTTP 頭組成,這些 HTTP 頭決定了瀏覽器是否阻止前端 JavaScript 代碼獲取跨域請(qǐng)求的響應(yīng)。

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

    同源安全策略

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

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

    原理

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

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

  • 請(qǐng)求方法是以下三種方法之一:

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

    • Accept

    • Accept-Language

    • Content-Language

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

    • DPR

    • Downlink

    • Save-Data

    • Width

    • Viewport-Width

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

  • 請(qǐng)求中沒有使用 ReadableStream 對(duì)象。

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

    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);} }

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

    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ù)檢請(qǐng)求頭頭中最重要的部分有下面幾個(gè):

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

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

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

    接受到響應(yīng)后,瀏覽器會(huì)自動(dòng)判斷實(shí)際請(qǐng)求是否被允許,如果不被允許,將會(huì)報(bào)上面的錯(cuò)誤。

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

    HTTP 緩存

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

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

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

    緩存控制

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

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

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

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

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

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

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

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

    對(duì)比緩存

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

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

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

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

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

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

    使用緩存

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

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

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

    其次,我們?cè)?Location 塊中配置了幾個(gè)值:

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

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

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

    HTTPS 細(xì)節(jié)

    HTTPS 的原理

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

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

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

      非對(duì)稱加密有一個(gè)形象的比喻:

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

    CA機(jī)構(gòu)和證書

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

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

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

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

    HTTPS 通信流程

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(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ā)送了一個(gè) client Hello 包,通過 Client Hello, 客戶端會(huì)生成一個(gè)隨機(jī)數(shù),并告訴服務(wù)端自己支持的加密,哈希等算法,我們可以在這個(gè)報(bào)文里看到這些內(nèi)容:

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

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

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

    • 密鑰交換算法:ECDHE
    • 身份驗(yàn)證算法:RSA
    • 對(duì)稱加密算法:AES_128_GCM
    • 摘要算法:SHA256

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

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

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

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

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

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

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

    總結(jié)

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

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

    實(shí)現(xiàn)

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

    那么SPDY3是什么呢?

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

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

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

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

    二進(jìn)制傳輸

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

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

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

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

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

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

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

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

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

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

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

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

    Header壓縮

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

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

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

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

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

    Server Push

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

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

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

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

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

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

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

    過渡到 HTTP/2

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

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

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

    如果你使用 Golang 的 Gin 框架,他默認(rèn)支持 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 細(xì)節(jié)

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

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

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

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

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

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

    QUIC

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

    優(yōu)勢(shì):

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

    參考

    mozilla 開發(fā)文檔

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

    Nginx緩存最佳實(shí)踐

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

    總結(jié)

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

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