日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

老板的苦惱

假如你在繁華的街角開了一家店,每天客人絡繹不絕。

不過你作為老板卻有一些苦惱,你想知道自己的顧客上一次是什么時候來的?

在店里的時候買了什么商品,方便購物的時候進一步提升用戶體驗。

可是這些客人赤果果的來,無牽掛的走,店里一直沒有留下客人的信息,聰明的你會怎么解決這個問題呢?

web 會話機制之 session cookie 詳解

 

輸入圖片說明

互聯網沒有記憶

我們常說互聯網沒有記憶。互聯網背后的 HTTP 協議也是如此,正因為它無狀態,所以足夠簡單,便于拓展,得以發展到今天這種局面。

同時也正是因為 HTTP 協議無狀態,所以對用戶訪問等缺乏識別記憶功能。

那怎么解決這個問題呢?

目前有兩張最主流的方式:cookie 和 session。

cookie

Cookie 是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現 Session 的一種方式。

這個就好比我們把客戶上次到店里的時間放在用戶的口袋里,下次他們來的時候,我們拿出來看一下,就知道客戶上次是什么時候來的了。

當然這些信息用戶自己是可以修改的,比如各種瀏覽器的 cookies 可以被清空。

這讓我想起來以前讀的一個故事:

剛在路邊攤準備買點小吃。我說:老板我經常來買,給我便宜點吧。老板說:我今天第一天擺攤。

鐵鍋燉自己

信息都放在用戶的口袋里雖然方便,但是服務端也要記一些必要的信息,不然被忽悠了都不知道。

session

Session 是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據可以保存在集群、數據庫、文件中;

這個就類似于店里來客人了,服務員留心看一下,知道用戶購物車里放了什么商品,是否需要幫助等等。

Cookie 操作

為了讓大家直觀的感受到 cookie 的使用,我們來看一下 CRUD 的例子。

為了簡單,此處使用 servlet 進行演示。

web 會話機制之 session cookie 詳解

 

Cookie

說明

Cookie是瀏覽器保存信息的一種方式,可以理解為一個文件,保存到客戶端了啊,服務器可以通過響應瀏覽器的set-cookie的標頭,得到Cookie的信息。

你可以給這個文件設置一個期限,這個期限呢,不會因為瀏覽器的關閉而消失。

添加

我們可以新建一個 cookie 返回給 resp。

package com.github.houbb.simple.servlet;

import JAVAx.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/add")
public class CookieAddServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1. 創建 cookie 信息
        Cookie cookie = new Cookie("age", "10");
        //30min
        cookie.setMaxAge(30 * 60);
        //2. 返回給客戶端,用于客戶端保存
        resp.addCookie(cookie);

        //3. 頁面輸出
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        // 后端會根據頁面是否禁用 cookie,選擇是否將 sessionId 放在 url 后面
        String url = resp.encodeURL("/cookie/get");
        out.println("<a href='"+url+"'>獲取 cookie</a>");
    }

}

獲取

獲取 cookie 也比較簡單,直接通過 req.getCookies() 就可以獲取到整個 cookie 列表。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/get")
public class CookieGetServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 實際的邏輯是在這里
        PrintWriter out = resp.getWriter();

        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                out.println(cookie.getName()+"="+cookie.getValue()+"");
            }
        }
    }

}

刪除

cookie 是非法直接刪除的,一般都是首先獲取,然后設置 maxAge 為 0。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 清空
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/clear")
public class CookieClearServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        for(Cookie cookie : req.getCookies()) {
            // 立刻失效
            cookie.setMaxAge(0);
            cookie.setPath("/");
            resp.addCookie(cookie);
        }

        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<a href='/cookie/add'>添加 cookie 信息</a>");
    }

}

session

web 會話機制之 session cookie 詳解

 

session

說明

