人生苦短,瞭解一下前端必須明白的http知識點

混元霹靂手發表於2018-06-29

半年了,沒有在個人的文章發表任何話題,可能是被噴的多了,也可能是累了,以前的分享都是以邊學邊寫的模式做出的文章,當時也是因為VUE的熱點寫了一堆看似現在純小白學的東西。但是言歸正傳,繼續分享我自己所學到的http對於前端需要了解的知識點。

對於http的報文格式就不多細說了,因為做為前端開發,我們需要知道前後端聯調時的請求和響應之間請求頭和返回頭之間的關係和每個欄位中的涵意,靜態檔案資源在載入時我們所觀察到可效能優化的點,和一些日常請求報錯如何去解決的坑,更重要的是面試的時候如何去從容的應對面試官

以下的講解純屬於個人理解,肯定會有錯誤和理解不到位的點,請在下方用你們猿族的語言噴起來

簡單跨域的解決方式

跨域是一個老生常談的話題,面試官問我如何解決跨域,以前只會和麵試官說用webpackproxy做代理,叫後端大哥給我本地啟一個nginx就可以了,那往往在一些特殊的情況下,後端大哥來大姨媽了,進入一個新公司的讓你維護一個很老的專案,並沒有用到工程化這些東西,而且後端又來了一位新的高不高,低不低的後端工程師,此時對跨域根本性的知識點了解才能解決根本性的問題。

猿族前端 VS 猿族後端java

後端說: 前端同志,我們先調一個get請求的一個介面,地址我給你,http:www.pilishou.com/getname/list

前端操作中。。。

fetch('http://http:www.pilishou.com/getname/list', {
    method: 'GET'
})
複製程式碼

寫了一個這樣的請求,聽從後端大哥向服務端傳送,此時瀏覽器報了一個這樣的錯誤Failed to load http://http:www.pilishou.com: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

小白前端會說: 大哥,你這是什麼介面,請求了還尼媽報錯,你那裡什麼鬼!

大牛前端會說: 大哥,幫個忙,你那裡忘記設跨域頭了。

小白後端會說:大哥,丟你老母啊,你會不會調介面,報錯還找我,我這裡postman上面調的一點問題都沒有

大牛後端會說: 大哥,等一下,我的跨域頭忘記設了,稍等

原理講解:

在本地向不同域請求的時候,瀏覽器會做一個Origin請求頭的驗證,如果沒有設定,在不同域名下或者本地請求時瀏覽器會向服務端傳送請求,服務端也會客戶端傳送對應的值,但是瀏覽器考慮到安全策略,會進行一個關於頭資訊的報錯,此時對於後端來說,需要在response的返回頭中加入'Access-Control-Allow-Origin': '*',來告訴瀏覽器我允許你進行一個跨域請求,不用報錯,把值返回給請求者,這樣你就可以安然的拿到資料。同時這樣也會導致任何一個域名傳送過來的請求,都允許跨域的情況下,可以針對'Access-Control-Allow-Origin': '此處設定指定的域名'

複雜跨域的解決方式

此時前端唱起來一首抖音網紅歌,我知道我對你不僅僅是喜歡!。。。。。

後端說:小夥,這裡有一個介面,需要遵循resutful介面,用PUT方法,·http:www.pilishou.com/getname/update

前端操作中。。。。

fetch('http://http:www.pilishou.com/getname/list', {
    method: 'PUT'
})
複製程式碼

繼續按部就班的寫了一個這樣的請求,然後又發現瀏覽器報了這樣一個錯誤Failed to load http://http:www.pilishou.com: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response

小白前端會說: 大哥,你介面又怎麼了,GET,POST都行,PUT怎麼不行,肯定是你的問題,我別的什麼都沒動啊。

大牛前端會說: 大哥,幫個忙,你把請求頭中加一些允許跨域的方法。

小白後端會說:大哥,丟你老母啊,你不會調介面,報錯還找我,我這次postman上面調的還是一點問題都沒有

大牛後端會說: 大哥,等一下,我加一些允許跨域的方法,稍等

原理講解:

在簡單的跨域請求中
1.請求方法是以下三種方法之一:
HEAD
GET
POST
2.HTTP的頭資訊不超出以下幾種欄位:

Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
複製程式碼

如果不超過以上的限制,後端則只需要提供一個允許跨域的Origin就可以了,如果在請求方法超過了以上三種,需要新增'Access-Control-Allow-Methods': 'PUT',同樣瀏覽器為了安全,不允其它請求方法在臺端沒有設定允許的方法中進行一個跨域請求

