會話技術之Cookie
Cookie 詳解
國際慣例,學什麼之前都得 HelloWorld 一下。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設定編碼
resp.setContentType("text/html; charset=UTF-8");
//new 一個cookie
Cookie cookie =new Cookie("username","ling");
//加入這個 cookie
resp.addCookie(cookie);
//輸出相關提示
resp.getWriter().write("Hello World! 寫入了一個cookie");
}
除錯模式下,可以看到已經存入自定義的 Cookie 。
Cookie 類的⽅法簡述:
-
Cookie(String name,String value) :構造方法
-
setComment(String purpose) 與 getComment(): 用於註釋,如果瀏覽器向使用者展示 Cookie,需要使用到。
-
setValue(String newValue) 與 getValue() ⽅法:為 cookie 賦值或者取值。
-
setMaxAge(int expiry) 與 getMaxAge()⽅法 :設定,獲取 Cookie 的有效期。
-
setPath(String uri) 與 getPath() ⽅法:Cookie 的path 屬性決定允許訪問Cookie 的路徑。⼀般地,Cookie 釋出出來,整個⽹⻚的資源都可以使⽤。現在我只想某個 Servlet 可以獲取到 Cookie,其他的資源不能獲取。
-
setDomain(String pattern) 與getDomain() ⽅法 :想要同級的域名可以共享Cookie 需要用到該方法。
-
getName⽅法 :取得該cookie 的 name ,name 在建立後不可以修改。
-
setSecure(boolean flag) :HTTP協議是⽆狀態的,還是不安全的!如果不想Cookie在⾮安全協議中傳輸,設定Cookie的secure屬性為true,瀏覽器只會在 HTTPS 和 SSL 等安全協議中傳輸該 Cookie。設定secure屬性不會將Cookie的內容加密。如果想要保證安全,最好使⽤ MD5 演算法加密。
Cookie 儲存中文(編碼問題)
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設定編碼
resp.setContentType("text/html; charset=UTF-8");
//new 一個cookie
//這裡與此前設值不同需要進行編碼處理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie","UTF-8"));
//加入這個 cookie
resp.addCookie(newCookie);
//取出該中文 Cookie 自然是需要解碼的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//輸出相關提示
resp.getWriter().write("Hello World! 寫入了一箇中文Cookie");
}
頁面是可以看到中文 Cookie 的正確輸出,上面還有HelloWorld 時候儲存下來的 Cookie。
當然在瀏覽器儲存的值是編碼後的 Cookie 。
Cookie 的有效期(刪除)
- 正值(n)表示該cookie將在n秒後過期。請注意,該值是Cookie過期的最長期限,而不是Cookie的當前期限,單位是 秒。
- 負值表示cookie不會持久儲存,並且在網路瀏覽器退出時將被刪除。
- 零值將導致cookie 被刪除。這是刪除Cookie 的一個途徑。
//設定有效期
newCookie.setMaxAge(60 * 60 * 24 *2); //以秒為單位,設定期限為兩天,現在是2020-09-04
注意要在新增該 Cookie 之前設定有效期,最後記得加入該 Cookie。
此外,不知道你是否注意到
原來 HelloWorld 時候儲存下來的 Cookie ,我們並沒有設定它的有效期,瀏覽器顯示它的有效期是 Session ,這就意味著,這是預設的值,僅限於此次會話,關閉頁面該 Cookie 就會失效。
Cookie 的域名
不知道你收否注意到:
在此之前,首先要明確Cookie 具有不可跨域名性,訪問不同的網站,瀏覽器會頒發不同的 Cookie 給伺服器,也不會修改別的網站的 Cookie。即使⼀級域名相同,⼆級域名不同,也不能獲取到 Cookie。
但是一個龐大的網站會像一個樹狀結構,必定會細分有二級域名,如果這時候,需要二級域名也可以訪問到相同一級域名下的 Cookie ,需要使用到 setDomain(String pattern) 方法。再次強調,要在新增該 Cookie 之前設定有效期,最後記得加入該 Cookie 。
//設定執行訪問Cookie 的域名
newCookie.setDomain(".ling.com"); //取值規定為".域名"
此時 www.blog.ling.com 也可以訪問到 www.ling.com 釋出的Cookie。
個人測試可以使用 Tomcat 配置不同的臨時域名進行請求測試,具體配置可以參照 Tomcat 簡單配置使用,基本工作原理 中的配置臨時域名部分進行配置。
Cookie 的路徑
在瀏覽器中 Domain 旁邊是 Path 屬性。
可以在瀏覽器中看到,每一個 Cookie 都會有自己的路徑。path 屬性決定允許的訪問路徑。一般來說 Cookie 一旦釋出,相同域名的網站都可以訪問到。
現在有一需求是,我只想,某些路徑以及該路徑下的子路徑可以訪問到該 Cookie。這時候可以使用 setPath(String uri) 方法。
假設你的瀏覽器當前已經有了兩個Cookie:
- c1:name=id; value=itcast; path=/ling/;
- c2:name=name; value=qdmmy6; path=/ling/servlet/。
當訪問http://localhost/ling/*時,請求頭中會包含c1,而不會包含c2。
當訪問http://localhost/ling/servlet/*時,請求頭中會包含c1和c2。
也就是說,在訪問子路徑時,會包含其父路徑的Cookie,而在訪問父路徑時,不包含子路徑的Cookie。
此前的專案名筆者設定為空,現在設定為 /ling ,以便驗證結論是否正確
Cookie的SetPath設定cookie的路徑,這個路徑直接決定伺服器的請求是否會從瀏覽器中載入某些cookie。
首先預設情況如果不設定cookie的path,預設是 /專案名稱/當前路徑的上一層地址如:請求路徑:/cookie_demo/servlet/login, cookie的路徑:/cookie_demo/servlet
如果我們設定path,如果當前訪問的路徑包含了cookie的路徑(當前訪問路徑在cookie路徑基礎上要比cookie的範圍小)cookie就會載入到request物件之中。
先不進行訪問路徑設定,訪問 http://localhost:8080/ling/cookies/testCookies ,檢視預設路徑。
就是 /專案名稱/當前路徑的上一層地址 。
再訪問 http://localhost:8080/ling/request/getServlet ,驗證是否能訪問該Cookie。
在未進行設定的情況下,當然是不行的。
下面進行設定:
//設定訪問路徑
newCookie.setPath("/ling/"); // /ling/ 路徑下所有子路徑都可以訪問
先訪問 http://localhost:8080/ling/cookies/testCookies 設定Cookie。
再訪問 http://localhost:8080/ling/request/getServlet ,驗證是否能訪問該Cookie。
Cookie 的修改
刪除,修改 Cookie 時,新建的 Cookie 除了 value 、maxAge 之外的所有屬性都要與原 Cookie 相同。否則瀏覽器將視為不同的 Cookie,不予覆蓋,導致刪除修改失敗!
為什麼不把修改放在刪除後說,為了驗證在除了 value 、maxAge 之外的其他屬性不同時,是否能刪除原 Cookie。
這裡的其他屬性,就以 Path 屬性舉例。
注意前提條件
已知,在瀏覽器已經將 name : Chinese-Cookie ;value:%E4%B8%AD%E6%96%87Cookie---2 ;Path :/ling/cookies . 的 Cookie1 儲存到硬碟上。
現在 new 一個 Cookie2 將 Path 設定為 /ling/ 再將其刪除(即 setMaxAge(0) )。
這次的訪問路徑:http://localhost:8080/ling/cookies/testCookies
//設定編碼
resp.setContentType("text/html; charset=UTF-8");
//new 一個cookie
//這裡與此前設值不同需要進行編碼處理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie---3,我是Cookie2 。。。","UTF-8"));
//cookie 的各種設定
//設定有效期
newCookie.setMaxAge(0); //設定期限為0 ,試圖刪除 Cookie1
//設定訪問路徑
newCookie.setPath("/ling/"); // /ling/ 路徑下所有子路徑都可以訪問
//加入這個 cookie
resp.addCookie(newCookie);
//取出該中文 Cookie 自然是需要解碼的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//輸出相關提示
resp.getWriter().write("Hello World! 寫入了一箇中文Cookie");
預期是不能刪除 Cookie1 ,得到的將是 Cookie1 ,因為 Cookie2 剛剛 new 出來雖然加了進去,可是已經 setMaxAge(0) ,就像這九子奪嫡,四阿哥已經登基,其他皇子想上臺面,就像 Cookie2想輸出在頁面,自然也是輪不到他。
這次來真的,刪掉 Cookie1 。
//設定編碼
resp.setContentType("text/html; charset=UTF-8");
//new 一個cookie
//這裡與此前設值不同需要進行編碼處理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie---3,我是Cookie2 。。。","UTF-8"));
//cookie 的各種設定
//設定有效期
newCookie.setMaxAge(0); //設定期限為0 ,試圖刪除 Cookie1
//加入這個 cookie
resp.addCookie(newCookie);
//取出該中文 Cookie 自然是需要解碼的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//輸出相關提示
resp.getWriter().write("Hello World! 寫入了一箇中文Cookie");
唉?你這沒設定路徑啊?
噢,你得結合上面說的訪問路徑和 Cookie 的路徑這一小節來看,因為訪問路徑是 http://localhost:8080/ling/cookies/testCookies ,這次的 Cookie2 預設Path 就是 /ling/cookies
可以看到,是可以獲取到在硬碟裡的 Cookie1 的,輸出在頁面的還是 Cookie1 ,Cookie2 終究上不了檯面,只是 Cookie1 被 Cookie2 覆蓋以後,兩大高手同歸於盡,瀏覽器再無 Cookie1 Cookie2 。
Cookie 的安全
HTTP協議不僅僅是⽆狀態的,⽽且是不安全的!如果不希望Cookie在⾮安全協議中傳輸,可以設定Cookie的secure屬性為true,瀏覽器只會在HTTPS和SSL等安全協議中傳輸該Cookie。
//設定只在安全協議下傳輸 Cookie
newCookie.setSecure(true);
當然了,設定secure屬性不會將Cookie的內容加密。如果想要保證安全,最好使⽤md5演算法加密。
Cookie 的簡單應用
顯示使用者上次訪問時間
首先明確需求:
- 第一次登入,記錄下時間;
- 第n次登入(n >= 2),顯示前一次登入時間,並更新時間。
要判斷是否是第一次登入,可以通過判斷是否含有指定 Cookie 來決定。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設定時間樣式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//編碼
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
//獲取Cookie
Cookie[] cookies = req.getCookies();
//迴圈取出 Cookie
for(int i = 0 ; cookies.length >0 && i < cookies.length -1 ; i++){
//判斷是否含有所需要的的 Cookie--"login-time"
if(cookies[i].getName().equals("login-time")){
//注意進行編碼
writer.write("歡迎您,您上次登入的時間是: "+ URLDecoder.decode(cookies[i].getValue(),"UTF-8"));
//有則顯示上一次時間,注意要解碼,並 new Cookie() 更新時間,以便下一次顯示使用
cookies[i].setValue(URLEncoder.encode(simpleDateFormat.format(new Date()),"UTF-8"));
//設定有效期
cookies[i].setMaxAge(2000);
resp.addCookie(cookies[i]);
return ;
}
}
//沒有則顯示是第一次登入,設定登入時間
Cookie cookie =new Cookie("login-time", URLEncoder.encode(simpleDateFormat.format(new Date()),"UTF-8"));
cookie.setMaxAge(2000);
resp.addCookie(cookie);
writer.write("歡迎您,您是第一次登入。");
}
第一次登入:
重新整理一下:
需要注意的幾個點
-
編碼問題:需要設定編碼,否則輸出的中文會出現亂碼。
-
更新 Cookie 要設定Cookie 的有效期,否則更新的 Cookie 有效期預設為 Session ,關閉頁面後,Cookie將會被銷燬,不會被儲存在硬碟。
-
Cookie 不支援特殊符號。下圖表示 Cookie 的 Value 中有空格,會報伺服器的錯。解決方案有兩個:
- 把空格換成別的符號,比如下劃線 _ ,橫杆 - 等等。
- 如上程式碼所示,進行編碼,不過注意,取值的時候也要進行解碼。
web.xml
參照 HttpServletRespnse 物件相關基本應用 進行配置 Servlet 。