HTTP 相關知識瞭解一下

首先了解下瀏覽器輸入url後http請求返回的過程是什麼,看下圖javascript

(一) 首先一開始要作 redirect 重定向,那麼爲何要 redirect 呢,由於瀏覽器可能記錄了你這個地址已經永久跳轉成一個新的地址,因此一開始瀏覽器須要判斷需不須要 redirect 以及 redirect 到哪裏。css

(二) 看緩存,請求的資源可能已經緩存過,在 App cache 裏看是否有緩存,若是沒有緩存,就會去服務器請求資源。html

(三) 輸入域名,域名會對應ip以後才能真正訪問到服務器,因此這時候會先去查找域名對應的IP地址,這就叫DNS解析前端

(四) 有了IP以後,就會建立tcp鏈接,該過程要通過tcp的三次握手以後才能真正建立鏈接。同時若是這個請求是https的,就會建立https的連接,這跟tcp的三次握手不同,中間會有一個保證安全的數據傳輸的過程。java

(五) 鏈接建立好以後,纔會真正發起http請求的數據包,數據包發送完成以後,服務器接收到這個數據並進行處理以後會返回這個請求響應的內容數據,返回數據以後這個http請求才真正完成node

1. 網絡協議分層

咱們先來看下經典五層模型圖例:nginx

這個五層模型中,分爲應用層、傳輸層、網絡層、數據鏈路層、物理層,每個服務器上都會有這樣的層級關係存在來維護整個網絡數據傳輸的過程。web

本文主要內容是http相關內容,因此主要是在應用層展開。http協議基於傳輸層裏的 TCP/IP 協議,該協議會涉及到一些http請求的性能,以及請求過程的消耗,會面也會有一些 TCP/IP 協議的介紹。ajax

低三層:算法

  • 物理層(硬件) - 主要做用是定義物理設備如何傳輸數據
  • 數據鏈路層 - 在通訊的實體間創建數據鏈路鏈接
  • 網絡層 - 爲數據在結點之間傳輸建立邏輯鏈路

1.1 傳輸層

傳輸層主要有兩個協議,一個是 TCP/IP,一個是 UDP。更多狀況下都使用的是 TCP/IP 協議,由於它更可靠的傳輸數據。

向用戶提供可靠的端到端(e2e)服務

我的電腦到網絡服務器創建鏈接以後,若是傳輸數據很大,一次性沒法完成傳輸,就須要分片傳輸,傳輸成功以後再從新組裝。兩端傳輸數據的方式都是在這一層定義

傳輸層向高層屏蔽了下層數據通訊的細節

http協議要傳輸數據只需在瀏覽器輸入url,輸入url這個過程還涉及到一系列數據的拼裝及傳輸,好比分包傳輸具體是怎麼實現,服務器怎麼接收,ajax請求,整個過程傳輸層已經作好了封裝,這個過程用戶不須要知道。

1.2 應用層

http協議所在層

  • 爲應用軟件提供了不少服務
  • 構建於TCP協議之上
  • 屏蔽網絡傳輸相關細節

2. http協議發展歷史

  • HTTP/0.9
    • 只有一個命令GET
    • 沒有HEADER等描述數據的信息
    • 服務器發送完畢,就關閉TCP鏈接
  • HTTP/1.0
    • 增長了不少命令
    • 增長 status code 和 header
    • 多字符集支持、多部分發送、權限、緩存等
  • HTTP/1.1
    • 持久鏈接
    • pipeline
    • 增長 host 和其它一些命令
  • HTTP2
    • 全部數據以二進制傳輸
    • 同一個鏈接裏面發送多個請求再也不須要按照順序來
    • 頭信息壓縮以及推送等提升效率的功能

頭信息壓縮及推送

http2 解決了 http 裏總體性能低下的問題,在http1.1裏,每次發送請求和返回請求,它的http頭都會進行一個完整傳輸,而且不少字段都是以字符串形式保存,佔用大量帶寬。http2裏會將頭信息進行壓縮傳輸。