同理複雜請求還包函著別的需要後端設定允許一些跨域請求的方式,比如通常會出現的:

  • 新增自定義頭
fetch('http://127.0.0.1:8887', {
    method: 'PUT',
    headers: {
      'x-header-f': '1234',
    }
  })
複製程式碼

報錯資訊 Failed to load http://http:www.pilishou.com: Request header field x-header-f is not allowed by Access-Control-Allow-Headers in preflight response.

解決方案需要服務端加上允許那些自定義頭進行一個跨域仿問 'Access-Control-Allow-Headers': 'x-header-f',

  • 新增不包括上面三者的請求型別
fetch('http://127.0.0.1:8887', {
    method: 'PUT',
    headers: {
      'x-header-f': '1234',
      'content-type': 'json'
    }
  })
複製程式碼

報錯資訊 Failed to load http://http:www.pilishou.com: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

解決方案需要服務端加上允許那些自定義頭進行一個跨域仿問 'Access-Control-Allow-Headers': 'content-type'這個請求頭資訊

複雜的跨域請求中,包括著預請求方案

在非同源的請求情況下,瀏覽器會首先進行Option請求,所謂的預請求,就是試探性請求,向服務端請求的時候,發現此介面設定了允許對應的請求方法或者請求頭,會再次傳送真正的請求,分別一共會向後臺傳送兩次請求,拿自己想要的資料,在OPTION請求時,服務端也會返回資料,但是在瀏覽器層被做了屏閉,如果沒有檢測出對應的跨域設定則會報出對應的錯誤。

減少預請求的認證次數

在本地聯調時,每次傳送一個非簡單請求時都會傳送一個預請求,預請求也是一個花費時間和資源的操作,就像一次實名質認證過了,在一定時間內就不用實名質認正了,原理一樣,如果當前請求的域名第一次認證通過,則在一定的時間內不需要進行一個二次認證,但是需要進行一次認證的時間控制,通過'Access-Control-Max-Age': '860000',返回,一旦在這個時間之內再次傳送時,直接傳送真正的請求,不通要再通過預請求Option方法進行一個探測認證。

cache-control 的使用場景和效能優化

cache-control這個東西就是對服務端拉取的靜態資源打上一個快取標誌

對於cache-control可以設定幾種模式,通常前端工程師又需要知道那幾種模式

  1. max-age = 10000 (以秒為音位,根據需求設定)
  2. no-cache (每次進行請求時都要向服務端進行驗證,需要配合etag,Last-Modified)使用
  3. no-store (每次請求都需要向服務端拉取新的資源)
  4. privite (私有的,不經過代理快取)
  5. public (公有的,如果本地失效,代理快取存在的話可以從代理快取進行通知用過期的資源)

max-age

當載入完資源時,瀏覽器會自動給我們儲存到記憶體當中,但是瀏覽內部的失效時間是由內部機制控制的,在用nginx做靜態資源的時候,在重新整理的時候,瀏覽會向服務端再次傳送是否過期的認證,在資源快取時間的確定情況下,通過max-age指定強快取後,瀏覽器再次載入同樣的資原始檔時,只需要從memory或者disk上面進行拉取複用

達到以上的功能需要在返回資源的服務端的對返回的資源設定'cache-control': 'max-age=時間(以秒為單位)',當再次重新整理頁面的時候,在設定的時間之內,重新整理頁面,不清除快取的情況下都會重新拉取記憶體了中的快取資源。

no-cache

no-cache 字面的字意是不快取的意思,但是很容易迷惑人,但是本質的函意,意味著每次傳送請求靜態資源時都需要向服務端進行一次過期認證,通常情況下,過期認真證需要配合(etag和Last-Modified)進行一個比較,這個話題後繼再展開討論,如果驗證並沒有過期,則會傳送304的狀態碼,通知瀏覽進複用瀏覽器的快取

no-store

no-store 代表每次資源請求都拉取資源伺服器的最新資源,就算同時設定max-age , no-store, no-store的優先順序則最高,此時max-age則不生效,同樣的會從服務端拉取最新的資源

private vs public

在資源請求時,有些情況不會直接到原資源伺服器傳送請求,中間會經過一些代理伺服器,比如說cdn,nginx等一些代理伺服器,如果寫入public的情況下,所有的代理伺服器同樣也會進行快取,比如說s-maxage就是在代理快取中生效的,如果本地max-age過期了,則會通過代理快取,代理快取並沒有過期,會告訴瀏覽器還是可以用本地過期的快取,但對於private中間代理伺服器則不會生效,直接從瀏覽器端向原伺服器進行一個驗證。