session的實現原理是建立在給瀏覽器回寫cookie,并且是以 JSESSIONID 為鍵,但是這個cookie是沒有時間的,也就是說,當你關閉瀏覽器時,代表一個會話結束了,也就是說你的session會被刪除,當你再次訪問服務器的時候,服務器會為你重新創建一個session。

添加

添加 session 屬性的方式也比較簡單,直接使用 req.getSession().setAttribute("name", "session"); 即可。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/add")
public class SessionAddServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 只有在 getSession 的時候,才會設置對應的 JSESSIONID
        req.getSession().setAttribute("name", "session");

        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();

        // 后端會根據頁面是否禁用 cookie,選擇是否將 sessionId 放在 url 后面
        String url = resp.encodeURL("/session/get");
        out.println("<a href='"+url+"'>獲取 session 信息</a>");
    }

}

獲取

我們可以通過 httpSession.getAttributeNames() 獲取到所有的 session 屬性。

也可以通過 req.getSession().getId() 得到我們的 JSESSIONID 屬性。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/get")
public class SessionGetServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 實際的邏輯是在這里
        PrintWriter out = resp.getWriter();
        String jsessionId = req.getSession().getId();
        out.println("jsessionId: " + jsessionId);

        HttpSession httpSession = req.getSession();
        Enumeration attrs = httpSession.getAttributeNames();
        while (attrs.hasMoreElements()) {
            String key = (String) attrs.nextElement();
            Object value = httpSession.getAttribute(key);
            out.println("key: " + key +"; value: " + value);
        }
    }

}

清空

清空 session 的操作非常簡單。

直接通過 httpSession.removeAttribute(key) 即可操作。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/clear")
public class SessionClearServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();

        HttpSession httpSession = req.getSession();
        Enumeration attrs = httpSession.getAttributeNames();
        while (attrs.hasMoreElements()) {
            String key = (String) attrs.nextElement();

            httpSession.removeAttribute(key);
            out.println("清空 key: " + key);
        }
    }

}

上面的代碼,為了便于大家學習,已經全部開源:

https://gitee.com/houbinbin/simple-servlet

session 的一些細節

相信很多小伙伴讀到這里依然是意猶未盡的。

接下來我們一起考慮幾個細節問題。

會話機制

session 創建于服務器端,保存于服務器,維護于服務器端,每創建一個新的Session,服務器端都會分配一個唯一的ID,并且把這個ID保存到客戶端的Cookie中,保存形式是以 JSESSIONID 來保存的。

一點細節

不要在意

通過HttpServletRequest.getSession 進行獲得HttpSession對象,通過setAttribute()給會話賦值,可以通過invalidate()將其失效。

  • 每一個HttpSession有一個唯一的標識SessionID,只要同一次打開的瀏覽器通過request獲取到session都是同一個。
  • WEB容器默認的是用Cookie機制保存SessionID到客戶端,并將此Cookie設置為關閉瀏覽器失效,Cookie名稱為:JSESSIONID
  • 每次請求通過讀取Cookie中的SessionID獲取相對應的Session會話
  • HttpSession的數據保存在服務器端,所以不要保存數據量耗資源很大的數據資源,必要時可以將屬性移除或者設置為失效
  • HttpSession可以通過 setMaxInactiveInterval() 設置失效時間(秒)或者在 web.xml 中配置
<session-config>
    <!--單位:分鐘-->
    <session-timeout>30</session-timeout>
</session-config>

session 的創建時機

一個常見的誤解是以為session在有客戶端訪問時就被創建,然而事實是直到某server端程序調用 HttpServletRequest.getSession(true) 這樣的語句時才被創建。

Session 何時被刪除

綜合前面的討論,session 在下列情況下被刪除

  1. 程序調用 HttpSession.invalidate();
  2. 距離上一次收到客戶端發送的 session id時 間間隔超過了session的超時設置;
  3. 服務器進程被停止(非持久session)

JSESSIONID 的創建與獲取