推送是什麼概念呢?在http1.1裏,客戶端發起請求而後服務端響應請求返回內容,客戶端永遠是主動方,服務端永遠是被動方。在http2裏,服務端是能夠主動發起數據傳輸的。好比:一個html頁面中引入了css和js,瀏覽器首先要對html進行分析,再尋找css和js對應的url去請求對應的文件,這就涉及到一個順序問題,須要先請求到html文本,在瀏覽器裏運行解析了這個文本以後才能發送css及js的請求。可是在http2中服務端能夠主動把css及js文件推送到客戶端,與html並行傳輸,極大提升傳輸效率

3. http的鏈接

在客戶端和服務端之間進行http請求的發送和返回的過程中,須要建立一個 TCP connection。http只有請求和響應這個概念,不存在鏈接,請求和響應都是數據包, 之間要通過一個傳輸的通道,這個傳輸的通道就是在tcp裏建立的一個鏈接(TCP connection)。這個鏈接能夠保持狀態,http請求就是在這個鏈接之上發送的,因此在一個tcp鏈接上能夠發送多個http請求。

在不一樣版本下,http鏈接的模式不同,在http1.0裏,這個鏈接是在http請求建立同時建立tcp鏈接,請求結束後tcp鏈接就會關閉。

在http1.1裏,這個鏈接能夠經過某種方式聲明是否保持鏈接狀態。tcp鏈接在建立過程當中有三次握手消耗,三次握手就表示三次網絡傳輸(客戶端發送 - 服務端響應 - 客戶端再次發送),而後才能發送http請求。若是tcp鏈接一直保持,第二個http請求就沒有三次握手的開銷

在http2裏不只能夠保持tcp的鏈接,同時這個鏈接上的http請求能夠併發,就是說同一個用戶對同一個服務器發起一個網頁請求時只須要一個tcp鏈接。

3.1 tcp的三次握手

(一) 在tcp的三次握手當中,客戶端會向服務端發起一個建立鏈接的數據包請求,這裏會有一個標識爲 SYN=1,SYN是一個標誌位,表示建立請求的數據包。後面會發送一個叫 Seq=X,X表示數字,通常爲1。服務端接收到這個數據包以後就會知道要建立一個鏈接

(二) 建立鏈接以後,服務端就會開啓一個 tcp 端口,返回給客戶端數據,這個數據 SYN=1,ACK=X+1,Seq=Y,客戶端拿到這個數據表示服務端容許建立這個tcp鏈接。

(三) 這時候客戶端再去發送 ACK=Y+1,Seq=Z

那麼爲何tcp要進行三次握手呢,這是爲了防止服務端開啓一些無用鏈接,網絡鏈接具備延時性。客戶端向服務端發起建立鏈接請求時,服務端直接建立了這個鏈接,返回的數據包由於網絡緣由丟失,那麼客戶端就一直接收不到服務器返回的數據,鏈接超時這個鏈接就會關閉,而後再發起新的鏈接,若是沒有三次握手的話,這時服務端是不知道客戶端到底有沒有接收到返回的數據,浪費服務端的開銷。因此須要三次握手去即時的察覺到網絡緣由致使的數據包傳輸中斷的問題。

3.2 URI、URL、URN

在http協議中,基本上使用的都是URL。

  • URI(Uniform Resource Identifier)
    • 統一資源標誌符
    • 用來惟一標識互聯網上的信息資源
    • 包含URL和URN
  • URL(Uniform Resource Locator)
    • 統一資源定位器
  • URN
    • 永久統一資源定位符
    • 在資源移動以後還能被找到
    • 目前尚未很是成熟的使用方案

3.3 HTTP報文

從圖中能夠看到http首部下面有一段空行,空行下面表示 http body 部分,上面就是 http headers 部分。

3.3.1 請求報文

(一) GET

http請求頭中,首行第一部分包含的是 method 請求方法,每一個方法有各自的語義,分別是 GET(獲取數據)、POST(建立數據)、PUT(更新數據)、DELETE(刪除數據)。這幾種方法只是語義上的說明,並無強約束,好比使用 GET 方法去更新數據,只是這樣違反了http語義化的定義規則。

(二) /test/hi-there.txt

首行第二部分是請求的資源資源地址url,通常這裏是存放路由相關的內容

(三) HTTP/1.0

首行第三部分是http的版本,如今的web服務通常都是http1.1,http2也會有愈來愈多的實現,不一樣的版本也會有不一樣操做方式。

3.3.2 響應報文

(一) 200 ok

http狀態碼,200 表明成功