快取驗證 Last-Modified 和 Etag

Last-Modified

最後修改時間,一般在服務端,對檔案的修改都會有一個修改時間的記錄,在nginx做靜態資源時,nginx會返回一個Last-Modified最後修改的時間,在瀏覽器再次請求的時候,會把對應的If-Modified-Since和 If-UnModified-Since在請求頭中再次傳送給服務端,告訴服務端上次你給我檔案改動的時間,但是Last-Modified只能以秒為單位,在有些情況下,是不夠精確的

Etag

是一個更加比較嚴格的驗證,主要通過一些資料簽名,每個資料都有自己的唯一簽名,一旦資料修改,則會生成另一個唯一的簽名,最典型的做法就是對內容做一個hash計算,當瀏覽器端向服務端再請求的時會帶上 IF-Match 或者 If-Non-Match,當服務端接收到後之後會對比服務端的簽名和瀏覽器傳過來的簽名,這也是彌補了Last-Modified只能以秒為單位,在有些情況下,是不夠精確的情況

Last-Modified和Etag 配合 no-cache 使用

通常只會在 cache-control 在 no-cache的情況下,瀏覽器也會對資源進行一個快取, 同時會對服務端進行一個認證過期,一旦服務端返回304狀態碼,則說明可以複用瀏覽器的快取,則會向服務端重新請求資料。

cookie的策略機制

cookie則是一個服務端和用端之間一個像身份證認證一樣的東西,一旦後端在返回頭中設定了cookie,則在response中會出現設定的cookie資料,同時也會存在瀏覽器的application/cookie中,當每次傳送請求的時候都會在request的頭中帶上當前域名下的cookie資訊

健值對方式設定

'Set-Cookie': 'id=1',

設定過期時間

通常情況,在不設定過期時間的時候,瀏覽器關閉的時候,則cookie,則會失效,我們可以通過max-age或者expire進行一個cookie失效時間的設定

不可獲取的cookie

如果在不設定httponly的情況下,可以通過document.cookie進行讀取,在不同情況下,考慮安全性,可以通過httponly設定,在document.cookie則獲取不到。

https下的secure cookie

如果設定了secure只有在https的服務下才會把欄位寫入application/cookie中,雖然在response有傳送cookie這個欄位,但是瀏覽器在識別不是https服務時,會進行一個乎略

二級域名下與二級域名的cookie傳輸

講一個例子:

公司的所有內部系統都全要走一個登陸系統。也可能說sso單點登陸,如果登陸是sso.pilishou.com的二級域名下,而你自己的開發的時候環境是localhost:9999埠,當登陸成功時,此時cookie是設在sso.pilishou.com域名下,在本地127 .0.0.1下傳送請求,根本拿不到sso.pilishou.com下的cookie資訊,cookie根本不會從request header中帶過去,可以通過host的對映,把127.0.0.1對映成web.pilishou.com

但是問題來了,ssoweb都是二級域名,在web下同樣拿不到sso下的cookie,此時解決辦法,在sso登成功後,需要後臺配合把cookie的資訊通過Dioman設定到pilishou.com的主域下

web二級域名下就可以拿到sso下請求成功後設定的cookie資訊,在不設定httponly情況下,嘗試用document.cookie可以拿到自己想要的cookie資訊,但是在傳送的時候,發現request頭中根本沒有把cookie資訊帶入請求,在fetch請求中我們要設定credentials: 'include',意思代表允許請求時帶上跨域cookie,此時就會發現cookie帶入了request頭部

經歷了這麼多的設定,在聯調的時候,後端同樣也需要配合你的行為,需要後臺工程師也需要配置在返回頭中加入'Access-Control-Allow-Credentials': 'true',允許進行cookie的跨域

但是問題又來了,真TMD的好多問題,少一步都不行,此時你的瀏覽器又會報錯,在設定跨域cookie的時候,不允許response header設定 Origin 設定為* ,只能設定指定的域名進行一個跨域仿問,此時還需要後端工程師配合把前面的* 改成你指定當前web.pilishou.com

如果講cookie你就用這麼多一套流講死麵試官。

http長連線與效能優化的各種架構方式

在以前沒有打包工具,或者沒有應用到打包工具的時候,一個大專案會有一堆js,一堆css,會引起各種問題,會導致引入資源會出現混亂,資源載入慢,有些時候頁面呈現了,點選的時候沒有任何響應,這個需要從http請求資源時,經過三次握手後建立的TCP連線說起。