我們在 session 創建的時候,也就是第一次調用 HttpServletRequest.getSession(true) 時,會給客戶端分配一個 JSESSIONID 用于唯一標識這個用戶。

這個信息會被寫回到客戶端的 cookie 中,并且后續的請求都會攜帶。

比如我測試時的 JSESSIONID:

Cookie: JSESSIONID=8AE65FE9AEB0AA6053FADF9ED7AEE544

可以發現實際上 JSESSIONID 是非常依賴客戶端 cookie 的,那么問題來了,如果用戶禁用了 cookie 怎么辦?

客戶端禁用 cookie

cookie 是用戶自己的口袋,如果用戶有一天把口袋全部封死也是有可能的。

如果客戶端禁用了 cookie,一般有兩種解決方案。

隱藏域

我們將 JSESSIONID 的值傳入到頁面中,放入一個隱藏的 input 框中,每次請求帶上這個參數。

<form name="testform" action="/xxx"> 
  <input type="hidden" name="jsessionid" value="8AE65FE9AEB0AA6053FADF9ED7AEE544"/>
  <input type="text"> 
</form>

后端通過 req.getParameter("jsessionid") 的方式獲取到這個 jsessionid 信息。

URL 重寫

URL地址重寫的原理是將該用戶Session的id信息重寫到URL地址中。服務器能夠解析重寫后的URL獲取Session的id。

這樣即使客戶端不支持Cookie,也可以使用Session來記錄用戶狀態。

encodeURL() 方法在使用時,會首先判斷Session是否啟用,如果未啟用,直接返回url。

然后判斷客戶端是否啟用Cookie,如果未啟用,則將參數url中加入SessionID信息,然后返回修改的URL;如果啟用,直接返回參數url。

就像老馬前面代碼寫的一樣:

// 后端會根據頁面是否禁用 cookie,選擇是否將 sessionId 放在 url 后面
String url = resp.encodeURL("/session/get");
out.println("<a href='"+url+"'>獲取 session 信息</a>");

如果我們禁用 cookie,鏈接的地址就會變成:

http://localhost:8080/session/get;jsessionid=3E2EEB9840F2566EDB3085BA392AE6CB

;jsessionid=3E2EEB9840F2566EDB3085BA392AE6CB 這個是 encodeURL 自己加上去的,這樣我們就可以像原來一樣處理 session id 了。

連鎖店的機遇與挑戰

當目前為止,你作為一家店的老板已經可以輕松的掌握客戶的信息了。

哪怕用戶把自己的口袋封死。

隨著你的生意越來越好,你的店從一家門面,變成了多家連鎖店。

新的問題又來了,一個用戶去了其中的一家,當到另外一家店面的時候,如何得到用戶對應的信息呢?

連鎖店

這個就涉及到分布式系統的 session 共享問題。

其實解決問題的思路也是從兩個角度出發:

(1)用戶的角度

在用戶的口袋中放著驗證信息。

不過需要考慮信息被惡意修改等,這方面 JWT 做的比較優秀。

可以參考:

分布式系統 session 共享解決方案 JWT 實戰筆記

(2)服務者的角度

我們作為連鎖店,只需要把各個店里的商戶信息共享即可。

至于共享到哪里,可以是 redis 也可以是數據庫。

這方面 spring session 設計的比較優秀,可以參考:

springboot整合redis實現分布式session

spring session 結合攔截器實戰

小結

這一節老馬和大家一起學習了 web 會話機制中的 session 和 cookie 機制。

我們知道問題的源頭,自然就理解了一個技術產生需要解決的問題。

最后拓展到了分布式系統中的 session 共享問題,后續我們將重點介紹下 spring sesison 和 jwt,感興趣的小伙伴可以關注一波不迷路。

希望本文對你有所幫助,如果喜歡,歡迎點贊收藏轉發一波。

我是老馬,期待與你的下次相遇。

分享到:
標簽:session cookie
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定