3.4 HTTP方法

  • 用來定義對於資源的操做
  • 經常使用有GET、POST等
  • 從定義上有各自的語義
GET - 請求指定的頁面信息,並返回實體主體。
HEAD - 相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
POST - 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。
PUT - 從客戶端向服務器傳送的數據取代指定的文檔的內容。
DELETE - 請求服務器刪除指定的頁面。
複製代碼

3.5 HTTP CODE

  • 定義服務器對請求的處理結果
  • 各個區間的CODE有各自的語義
  • 好的HTTP服務能夠經過CODE判斷結果

HTTP Status:

1xx(臨時響應)
表示臨時響應並須要請求者繼續執行操做的狀態代碼。

代碼   說明
100   (繼續) 請求者應當繼續提出請求。 服務器返回此代碼表示已收到請求的第一部分,正在等待其他部分。
101   (切換協議) 請求者已要求服務器切換協議,服務器已確認並準備切換。

2xx (成功)
表示成功處理了請求的狀態代碼。

代碼   說明
200   (成功)  服務器已成功處理了請求。 一般,這表示服務器提供了請求的網頁。
201   (已建立)  請求成功而且服務器建立了新的資源。
202   (已接受)  服務器已接受請求,但還沒有處理。
203   (非受權信息)  服務器已成功處理了請求,但返回的信息可能來自另外一來源。
204   (無內容)  服務器成功處理了請求,但沒有返回任何內容。
205   (重置內容) 服務器成功處理了請求,但沒有返回任何內容。
206   (部份內容)  服務器成功處理了部分 GET 請求。

3xx (重定向)
表示要完成請求,須要進一步操做。 一般,這些狀態代碼用來重定向。

代碼   說明
300   (多種選擇)  針對請求,服務器可執行多種操做。 服務器可根據請求者 (user agent) 選擇一項操做,或提供操做列表供請求者選擇。
301   (永久移動)  請求的網頁已永久移動到新位置。 服務器返回此響應(對 GET 或 HEAD 請求的響應)時,會自動將請求者轉到新位置。
302   (臨時移動)  服務器目前從不一樣位置的網頁響應請求,但請求者應繼續使用原有位置來進行之後的請求。
303   (查看其餘位置) 請求者應當對不一樣的位置使用單獨的 GET 請求來檢索響應時,服務器返回此代碼。
304   (未修改) 自從上次請求後,請求的網頁未修改過。 服務器返回此響應時,不會返回網頁內容。
305   (使用代理) 請求者只能使用代理訪問請求的網頁。 若是服務器返回此響應,還表示請求者應使用代理。
307   (臨時重定向)  服務器目前從不一樣位置的網頁響應請求,但請求者應繼續使用原有位置來進行之後的請求。

4xx(請求錯誤)
這些狀態代碼表示請求可能出錯,妨礙了服務器的處理。

代碼   說明
400   (錯誤請求) 服務器不理解請求的語法。
401   (未受權) 請求要求身份驗證。 對於須要登陸的網頁,服務器可能返回此響應。
403   (禁止) 服務器拒絕請求。
404   (未找到) 服務器找不到請求的網頁。
405   (方法禁用) 禁用請求中指定的方法。
406   (不接受) 沒法使用請求的內容特性響應請求的網頁。
407   (須要代理受權) 此狀態代碼與 401(未受權)相似,但指定請求者應當受權使用代理。
408   (請求超時)  服務器等候請求時發生超時。
409   (衝突)  服務器在完成請求時發生衝突。 服務器必須在響應中包含有關衝突的信息。
410   (已刪除)  若是請求的資源已永久刪除,服務器就會返回此響應。
411   (須要有效長度) 服務器不接受不含有效內容長度標頭字段的請求。
412   (未知足前提條件) 服務器未知足請求者在請求中設置的其中一個前提條件。
413   (請求實體過大) 服務器沒法處理請求,由於請求實體過大,超出服務器的處理能力。
414   (請求的 URI 過長) 請求的 URI(一般爲網址)過長,服務器沒法處理。
415   (不支持的媒體類型) 請求的格式不受請求頁面的支持。
416   (請求範圍不符合要求) 若是頁面沒法提供請求的範圍,則服務器會返回此狀態代碼。
417   (未知足指望值) 服務器未知足」指望」請求標頭字段的要求。

