Session、LocalStorage、Cache Control

項目地址css

使用session保存信息

Cookie 存在的問題

用戶能夠隨意篡改 Cookiehtml

Session 與 Cookie 的關係

通常來講,Session 基於 Cookie 來實現。前端

Cookie

1.服務器經過 Set-Cookie 頭給客戶端一串字符串vue

2.客戶端每次訪問相同域名的網頁時,必須帶上這段字符串html5

3.客戶端要在一段時間內保存這個Cookie(cookie是保存在客戶端的)node

4.Cookie 默認在用戶關閉頁面後就失效,後臺代碼能夠任意設置 Cookie 的過時時間 大小大概在 4kb 之內react

好比:git

i.當用戶登陸成功,用戶就會獲得一個Cookie好比是sign_email:1@Reagen.com程序員

ii.當用戶訪問首頁的時候,請求中就包含了cookie中的字符串github

iii. 服務器讀Cookie,發現用戶是以sign_email:1@Reagen.com這個信息登陸的,就從數據庫把用戶相關的信息渲染到html中而後再返還給用戶,若是服務器發現讀的cookie是空的,那麼服務器就會返回和以前登陸頁面不一樣的一個頁面

以前說過Cookie是能夠被用戶篡改的,也就是若是用戶篡改了本身的Cookie它就能夠以另外一種身份去登陸或者向服務器發請求了

好比:將頁面中的1030052539@qq.com改成1@Reagen.com後刷新頁面咱們就可獲得保存再數據庫中的用戶1@Reagen.com對應的密碼了,這就是cookie的弊端,用戶能夠修改cookie的id,而後把id對應的密碼明文的展現出來,很危險

怎麼辦?session能夠解決這個問題

session

看下以前的註冊登陸,server.js中登陸的路由中一旦用戶登陸成功就設置cookie,並將用戶的email重要敏感信息明文的展現在cookie中

+++
if(found){
        // Set-Cookie: <cookie-name>=<cookie-value> 
        response.setHeader('set-Cookie',`sign_in_email = ${email}`)
        response.statusCode = 200
      }
+++
複製代碼

改一:

咱們不如用一個隨機數記錄用戶的email,setCooke的時候就是展現這個隨機數,這樣隨機數就不容易被篡改了

+++
 if (found) {
        let sessions = {}    
        let sessionID = Math.random() * 1000000
        sessions[sessionID] = { sign_in_email: email }
        response.setHeader('set-Cookie', `sessionID = ${sessionID}`)
        response.statusCode = 200
      }
+++
複製代碼

這樣登陸成功後,響應頭就會有一個set Cookie和對應的隨機ID,

咱們該如何利用這個隨機的ID呢?

改二:

咱們去看下set Cookie裏面的SessionID對應的值是多少

