本文主要學習一下一些高級的HTTP知識,例如
Session
LocalStorage Cache-Control Expires ETag
javascript其實主要就是涉及到了持久化存儲與緩存的技術css
在此以前已經學習了Cookie
的相關知識,可是Cookie
有個缺點是能夠人爲修改,有必定的安全隱患。html
因此,針對這個缺點,誕生了Session
html5
通常來講Session
是基於Cookie實現的,它利用一個sessionId
把用戶的敏感數據隱藏起來,除非暴力窮舉纔有可能得到敏感數據。java
sessionId
咱們使用Cookie
的時候,通常是服務器給用戶一個響應頭,設置Cookie
node
response.setHeader('Set-Cookie', 'sign_in_email=...;HTTPOnly')
複製代碼
既然Session仍是基於Cookie
實現的,那麼仍是應該在Set-Cookie
上搞事情。web
//預先在服務器端預留對象準備存儲各類session
let sessions = {
}
...
let sessionId = Math.random() * 100000
sessions[sessionId] = {sign_in_email: email}
response.setHeader('Set-Cookie', `sessionId=${sessionId};HTTPOnly`)
複製代碼
使用隨機數來作sessionId
,最終只是把這串隨機數暴露給外界,而真正的信息卻保存在了服務器端的sessions
對象裏面。它就像一個密碼簿同樣,有效的信息與sessionId
一一對應,這是服務器的事,保證了安全性。算法
當下次用戶訪問該網站的其餘頁面的時候,就會帶着登陸時服務器給的這個sessionId
,服務器得到這個sessionId
後,而後一轉化就知道是正確的用戶了。npm
let sessions = {
sessionId: {
sign_in_email: ...
}
}
複製代碼
在HTML裏面js文件
裏面的變量或對象,每當網頁刷新的時候,就會死掉,又從新生成,雖然仍是那個a
,可是刷新後已是另外一塊內存了。既然它也沒變,咱們爲何不把它一直保留着呢,即便刷新了a
仍是那個a
,也就是持久化存儲的意義。之前使用Cookie
作這個功能,不過Cookie
每次發請求會把Cookie裏面的全部東西都帶着去服務器,加劇內存的負擔,並且請求響應時間長,因此html5
給了一個新的API localStorage
segmentfault
關於Cookie如何工做的,我發現這篇文章寫得特別好
LocalStorage
它本質上仍是個hash
,不過是存在於瀏覽器端的,不一樣於session
存在與服務器端的hash
。通常存儲的都是沒有用的或者不敏感的信息。
localStorage
是window的全局屬性,經常使用的有三個方法
//1. 添加鍵、值
localStorage.setItem('a', '...')
//2. 得到鍵、值
localStorage。getItem('a')
//3.清空localStorage
localStorage.clear()
複製代碼
注意,它存的值全是字符串,即便你寫的像對象也沒有卵用。
若是想存儲字符串須要用到JSON.stringify( )
很簡單的一個例子:網站進行更新了,用戶登陸進來了,想提示用戶一下---我有新東西啦,這個提示並不該該在每次刷新的時候反覆告訴用戶,只是在第一次用戶進來的時候告訴他便可。
let already = localStorage.getItem('已經提示過了')
if (!already) {
alert('咱們的網站新進了一些貨物,您看一下有沒有您須要的啊O(∩_∩)O~')
localStorage.setItem('已經提示過了', true)
} else {
}
複製代碼
當第一次訪問的時候,already
爲null,因此進入if
代碼片斷,提示用戶一次,接着把already
設爲true
,不會進入if
,也就再也不提示了。
Cookie
的session
學習了localStorage
,就能夠搞一些黑科技了,前面說了,session
通常是基於Cookie
的,那麼有沒有例外呢。
有的。利用查詢參數和localStorage
但是實現session
Id`。
Expires
會話存儲主要特色與localStorage
基本相同,最大的不一樣是SessionStorage
在用戶關閉頁面(會話結束)後就失效。
假如說咱們要訪問的的文件比較大,咱們請求完以後,下載須要花很長時間,當咱們刷新頁面的時候,雖然文件沒有任何更新,可是咱們又從服務器端下載了一遍大文件,致使每次響應時間依然很長。
經過上圖的實驗能夠看到localhost
的請求響應很快,10ms;而default.css
、main.js
文件較大,響應時間是localhost
的25倍,而jq
文件使用了cdn
加速,是從內存的緩存中得到的,幾乎瞬間。若是每次都這樣的話,用戶體驗確定不好。
那麼咱們能不能在第一次響應完畢以後,若是資源沒有更新,就不去服務器端下載,而是去某個地方得到呢?
答案是確定的,能夠實現,經過緩存,正如上圖的jq
實現的方法同樣。
這部分能夠做爲web性能優化的一個方法。
經過max-age
設置緩存的有效時間(持續時間)
if (path === '/css/default.css'){
let string = fs.readFileSync('./css/default.css', 'utf8')
response.setHeader('Content-Type', 'text/css;charset=utf-8')
response.setHeader('Cache-Control', 'max-age=1000000')
response.write(string)
response.end()
}
複製代碼
在響應頭裏面加上Cache-Control
,表示在100000秒內不要再去向服務器要這個資源了,就從個人內存緩存裏面得到。
雖然使用了緩存技術,不過有一點疑惑的就是有時候從硬盤的緩存裏面得到,這個速度提高並不大,可是仍然避免了向服務器再次發起請求得到資源的過程;有時候從內存的緩存裏面得到,這個就特別快了。大概是由於內存的緩存特別快吧。
一般咱們把Cache-Control
的有效時間設的很長。
以常常逛得知乎爲例。
若是一個文件長期不變,把它設爲從緩存裏面得到,知乎設置了32596169秒的有效時間,超過了1年=31536000秒的時間。
咱們刷一些論壇性質的或者新聞性質的網站,注重時效性,通常會把爆炸性的、高質量的內容放到首頁去,若是咱們看了一會,想刷新看看新的更新的內容,而你設了緩存,看到的仍是10分鐘以前的首頁,那就太尷尬了☺……
因此首頁儘可能不用緩存技術,只對那些長期不變的文件、圖片等使用緩存技術。
仍是以知乎爲例。
對於知乎的Cache-Control
的寫法我是比較懵逼的。
public
Indicates that the response may be cached by any cache.
private
Indicates that the response is intended for a single user and must not be stored by a shared cache. A private cache may store the response.
no-cache
Forces caches to submit the request to the origin server for validation before releasing a cached copy.
no-store
The cache should not store anything about the client request or server response.
must-revalidate
The cache must verify the status of the stale resources before using it and expired ones should not be used.
MDN推薦關閉緩存的寫法是Cache-Control: no-cache, no-store, must-revalidate
。
那麼若是有的資源確實被更新了,如何去更新緩存呢。
經過服務器端代碼server.js
咱們能夠發現
if (path === '/js/main.js') {
...
response.setHeader('Cache-Control', 'max-age=1000000')
...
} else if (path === '/css/default.css'){
...
response.setHeader('Cache-Control', 'max-age=1000000')
...
}
複製代碼
只要當URL
符合要求的時候,會使用緩存技術,不去發起請求從新下載資源。
因此當文件確實被更新了以後,咱們能夠改變URL
,那麼就會去從新下載新的文件了。
既然咱們的網頁入口是html
,能夠在這裏面動手腳
...
<script src="./js/main.js?V2"></script>
...
複製代碼
當你更新代碼以後,理論上只須要在URL上添加查詢參數?V2
便可。
咱們仍是去知乎看看他們的例子。
能夠看到知乎也是把URL
改了,只不過比我那種高級,它在文件名字動了手腳,大概是用了什麼框架或者處理工具吧,不過更新緩存的思路上是同樣的。文件變了,知乎就把文件緩存的URL
填點東西;沒變的話,就緩存一年,在你的硬盤某處睡一年^_^。
使用緩存就用response.setHeader('Cache-Control', 'max-age=100000')
,當你想更新的時候就改變文件的URL
。
固然,緩存存多了,你的硬盤估計就爆了,瀏覽器會去權衡這些的,應該優先清楚哪些緩存,是瀏覽器的事。
俗話說得好啊,吃井不忘挖井人啊,要學會憶苦思甜啊,咱們如今用的可爽的Cache-Control
也不是憑空冒出來的,是有歷史緣由的,之前呢,是用Expires
實現緩存的技術。
Expires
的英文是到期的意思,很明顯是與緩存有關的技術,不過從其英文意思也能看出它是到某個時間點截止的意思,不是Cache-Control
的有效時間。
從語法和示例能夠看出它是基於格林威治時間的。
咱們還要處理一下時間
var d = new Date() //Sat Feb 10 2018 11:18:54 GMT+0800 (CST)
d.toGMTString() //"Sat, 10 Feb 2018 03:18:54 GMT"
複製代碼
能看出來,這個響應頭的最大的弊端在於,時間戳是與你的本地時間關聯的
若是本地電腦的時間系統錯亂了,並且這種毛病還真的時常發生,那你的緩存就毫無做用了。maybe這就是HTTP要升級這個響應頭的緣由吧O(∩_∩)O~
當Cache-Control
和Expires
共同存在的時候
若是還有一個 設置了 "max-age" 或者 "s-max-age" 指令的
Cache-Control
響應頭,那麼Expires
頭就會被忽略。
關於緩存的技術,還有最後一個兄弟ETag
,在搞定它以前,先來學習一下它的小跟班MD5
MD5
是一個摘要算法。常常用於比較兩個文件是否徹底同樣,若是有一點不同,偏差會放大。例如咱們常常重裝系統的話,有良心的系統提供者會給你一個對應的MD5
值,當你下載完畢後,查看你下載的系統的MD5值是否與官方提供給你的同樣,確保是否會由於網絡緣由致使你下載的東西不完整。
在Linux
系統裏面使用md5sum
指令進行MD5校驗
第一個紅框裏面就是1.txt
文件(內容設定爲123456)的MD5值,第二個紅框裏面就是1-copy
文件(內容被我改成了123460)的MD5值。
在nodejs
裏面如何使用呢,Google後發現有npm
的MD5。
npm install md5
...
//在server.js引入
var md5 = require('md5');
複製代碼
準備工做作完,能夠搞ETag
了。
The ETag HTTP response header is an identifier for a specific version of a resource.It allows caches to be more efficient, and saves bandwidth, as a web server does not need to send a full response if the content has not changed. On the other side, if the content has changed, etags are useful to help prevent simultaneous updates of a resource from overwriting each other ("mid-air collisions").
If the resource at a given URL changes, a new
Etag
value must be generated. Etags are therefore similar to fingerprints and might also be used for tracking purposes by some servers. A comparison of them allows to quickly determine whether two representations of a resource are the same, but they might also be set to persist indefinitely by a tracking server.
能夠看出ETag
應該是一串值,此時上一節的MD5
就派上用場了,咱們使用MD5來比較先後兩次請求文件的內容。
當某個URL來訪問服務器的資源的時候,若是服務器設置了響應頭ETag:一串md5值
,那麼
如今沒有什麼其餘變化,若是第二次刷新的話,你會發現
請求頭多了一個If-None-Match:一串MD5值
。
比較上述兩圖,個人main.js
沒有改變過,發現ETag:一串md5值
和If-None-Match:一串MD5值
的同樣,稍微一思考的話,就能明白,第二次刷新的時候若是個人main.js
變了的話,那麼
第二次向服務器發起請求,下載的main.js
的ETag
的MD5值必然不一樣了。
根據這個現象,而後結合MDN文檔
ETag頭的另外一個典型用例是緩存未更改的資源。 若是用戶再次訪問給定的URL(設有ETag字段),顯示資源過時了且不可用,客戶端就發送值爲ETag的
If-None -Match
header字段:If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4" 複製代碼
服務器將客戶端的ETag(做爲If-None-Match字段的值一塊兒發送)與其當前版本的資源的ETag進行比較,若是兩個值匹配(即資源未更改),服務器將返回不帶任何內容的
304
未修改狀態,告訴客戶端緩存版本可用(新鮮)。
能夠推理出以下的代碼了:
if (path === '/js/main.js') {
let string = fs.readFileSync('./js/main.js', 'utf8')
response.setHeader('Content-Type', 'application/javascript;charset=utf-8')
let fileMd5 = md5(string)
response.setHeader('ETag', fileMd5)
if (request.headers['if-none-match'] === fileMd5) {
response.statusCode = 304
} else {
response.write(string)
}
response.end()
}
複製代碼
HTTP 304 說明無需再次傳輸請求的內容,也就是說可使用緩存的內容。這一般是在一些安全的方法(safe),例如
GET
或HEAD
或在請求中附帶了頭部信息:If-None-Match
或If-Modified-Since
。
304和緩存的區別:
cookie
是瀏覽器提供的功能。cookie
實際上是存儲在瀏覽器中的純文本,瀏覽器的安裝目錄下會專門有一個 cookie 文件夾來存放各個域下設置的cookie
。Set-Cookie
以後,用戶的每次訪問服務器,請求裏面都會帶着Cookie
到服務器上,與HTTP有關,而LocalStorage
不用發到服務器端,它是存儲在瀏覽器裏面的,與HTTP無關,是瀏覽器的屬性,window.localStorage
。Cookie
通常比較小,大約4k左右,而LocalStorage
大約能用5MCookie
默認會在用戶關閉頁面後失效,不事後端能夠設置保存時間,而LocalStorage
永久有效,除非用戶手動清理。LocalStorage
永久有效,除非用戶手動清理localStorage.clear()
。不會自動過時設置過時時間:Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
data`是格林威治時間,響應頭裏裏面應該這麼寫代碼
response.setHeader('Expires', 'Fri, 09 Feb 2018 11:29:48 GMT')
複製代碼
也就是說Cookie在格林威治時間的2018年2月9號的11點29分48秒失效。
設置cookie過時時間小於當前時間,那麼就會刪除該cookie。
function deleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
}
複製代碼
Cache-Control: max-age=1000
的緩存 是直接不發請求的,1000秒內相同URL的用戶請求資源的時候,不會再去發請求訪問服務器了,直接從本地內存的緩存裏面獲取ETag
的緩存是無論怎麼樣都要發起請求,第二次訪問的是時候會多一個請求頭If-None-Match : md5值
,若是兩次請求之間的MD5值相同就不會去下載新的文件,響應體是第一次下載的;若是MD5值變了,就要去下載新的文件。