5xx(服務器錯誤)
這些狀態代碼表示服務器在嘗試處理請求時發生內部錯誤。 這些錯誤多是服務器自己的錯誤,而不是請求出錯。

代碼   說明
500   (服務器內部錯誤)  服務器遇到錯誤,沒法完成請求。
501   (還沒有實施) 服務器不具有完成請求的功能。 例如,服務器沒法識別請求方法時可能會返回此代碼。
502   (錯誤網關) 服務器做爲網關或代理,從上游服務器收到無效響應。
503   (服務不可用) 服務器目前沒法使用(因爲超載或停機維護)。 一般,這只是暫時狀態。
504   (網關超時)  服務器做爲網關或代理,可是沒有及時從上游服務器收到請求。
505   (HTTP 版本不受支持) 服務器不支持請求中所用的 HTTP 協議版本。
複製代碼

4. HTTP客戶端

瀏覽器就是咱們最經常使用的http客戶端

同時 curl 能夠查看http請求返回的內容

curl還能夠查看請求的詳細內容, curl -v [host]

4.1 CORS跨域請求的限制與解決

作過前端開發的同窗對跨域並不會陌生,一般咱們可使用jsonp去實現跨域請求。

jsonp跨域:

<script src="http://www.example.com:8080"></script>
複製代碼

原理:

瀏覽器容許 link、img、script 標籤上寫入路徑加載一些內容的時候,是容許跨域的。

服務端設置跨域:

這裏以express爲例,咱們只需在響應頭中添加 Access-Control-Allow-Origin 便可

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*'
})
複製代碼

瀏覽器在發送請求的時候並不知道服務是否跨域,仍是會發送請求而且接收返回內容,只是在瀏覽器接收內容的時候沒有找到 Access-Control-Allow-Origin 頭設置爲容許的話,它會把請求返回的內容忽略掉而且會在服務端報錯。這個是瀏覽器所提供的功能。

實際上 Access-Control-Allow-Origin 值爲 * 是不安全的,這樣會致使第三方服務也能夠經過跨域訪問你的服務,能夠設置爲特定的域名

response.writeHead(200, {
  'Access-Control-Allow-Origin': 'http://xxxx.com'
})
複製代碼

4.1.1 CORS預請求

瀏覽器是根據 header 判斷某個請求的返回是否容許,若是想要容許自定義的頭進行發送的話,須要返回新的頭告知瀏覽器容許

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'X-Test-Cors'
})
複製代碼

同時會發現 network 中多出一個請求,這就是預請求,它的 Request MethodOPTIONS,服務端能夠根據不一樣method進行不一樣的操做,瀏覽器根據這個 OPTIONS 請求,來得到服務端容許的權限,接下來發送的它所承認的請求就會被容許,這就是瀏覽器對於跨域請求的預請求操做。

(一) 在跨域請求中,默認容許的方法只有 GET、HEAD、POST,其它的方法默認不容許,瀏覽器是有一個預請求的方式去驗證的。

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'PUT'
})
複製代碼

還能夠設置某個請求下容許跨域的最大時間,這樣就不須要再次發送預請求去驗證,可直接發送正式請求

response.writeHead(200, {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'PUT',
  'Access-Control-Max-Age': '1000'
})
複製代碼

(二) 默認容許的Content-Type:text/plain、multipart/form-data、application/x-www-form-urlencoded。這三個就是在html裏使用form表單能夠設置的三種數據類型。其它的也須要預請求驗證過以後才能進行發送。

(三) 其它的請求頭限制具體能夠查看文檔 https://fetch.spec.whatwg.org/#cors-safelisted-request-header

(四) XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器

(五) 請求中沒有使用 ReadableStream 對象