+++
if (path === '/') {
    let string = fs.readFileSync('./index.html', 'utf-8')
    // 讀取cookie的email
    let cookies = ''                                  // cookies有可能爲空
    if (request.headers.cookie) {
      cookies = request.headers.cookie.split(';')    // ['email = 1@...', 'xxx= 1','yyy=2']
    }
    let hash = {}
    for (let i = 0; i < cookies.length; i++) {
      let parts = cookies[i].split('=')
      let key = parts[0]
      let value = parts[1]
      hash[key] = value
    }
    // 拿到用戶cookie裏面的sessionID,而後經過sessionID去找sessions這個hash對應的對象的sign_in_email屬性對應的值
    let email = sessions[hash.sessionID].sign_in_email  // 這句話要理解
+++ 
複製代碼

咱們這樣寫let email = sessions[hash.sessionID].sign_in_email `會報錯,由於sessions是存在內存裏的,一刷新就沒了,因此要作一下判斷

let email = sessions[hash.sessionID].sign_in_email改成 ==>

let sessionId = hash[' sessionID']  // 當心取值時,鍵名有空格
let mySession = sessions[sessionId]
let email
if (mySession) {
    email = mySession.sign_in_email
}
複製代碼

.和[]的區別:

sessions[sessionID] = { sign_in_email: email }
複製代碼

sessions.sessionID = { sign_in_email: email }
複製代碼

的區別是什麼??

JavaScript中對象屬性有兩種調用方式,一種是點"."一種是"[]"。 通常狀況下咱們使用點調用屬性的方式,可是當obj的某個屬性是一個變量時(你的attr在這裏是一個字符串),這種點調用的方式就行不通了,想一想看obj.'property'這樣的方式不對; 因此,若是對象內的屬性是一個變量,只能使用[]調用

之後能不用.就不用點,而用[]


完成改一和改二處的代碼後,重啓服務器: node server 8888

而後刷新頁面後請求http://localhost:8888/sign_in

而後打開network,sign_in的響應頭裏有set-Cookie: sessionID = 260634.3102729094

是怎麼知道咱們的密碼的呢?

是由於咱們在訪問首頁的時候就帶上了Cookie: sessionID=260634.3102729094

服務器就去看內存裏面這個ID對應的對象的email是多少,因此服務器就知道用戶的身份了,也就是說不告訴瀏覽器email是多少,只告訴這個email對應的ID是多少,這個ID對應的email服務器是有記錄的,就比如是進景區不告訴你票的姓名是什麼,只告訴你票對應的ID是多少,這個票的ID對應的姓名不是你關心的,可是景區是有記錄每一個ID對應的姓名是什麼

就拿去景區買票的例子來講

一個服務器維護了一個哈希叫sessions,它給每一個用戶分配了一小塊內存,這個內存用來存儲用戶的隱私數據,用戶值保存這個key也就是隨機數,爲何是隨機數,由於別人很難猜,就篡改不掉cookie了

Session(不翻譯)---- 什麼是session(session的是指是一塊內存)

1.session是基於cookie實現的session必須將SessionID(隨機數)經過 Cookie 發給客戶端

2.客戶端訪問服務器時,服務器讀取 SessionID

3.服務器有一塊內存(哈希表)保存了全部 session

4.經過 SessionID 咱們能夠獲得對應用戶的隱私信息,如 id、email

5.這塊內存(哈希表)就是服務器上的全部 session

若是不當心手動將這個隨機數刪了,請從新登陸,由於這個隨機數服務器也沒有保存(就比如是把票弄丟了,請從新買票)

編程的經驗:一些看起來很複雜的東西,都是由簡單的數據結構和簡單的交互邏輯實現的,好像不少地方都用到了內存,哈希

JS作的事情:

1.操做dom 2.ajax發請求 3.組織代碼,這些已經被vue,react作了

localStorage簡介

localStorage是html5提供的API即window.localStorage

localStorage的本質是哈希,Session是服務器上的哈希表,localStorage是瀏覽器上的哈希表

localStorage如何存值呢?

1.任何一個對象({}).toString()都會變成[object object]

2.只能存字符串,若是存的是對象會自動轉成字符串

3.若是真的想存那個對象怎麼辦呢?只能經過JSON轉成字符串

localStorage如何取值呢?

clear以後localStorage就清空了

localStorage用法

變量會消失

咱們在html文件中插入js,在js中寫var a = 1 console.log(a).用瀏覽器在控制檯輸入a= 2的,這時候a就等於2,可是當咱們刷新頁面的時候,這個a是幾?當頁面刷新以後,這個變量就沒了,變成一個新的a ,a = 1.那有什麼辦法可讓變量能記住它上一次的值呢?經過localStorage,它是存在C盤的文件裏的,不是存在瀏覽器上的,因此頁面刷新,那個文件仍是一直存在在那裏

localStorage讓前端有記憶力

例子: 下面這樣第一次回去localStorage裏找a的值,發現沒有a = 0,而後將a做爲localStorage的key,a的值做爲localStorage裏的key對應的值,下一次每次刷新頁面,local的a的值都會加1

+++
 <script>
    let a = localStorage.getItem('a')
    if(!a){
        a = 0
    }else{
        a = parseInt(a,10)+1
    }
    console.log('a')
    console.log(a)
    localStorage.setItem('a',a)
    </script>
+++
複製代碼

在有localStorage以前全部的變量在頁面刷新的那一刻所有銷燬不能保存下來,可是有了localStorage能夠存在本地的c盤的一個文件裏以便之後長期的使用,也就是說在localStorage出來以前前端是沒有記憶力的.

localStorage的應用場景

例:若是咱們的網站改版了,要提示用戶

alert('您好,咱們的網站改版了,新添了這些功能....')
複製代碼

這樣每次用戶刷新頁面都會彈框提示,會很煩,有沒有辦法只提示一次?

這就須要在跨頁面的時候去記一些東西,怎樣作呢,不要存到變量裏,存到localStorage裏

<script>
    let already = localStorage.getItem('已經提示了')
    if(!already){
        alert('您好,網站已經改版了,多了這些功能...')
        localStorage.setItem('已經提示了',true)
    }else{
        
    }
</script>
複製代碼

這樣第一次進入頁面的時候,咱們發現沒有提示用戶,因此就提示用戶,當用戶點擊肯定以後,localStorage已經將'已經提示了'的key存下來了,第二次刷新頁面就不會再刷新頁面了,由於咱們知道上一次已經提示過了,這就是localStorage最典型的應用記錄下有沒有提示過用戶

localStorage的特色

1.LocalStorage 跟 HTTP 無關

2.HTTP 不會帶上 LocalStorage 的值

3.只有相同域名的頁面才能互相讀取 LocalStorage(沒有同源那麼嚴格)

4.每一個域名 localStorage 最大存儲量爲 5Mb 左右(每一個瀏覽器不同)

5.經常使用場景:記錄有沒有提示過用戶(沒有用的信息,不能記錄密碼) LocalStorage 永久有效,除非用戶清理緩存

6.理論上localStorage永久有效,除非用戶手動清理緩存即在瀏覽器按住ctrl+shift+deleate設置裏的高級裏的清除cookie及其餘數據,其餘數據就包括localStorage的數據

若是不是相同域名都能訪問彼此的localStorage那太危險了,若是A網站知道一我的的姓名,郵箱,B網站知道同一我的的出生日期,身份證號,手機號,C網站知道銀行卡的帳號,住址之類的用戶我的信息,只要這3家網站之間存在彼此相互競爭的關係,就不會拿到用戶的完整信息,因此是相對比較安全的

一旦存的數據超過了localStorage的最大容量就會報錯

SessionStorage

1,2,3,4,點同localStorage

5.SessionStorage在用戶關閉頁面後就失效

sessionStorage也是window上的一個屬性,API和localStorage同樣

sessionStorage和上面說到的session一點關係都沒有,sessionStorage叫會話存儲而session不翻譯

不基於Cookie的session

session能夠不基於cookie實現

拿以前寫的註冊登陸的代碼來講;

當用戶登陸成功後不後臺不響應cookie而是經過JSON直接返回sessionID傳給前端,前端拿到響應後把sessionID後存到localStorage裏面備用,而後跳轉到首頁時候的路徑就不是/,而是把sessionStorage寫到查詢參數裏面,怎樣知道當前用戶的Session呢?經過sessions[query.sessionID],這樣首頁就能夠經過查詢參數來知道當前用戶的sessionID是什麼,這個Session就有用戶的email

// server.js
+++
else if (path === '/sign_in' && method === 'POST') {   // 登陸
    +++
    if (found) {
        let sessionID = Math.random() * 1000000
        sessions[sessionID] = {sign_in_email: email }
        // response.setHeader('set-Cookie', `sessionID=${sessionID}`)      第二種方法基於不依賴Cookie的Session
        response.write(`{"sessionID": ${sessionID}}`)
        response.statusCode = 200
    }
   +++
}
+++
複製代碼
// sign_in.html
+++
$.post('/sign_in', hash)
    .then((response) => {
        // window.location.href = '/'   第二種方法基於不依賴Cookie的Session
        let obj = JSON.parse(response)
        localStorage.setItem('sessionID',obj.sessionID)
         window.location.href = `/?sessionID=${obj.sessionID}`   // 必定要當心這裏的href地址不能有空格否則就找不到這個sessionID
    }, (request) => {
            alert('郵箱與密碼不匹配')
        })
+++
複製代碼
// server.js 
+++
if (path === '/') {
    let string = fs.readFileSync('./index.html', 'utf-8')
    /*  註釋的部分爲經過cookie的方法拿到用戶的email和password
    let cookies = ''                                  // cookies有可能爲空
    if (request.headers.cookie) {
      cookies = request.headers.cookie.split(';')    // ['email = 1@...', 'xxx= 1','yyy=2']
    }
    let hash = {}
    for (let i = 0; i < cookies.length; i++) {
      let parts = cookies[i].split('=')
      let key = parts[0]
      let value = parts[1]
      hash[key] = value
    }
    let sessionId = hash[' sessionID']
    let mySession = sessions[sessionId]
    */
    // 下面的方法是首頁經過前端發來的查詢參數的sessionID去sessions裏面讀哪一個session,這個session就有用戶的郵箱

    let mySession = sessions[query.sessionID]
    let email
    if (mySession) {
      email = mySession.sign_in_email
    }
+++
複製代碼

以上註釋的部分爲以前是經過基於cookie的Session

Session大部分狀況是基於Cookie來存sessionID的,可是也能夠經過查詢參數和localStorage來存儲sessionID

爲何會有人拿cookie和localStorage比較呢?

是有歷史緣由的,localStorage是新的API,那麼以前的程序員是如何作跨頁面的存儲呢?只能經過cookie, cookie也是存在c盤的一個文件裏的,因此Cookie,以後出現了localStorage就不用cookie了,cookie的一個問題就是全部保存在cookie上的數據都會被保存到服務器上 ,這就讓請求變慢,服務器性能降低

前端永遠不要讀寫Cookie,那是後端作的事,Cookie通常只存一個sessionID,不存用戶的信息,若是想知道用戶的數據,向服務器請求

http緩存之緩存控制Cache-Control

如何加緩存

也就是日常說的web性能優化,說的就是如何加速http的請求和響應

那就向以前寫的註冊登陸的項目裏,添加css/default.css和js/main.js,而後在server.js裏給他們加路由

而後讓default.css和main.js的內容很大很大,發現它們請求的時間很長,那麼如何讓它們請求的事件加快呢?

在響應的文件路由裏添加

response.serHeader('Cache-Control','max-age=30') 
複製代碼

在控制檯看響應文件的響應頭多了'Cache-Control','max-age=30',表示30秒以內就不要再次向服務器發起請求了,當前的已是最新的了.因此該文件的請求時間很短,基本上是0ms.30秒以後再次刷新頁面請求的時候,又會從新發起請求可是響應頭又多了一個'Cache-Control','max-age=30'再接下來的30秒內不會再向服務器發請求......如此往復

如何更新緩存

爲何不少網站首頁不容許在響應頭裏設置Cache-Control?(通常html不設置緩存)

由於若是頁面更新了,用戶無法獲取最新的版本,由於在緩存時間內,用戶使用的都是舊的緩存

在設置的緩存時間內加載文件的時候不會再向服務器發請求.那怎麼辦呢?若是在設置的緩存時間內,服務器更新了這個文件呢?只有請求相同的URL纔會利用以前的緩存,若是文件更新了只需在以前的請求路徑下加上一個查詢參數就好了

如: <link rel="stylesheet"href="./css/default.css?v=2">

這樣有什麼用呢?還不是在訪問這個css,可是URL變了,多了個查詢參數,這時候就會重新向服務器發請求,由於URL不是以前的URL了,不是以前的URL就不用以前的緩存了,這樣每次升級更新文件的時候,再引入文件的地方修改請求的URL讓每次的查詢參數不同就能夠了.當咱們改了請求的URl時,第一次打開頁面會從新發起請求,可是第二次再次打開的時候又會回到緩存控制的狀態,繼續不向服務器發請求

如:<link rel="stylesheet" href="./css/default.css?v=3">

因此實際中通常'Cache-Control','max-age=xxx'的時間有多長就設置多長,若是文件更新了,只須要修改文件請求時候的URL便可

如:<link rel="stylesheet" href="./css/default.css?v=n">

這樣既能保證緩存的事件保持好久,又能隨時升級更新文件

Expires

和Cache-Control很像的也是用來控制緩存的,若是同時使用Cache-Control和Expires就會使用Cache-Control,由於Cache-Control是後面纔出來的,之前用Expires,如今用Cache-Control

語法:

Expires: <http-date>   // 這裏的時間是GMT時間
複製代碼

當前的GMT時間

d = new Date()
Wed Jul 31 2019 11:49:12 GMT+0800 (中國標準時間)
d.toGMTString()
"Wed, 31 Jul 2019 03:49:12 GMT"
複製代碼

也就是說Expires是設置什麼時間過時(設置時間點),Cache-Control(設置時間點)設置的是多長時間後過時

爲何儘可能別用Expires呢,由於它的事件是基於本地時間,若是用戶本地事件錯亂了,那麼緩存控制的事件也就還不肯定了

若是Expires和Cache-Control同時設置,那麼優先使用Cache-Control,由於Cache-Control是新的API

ETag

先說一下MD5,MD5是一個摘要算法,什麼是摘要算法

現實中咱們下載一個文件,咱們怎麼知道本身有沒有下載對呢?會不會中間除了什麼錯,致使少了幾個字節呢?MD5就是爲了解決這個情景生的

好比下載Linux

Linux    400MB     MD5值: xxxx

本身下載的400MB     MD5值: yyyy

理論上2個MD5值要同樣才表示下載的Linux是完整的

好比: 在桌面上建立了一個1.txt的文件,文件內容爲1111111111111,而後在終端中運行md5sum 1.txt(在Linux系統中)就會顯示 f85ed079926039709c8f506a1866c2a9 這樣的一串MD5值

咱們再在桌面賦值一份1.txt,文件名叫1-copy.txt,而後進去刪掉文件內容的一個1,而後在終端中運行md5sum 1-copy.txt就會顯示MD5的值爲87b8769b874865e65a4525bfe9e56ba8

先後比較2次的MD5值是不同的因此就意味着下載的文件不是完整的了

MD5和http有什麼關係嗎?ETag

nodejs也有一個MD5的庫,這個庫沒用過,就用CRM的套路,Google nodejs md5,就會出現npm有一個md5的庫

首先安裝一下:npm install md5

引入:

var md5 = require('md5');
複製代碼

而後在server.js(後端文件)中須要算MD5的路由上,而後把文件的MD5即fileMD5放到ETag上

+++
 if (path === '/js/main.js') {
    let string = fs.readFileSync('/js/main.js', 'utf-8')
    let fileMD5 = md5(string)
    response.setHeader('ETag', fileMD5)
複製代碼

當加載main.js的時候,文件的響應頭裏面就多一個ETag:MD5的值

但是這有什麼用呢?當咱們刷新頁面的時候,請求頭裏面多了一個

if-None-Match: ETag上那個MD5的值
複製代碼

也就是說他把上一次的MD5值放到下一次請求的if-None-Match裏面,if-None-Match裏面的MD5值就至關於一個版本號,若是服務器收到這個版本號若是和服務器上的MD5值同樣說明不須要下載

+++
 if (path === '/js/main.js') {
    let string = fs.readFileSync('/js/main.js', 'utf-8')
    let fileMD5 = md5(string)
    response.setHeader('ETag', fileMD5)
    if(request.headers['if-None-Match'] === fileMD5){    // 若是同樣說明不要從新下載
        response.statusCode = 304                        //   沒有響應體只有響應頭                        
    }else{
        response.setHeader('ETag', fileMD5)
        response.write(string)                           // 有響應體
    }
     response.end()
+++
複製代碼

304表示not Modified,表示沒改過

那麼使用ETag和使用Cache-Control有什麼區別嗎?

Cache-Control:表示在緩存時間內若是再次加載相同URL的文件就直接不發起http請求到服務器,從瀏覽器緩存中拿數據

ETag: 第一次加載文件的時候,會把MD的值放到響應頭的ETag裏面,當第二次加載文件的時候,頁面發起請求時請求頭多一個if-None-Match它的值是上一次的MD5的值.服務器收到請求後會和上一次的MD5值進行比較若是同樣就沒有響應體,給個304

一句話總結區別就是,Cache-Control不發起請求,ETag發請求可是響應體是空的(都是針對請求同上一次同樣的文件)

相關文章
相關標籤/搜索