因為每個瀏覽器的執行策略不一樣,所以我只針對Chorme來說,開啟開發者工具,點選network,通過右健點選Name,有一個connect id, Chrome可以一次性建立六個併發連線,但是六個併發連線會阻塞後面的資源的請求,如果前六個資原始檔很大,後面的資源請求會被一直阻塞著,會進行一個佇列的等待請求,當頁面在網路不穩的情況下,HTML,CSS,已經載入好了,也渲染完畢,JS終於等到請求,但是突然網速變差,使用者點選此時是沒有任何響應的,因為JS根本還沒有載入好

為了驗證,開啟網路資源多的網站,把網速調到2G模式,會發現,一開始只會出現6次connect連線,但是也不是一下子全出來,因為建立TCP連線需要經過三次握手,這中間也是需要時間,當6個連線建立完成時,又回到了序列的方式,除非只有connect連線請求完成後才會讓出連線資源,讓下一個佇列中的請求,進行復用,不用再建立新的TCP,但是在TCP最後的關閉,瀏覽器會與伺服器自行進行一個協商關閉,也可以設定關閉時間,在多長時間沒有請求後,才進行一個連線關閉,在觀查connect id會發現,只會出現6個connect id,其餘的全會被複用,如果有些資源是複用的其它網站的,會另開新的connect id

解決方案

所以現在的對於spa的頁面,都採取了,資源合併,把CSS,JS進行一個合併,通常在VUE中都打出4個檔案,vendor.js, app.js, manifest.js, app.css

能讓瀏覽器充分的剛好利用讓一個工程上的主檔案一次性全都通過TCP連線並行下載下來,無論從效能速上還是解決造成使用者無響應的解決方案,那我再簡單的講解一次為什麼要分成這四個檔案。

1.Vendor.js一般是node_modules檔案,不會輕意更改,所以可以通過瀏覽器快取,能長期進行一個快取 2.app.js 一般都是業務程式碼的檔案,業務程式碼的檔案,對於公司來說是業務程式碼很一個迭代很頻繁的事,所以當使用者拉取資源的時候,只需要拉需app的新資源,app.js中還可以分每個module進行一個資源更新, 3.manifest.js是一個runtime執行時的檔案,無論app.js或者vendor有改動,masfiste檔案則就會改動,所以也進行一個單獨更新 4。app.css。是一個綜合考慮,雖然如果如改動一部分小資源,但是也會重新拉取,但是節省了請求的次數,對於合併自己可能根據專案進行一個著情考慮。

每個檔案其實更新並不是通過什麼快取的設定,而是在每個js或者css後面會跟一個檔案的hash,這個hash是打包工具給我們做的,一旦檔案有改動,就會重新生成一個hash,瀏覽器在載入資源的時候,發現沒有找到對應的快取檔案,則會向服務端進行一個重新請求。

多次複用,和單次複用的決擇性

上面我們講了因為瀏覽器的請求速度影響和,TCP連線的限制,我們採取了以上的方案,但是每個方案是針對不同的場景和架構的,對於後臺管理專案,基本上公司都是統一工程化做的,所以的工程方案都是採用一套都或幾套,但是採用專案基礎檔案都是一樣的,要升級也會根據專案特定需要才進行升級的,對於公司的內部系統,採用最好的方式就是放棄初次載入的效能,利用快取進行多專案快取複用

通常一個vue的專案,vue.js vuex.js router.js 和一些公共的內部js檔案都是在專案架構中整合的

舉個例子

公司的內部專案一般都有三個環境,加上你本地除錯有四個,如果把這些檔案全打到vendor中,會產生只要項重發之後,或者切換環境,在這個四個資源環境中就不能行成一個複用,因為域名都是不一樣的,所以瀏覽器找快取不能共享,往往這些檔案在所有專案中,所有環境中都是不會反覆變的檔案,一次載入,任何環境,任何專案共享利用快取資源

1。我們可以利用cdn把以上前面提到的檔案進行利用 2。也可以把檔案放到一個域名下的公共目錄下,進行利用。

from memory cache 和 from disk cache

  1. from memory cache 從記憶體中拉取的快取
  2. from disk cache 從磁碟上拉取的快取