4.2 緩存Cache-Control

  • 可緩存性
    • public(任何地方都會緩存)
    • private(發起請求的瀏覽器)
    • no-cache 能夠在本地進行緩存,可是每次發起請求都要在服務端驗證,若是服務端容許使用本地緩存,才能真正使用本地緩存。
  • 到期
    • max-age = <seconds>
    • s-maxage = <seconds> 在代理服務器中才會生效,代理服務器中若是兩個都設置了,會優先選擇 s-maxage 配置項
    • max-stale = <secon ds> 發起端設置。即使緩存失效,只要這個時間內還可使用過時的緩存,而不須要去原服務器請求新的內容。
  • 從新驗證(不經常使用)
    • must-revalidate 在設置了max-age緩存中若是過時,必須去原服務端發送請求而後從新獲取數據再來驗證內容是否真的過時,而不能直接使用本地緩存。
    • proxy-revalidate 和 must-revalidate 相似,可是用在緩存服務器當中。
  • 其它
    • no-store 與 no-cache 對應,表示任何狀況下都不會存儲緩存,永遠都要去服務端請求新的內容才能使用它。即使服務端容許使用緩存,但本地沒有進行緩存
    • no-transform 用在 proxy 服務器,有些proxy服務器返回資源過大,會幫助進行壓縮及格式轉換,該屬性會不容許。

沒有緩存狀況下請求資源

設置客戶端緩存後

response.writeHead(200, {
  'Content-Type': 'text/javascript',
  'Cache-Control': 'max-age=200'
})
response.end('console.log("script loaded")')
複製代碼

能夠看到 Size 變爲了 == (from memory cache) ==,表示從瀏覽器中讀取緩存。

在作前端開發的時候,有些靜態資源文件咱們但願瀏覽器緩存下來,可是在服務端內容更新以後,客戶端請求的是緩存下的舊資源文件,這樣就無法更新應用。

目前最多見的方式就是前端編譯的時候加靜態資源文件md5戳。

4.3 資源驗證

首先看一張圖

瀏覽器建立請求,請求到達本地緩存(若是有cache-control),若是有本地緩存,就直接返回給瀏覽器顯示出來,不通過任何網絡傳輸。若是沒有本地緩存,請求進入網絡傳輸,若是有代理服務器就會進入並查找緩存設置,查看資源是否被緩存,有緩存就返回緩存資源通過本地緩存再到瀏覽器顯示。若是代理服務器未緩存,就會進入原服務器獲取新的內容再返回。

4.3.1 驗證頭

  • Last-Modified
    • 上次修改時間
    • 配合If-Modified-Since或者If-Unmodified-Since使用

瀏覽器在請求資源的headers裏有 Last-Modified 這個頭,並指定了時間,這個時間內下次瀏覽器發起請求時就會帶上 Last-Modified傳入的值,經過 If-Modified-SinceIf-Unmodified-Since(一般爲If-Modified-Since)帶到服務器上,服務器經過讀取 headers裏If-Modified-Since 帶入的值找到資源存在的地方對比上次修改的時間,若是時間同樣,就表示資源沒有被從新修改過,服務器就通知瀏覽器直接使用緩存的資源,這就是資源驗證的過程。

  • Etag(更加嚴格的驗證方式)
    • 數據簽名 - 對內容產生惟一的簽名
    • 配合If-Match或者If-Non-Match使用

有任何的修改兩個簽名就會不同,最典型的作法就是對資源內容作哈希計算,計算以後會獲得一個惟一值,用這個簽名來標記這個資源,下一次瀏覽器發起請求時會帶上 If-Match或者If-Non-Match頭,這個頭的值就是服務端返回Etag的值,而後對比服務器拿到瀏覽器傳入的簽名和服務器存在的簽名,若是相同,就不須要返回新的內容。

4.4 Cookie和Session

  • Cookie

    • 經過Set-Cookie設置
    • 下次請求會自動帶上
    • 鍵值對,能夠設置多個
  • Cookie屬性

    • max-age(有效時間)和expires(到某個時間點過時)設置過時時間
    • Secure只在https的時候發送
    • HttpOnly沒法經過document.cookie訪問
// Cookie設置,以express爲例
response.writeHead(200, {
  'Content-Type': 'text/html',
  'Set-Cookie': 'name=hello'
})

// 設置多個
'Set-Cookie': ['name=hello', 'age=12']

// 過時時間
'Set-Cookie': ['name=hello; max-age=2', 'age=12']

// 禁止js訪問cookie
'Set-Cookie': ['name=hello', 'age=12; HttpOnly']

// 子域名共享主域名cookie,前提是cookie要在主域名下設置
'Set-Cookie': ['name=hello', 'age=12; domain=example.com']
複製代碼

Session

session機制是一種服務器端的機制,服務器使用一種相似於散列表的結構(也可能就是使用散列表)來保存信息。

當程序須要爲某個客戶端的請求建立一個session的時候,服務器首先檢查這個客戶端的請求裏是否已包含了一個session標識 - 稱爲session id,若是已包含一個session id則說明之前已經爲此客戶端建立過session,服務器就按照session id把這個session檢索出來使用(若是檢索不到,可能會新建一個),若是客戶端請求不包含session id,則爲此客戶端建立一個session而且生成一個與此session相關聯的session id,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。

保存這個session id的方式能夠採用cookie,這樣在交互過程當中瀏覽器能夠自動的按照規則把這個標識發揮給服務器。通常這個cookie的名字都是相似於SEEESIONID,而。好比weblogic對於web應用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

因爲cookie能夠被人爲的禁止,必須有其餘機制以便在cookie被禁止時仍然可以把session id傳遞迴服務器。常常被使用的一種技術叫作URL重寫,就是把session id直接附加在URL路徑的後面,附加方式也有兩種,一種是做爲URL路徑的附加信息,表現形式爲http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

另外一種是做爲查詢字符串附加在URL後面,表現形式爲http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

這兩種方式對於用戶來講是沒有區別的,只是服務器在解析的時候處理的方式不一樣,採用第一種方式也有利於把session id的信息和正常程序參數區分開來。爲了在整個交互過程當中始終保持狀態,就必須在每一個客戶端可能請求的路徑後面都包含這個session id。

另外一種技術叫作表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時可以把session id傳遞迴服務器。好比下面的表單:

<form name="testform" action="/xxx">
  <input type="text">
</form>
複製代碼

在被傳遞給客戶端以前將被改寫成:

<form name="testform" action="/xxx">
  <input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
  <input type="text">
</form>
複製代碼

這種技術如今已較少應用,實際上這種技術能夠簡單的用對action應用URL重寫來代替。

4.5 HTTP長鏈接

http的請求是在tcp的鏈接上進行發送的,tcp鏈接又分爲長鏈接和短鏈接。

長鏈接就是在tcp鏈接上把http請求發送並接收返回,這個時候一次http請求已經結束,瀏覽器和服務器會協商是否斷掉這個鏈接,長鏈接就是在不斷掉鏈接下能夠持續發送http請求,適合高併發。

在Connection Id這一欄能夠看到不少 10247 的相同id,表示這些請求都是在同一鏈接下發送的。但仍是會有不一樣的鏈接,由於http1.1的鏈接在tcp鏈接上發送請求是有前後順序的,不會併發請求。

咱們但願在加載網站首頁的時候能夠併發處理這些請求,瀏覽器能夠容許產生一個併發的建立tcp鏈接,chrome容許最大併發數爲6。

能夠看到,併發狀況下會建立不一樣的tcp鏈接,chrome若是超出了6個併發,後面的請求會等待前面的完成,而且會盡可能複用前面的鏈接地址而保持長鏈接,這是瀏覽器默認的行爲。

能夠手動關閉長鏈接

response.writeHead(200, {
  'Content-Type': 'image/jpg',
  'Connection': 'close'
})
複製代碼

關閉長鏈接以後能夠發現每次鏈接id都會不同,後面的也會等待前面的完成,沒有重複利用tcp鏈接,每次請求發送完成tcp鏈接就會關閉。

通常狀況下keep-alive都是開啓的,而且會設置一個自動關閉時間。

信道複用

在tcp鏈接上併發的發送http請求,也就是說在鏈接一個網站時只需一個tcp鏈接,只在同域下請求有效,http2實現了這個功能。

4.6 數據協商

在客戶端發送給服務端請求的時候,客戶端會聲明這個請求拿到的數據格式以及數據相關的一些限制是怎樣的,服務端會根據客戶端的這個聲明作出判斷,從而返回不一樣的數據類型格式。

分類

  • 請求
    • Accept
    • Accept-Encoding
    • Accept-Language
    • User-Agent
  • 返回
    • Content-Type
    • Content-Encoding
    • Content-Language

Accept裏MIME_types相關對照表看這裏文檔

4.7 Redirect

在開發中,咱們存放資源的位置若是發生了改變,頁面在請求時就會報404錯誤,爲了不這種錯誤,須要幫助瀏覽器指向到正確的地址。