在資源拉取過後,這裡還是針對的Chrome進行解釋,瀏覽器在拉取資源後,會對資源進行磁碟和記憶體進行快取,而css檔案會快取到磁碟上,html.js,img等檔案都會在記憶體和磁碟進行快取,當重新整理頁面時,除了在特定的資源中返回頭中寫入cache-contorl: no-cache或者no-store的情況下都會直接從快取中拉取資源,會在size中顯示from memory cache,而css檔案則顯示from disk cache, 但是 no-cache驗證沒有過期,則還會返回304進行讀取快取,只是到原伺服器進行了一個驗證。

meta http-equiv="Cache-Control" content="no-cache" 設定的備要性

此時前端和後端同學前後端已經聯調好了,發到測試環境,讓測試同志進行測試。

測試說:你頁面中一個字寫錯了,改一下,重新發包我再來測,測試關閉瀏覽器,刷了一會抖音

前端一頓操作後。。。。這一頓操作猛如虎

前端說:好了,你測吧。我發上去了,此時也關閉了瀏覽器,心裡想測試要測試默默JJ的,我先刷一會抖音。

測試一頓操作後。。。開啟瀏覽器,把地址輸入了進去,回車後。。。

測試說: 你到底改沒改啊,怎麼沒有效果。

前端此時也開啟瀏覽器,輸入地址,一看,wc什麼情況。開始懷疑人生了。。。我明明改了。怎麼沒效果。然後又是一頓猛如虎的開啟檔案看了看,又重新發包

問題總結

根本原因,進行一個分析,正是因為快取問題而導致,瀏覽器對html頁會進行一個自動快取,但是正常重新整理情況下,如果用nginx做一個靜態資源的情況下,都會進行一個304的重新向服端進行一個資源是否改動的驗證,如果沒有改動則進行一個304的快取利用

當關閉瀏覽器程式的時候,快取在記憶體中的資源會隨著瀏覽器的閉畢一起清除,當再次開啟瀏覽器的時候會從磁碟上讀取快取,這時候如果沒有設定meta http-equiv="Cache-Control" content="no-cache",當開啟瀏覽器再次仿問的時候,html頁面初次會進行瀏覽器的磁碟上讀取就是from disk cache,那此時肯定用的還是原本舊的資源,這就是問題產生的根本,所以在加入每次都從原伺服器驗證資源,在開啟瀏覽器的時候就不會出來資源沒有及時更新的問題。

redirect 重定向的坑

重定向在response中會有一個location欄位進行重定義,比如說返回值/list,需要我們重定向到/list的頁面,但是在響應碼中,可以返回 302或者301

301適合永久重定向

301比較常用的場景是使用域名跳轉。比如,我們訪問 http://www.baidu.com 會跳轉到 https://www.baidu.com,傳送請求之後,就會返回301狀態碼,然後返回一個location,提示新的地址,瀏覽器就會拿著這個新的地址去訪問。

302用來做臨時跳轉

302和301的區別則是設定了302如果再次訪問則是從服務端再次拉取資源,然後進行重定向。301則是如果有快取檔案,則直接讀快取檔案上響應頭上的重定向位置,如果原服務端重定向的位置有變化,則只能通過使用者清除快取進行重新拉取新資源進行再次重定向,所以301的使用需要嚴謹。

csp的理解 (cotent Security Policy) 內容安全策略

為了讓我們網頁更加安全

1。限制資源獲取 2。資源獲取越權

可以通過設定 default-src 設定全域性需要資源的內容,也可以設定資源型別的範圍

1。connect-src 我們連線的資源 2。style-src 樣式請求的資源 3。script-src 指令碼的請求資源 。。。等等

可以通過響應頭的返回設定'Content-Security-Policy'進行設定

有些情況一些xss攻擊是通過inline scrpit進行注入一些程式碼進行攻擊,可以通過設定進行一個禁用。可以設定'Content-Security-Policy': 'default-src http: https:'對inline scrpit進行一個禁用。設定之後,後報Refused to execute inline script because it violates the following Content Security Policy directive: "default-src http: https:". Either the 'unsafe-inline' keyword, a hash ('sha256-9aPvm9lN9y9aIzoIEagmHYsp/hUxgDFXV185413g/Zc='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.錯誤。

不允許引入外部連線:

可以設定 ''Content-Security-Policy': 'default-src \self\'' 進行設定,如果引用了外部的資源則會報Refused to load the script 'http://static.ymm56.com/common-lib/jquery/3.1.1/jquery.min.js' because it violates the following Content Security Policy directive: "default-srcself". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.錯誤

如果需要指定外鏈的地址,則可以,在default-src加入指定的地址

其餘的則可以根據Content-Security-Policy' 內容安全策略文件進行設定。

相關文章