http.createServer(function(req, res) {
  if(req.url === '/'){
    res.writeHead(302, {
      'Location': '/new'
    })
    res.end('')
  }

  if(req.url === '/new'){
    res.writeHead(200, {
      'Content-Type': 'text/html'
    })
    res.end('<div>web</div>')
  }
})
複製代碼

圖中能夠看到第一次請求頁面狀態碼爲302,並跳轉到了new這個url下。

可是 302 是臨時跳轉,每一次訪問都要通過服務端的跳轉,圖中也能夠看到有兩個url的請求。若是肯定每次訪問 / 下都會跳轉到 new 下,能夠指定狀態碼爲 301 永久跳轉,這樣訪問頁面就只出現 new。

須要注意的是,301 會盡量長時間的把跳轉頁面緩存下來,這時候服務端即便修改了url,瀏覽器仍是會從緩存裏讀取,這個是有用戶使用瀏覽器狀況所決定的,除非用戶主動去清理瀏覽器緩存。因此 301 要慎重處理。

4.8 CSP

Content-Security-Policy內容安全策略,這使得瀏覽器變得更加安全。

做用

  • 限制資源獲取
  • 報告資源獲取越權

限制方式

  • default-src限制全局
  • 制定資源類型
    • connect-src
    • img-src
    • manifest-src
    • font-src
    • media-src
    • style-src
    • frame-src
    • script-src
res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy': 'default-src http: https:'
})
複製代碼

加入限制以後瀏覽器就會阻止js腳本的加載並報錯

只容許本站下的資源

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy': 'default-src \'self\''
})
複製代碼

容許某些站點的內容

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy': 'default-src \'self\' http://www.example.com/'
})
複製代碼

限制form表單的跳轉

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy': 'default-src \'self\'; form-action \'self\''
})
複製代碼

詳細內容能夠查看csp文檔 MDN CSP

內容安全策略若是出現了不但願出現的狀況下,能夠申請主動向服務端發起請求來彙報

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy': 'default-src \'self\' report-uri /report'
})
複製代碼

若是咱們只想對限制進行錯誤報告而不阻止資源加載的話,能夠這麼寫

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Content-Security-Policy-Report-Only': 'default-src \'self\''
})
```
scp不只能夠寫在 headers 裏,還能夠在html的meta標籤裏寫
複製代碼
``` 在meta下是不容許寫 report-uri 的,這個指令只能寫在 headers 裏。

5. Nginx反向代理服務器

5.1 基礎代理配置

nginx是如今互聯網界用的最多的web服務器,它是一個很是純粹的作http協議實現的服務器,並無一個工具來實現業務邏輯的開發。主要是用來作http的代理服務器。

nginx的安裝和用法能夠查網上相關教程。這裏介紹的主要是nginx的代理和緩存的功能。

一個最簡單的代理

// nginx.conf

server {
  listen       80;
  server_name  example.com;

  location / {
    proxy_pass http://127.0.0.1:8080;
    # proxy_set_header Host $host;
  }
}
複製代碼

能夠看到,瀏覽器下host 是 test.com,但在服務器下就變成了 127.0.0.1:8888。這是由於設置了代理,瀏覽器請求是發送到nginx的,nginx再進行轉發,發送到實際的node服務,這時候做爲發起方,它認爲的 host 就是這裏設置的 proxy_pass

想要拿到瀏覽器的 host 。能夠設置 proxy_set_header 屬性 $host

中間代理能夠修改任何想要修改的數據,但只是在http中,https的傳輸過程是加密的,中間代理沒法解析。在手機上所看到的一些移動聯通的廣告就是通過代理層插入了一些代碼所展現的。

5.2 nginx緩存

// nginx.conf

proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;

server {
  listen       80;
  server_name  example.com;

  location / {
    proxy_cache my_cache;
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
  }
}
複製代碼

**proxy_cache_path:**第一個選項表示緩存路徑,levels 是否建立二級文件夾,keys_zone url對應的緩存位置及內存大小

response.writeHead(200, {
  'Cache-Control': 'max-age=10, s-maxage=10, private'
})
複製代碼

s-maxage 是專門爲代理緩存設置過時時間的,而private就表示只容許瀏覽器緩存。

5.3 HTTPS

https在傳輸過程當中,客戶端會生成一個隨機數傳輸到服務端,中間會帶上一個支持的加密套件,服務端拿到以後保存而且也生成一段隨機數,而後把這段隨機數和服務端生成的證書一同發到客戶端,同時客戶端也會把服務端的隨機數保存,而且經過服務端證書生成預主祕鑰,生成過程也會生成一個隨機數,這個隨機數經過公鑰加密後傳輸給服務端,服務端經過私鑰解密拿到預主祕鑰。而後客戶端和服務端同時對這三個隨機數進行算法解密生成主密鑰(這裏會涉及到加密套件,服務端選擇的加密套件必須是客戶端所支持的),後續的數據傳輸都是通過主密鑰加密進行傳輸的。這對主密鑰只有客戶端和服務端共有,中間代理沒法破解,這就是https的加密原理。

這裏是經過抓取工具抓的https加密的站點,能夠看到,數據都被加密,沒法破解。

5.3.1 nginx部署https服務

要部署https服務,首先要生成一對公鑰和私鑰,這裏有一個命令能夠幫助生成

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout localhost-privkey.pem -out localhost-cert.pem
複製代碼

敲入回車以後能夠看到這樣的提示,這裏咱們測試,所有按回車跳過就好。

最終會生成兩個文件,而後在nginx配置這個證書

// nginx.conf

proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;

server {
  listen       80 default_server;
  listen       [::]:80 default_server;
  server_name  test.com;
  return 302 https://$server_name$request_uri;
}

server {
  listen       443;
  server_name  test.com;

  ssl on;
  ssl_certificate_key /www/data/cert/localhost-privkey.pem;
  ssl_certificate /www/data/cert/localhost-cert.pem;

  location / {
    proxy_cache my_cache;
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
  }
}
複製代碼

配置好以後重啓nginx服務,而後輸入https的域名

提示非安全鏈接是由於chrome瀏覽器認爲的安全證書是要經過有權威的機構去簽發的,這種機構會先認證域名全部者與服務是否屬於你,驗證經過纔會簽發證書。

5.3.2 http2

  • 優點
    • 信道複用
    • 分幀傳輸
    • Server Push(推送)

http2的使用

res.writeHead(200, {
  'Content-Type': 'text/html',
  'Link': '</test.jpg>; as=image; rel=preload'
})
複製代碼

server 頭信息了的 Link能夠指定這個頭想要推送的內容,</xxx> 爲文件絕對路徑,as 指定文件類型,preload 表示須要進行服務端推送。

nginx裏也要作這些配置。在使用nginx作反向代理時,咱們但願nginx幫助處理這些東西,而 http2 也是在nginx裏提供的,node server 仍是爲http的服務,nginx 會把http2的請求轉化爲http的請求發送到node服務上。

爲什麼不在node上作http2的服務呢,由於在nginx開啓一個http2的服務是很是容易的,在 node 上作http2的服務可能還會涉及到大量的邏輯修改,成本開銷比較大。

須要注意的是,目前只有在https下才能開啓http2

在nginx下開啓http2很簡單。

// nginx.conf

proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;

server {
  listen       80 default_server;
  listen       [::]:80 default_server;
  server_name  test.com;
  return 302 https://$server_name$request_uri;
}

server {
  listen       443 http2;
  server_name  test.com;
  http2_push_preload on;

  ssl on;
  ssl_certificate_key /www/data/cert/localhost-privkey.pem;
  ssl_certificate /www/data/cert/localhost-cert.pem;

  location / {
    proxy_cache my_cache;
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
  }
}
複製代碼

http2_push_preload 開啓以後,在接收 node 返回信息裏若是有 Link:rel=preload,就會去尋找該路徑資源,而後主動推送。

能夠看到 Protocal 值爲 h2,這個就是http2的縮寫。

這裏有一個能夠測試http2性能的網站 網站入口

能夠看到,使用HTTP2的性能提高很是顯著。

有些瀏覽器不支持http2,nginx會幫助瀏覽器作兼容處理,這個兼容方案爲 ALPN ,客戶端跟服務端會進行協商用哪一個協議,若是客戶端只支持http1.1,服務端就會以http1.1的傳輸方式進行。

以上就是對http知識的總結,不少地方沒有寫全,有很差的地方請你們多多指正

相關文章
相關標籤/搜索