概述:會話是瀏覽器和服務器之間的屢次請求和響應javascript
也就是說,從瀏覽器訪問服務器開始,到訪問服務器結束,瀏覽器關閉爲止的這段時間內容產生的屢次請求和響應,合起來叫作瀏覽器和服務器之間的一次會話html
實際上會話問題解決的仍是客戶端與服務器之間的通訊問題,經過一些會話技術,能夠將每一個用戶的數據以例如cookie/session的形式存儲,方便之後用戶訪問web資源的時候使用java
假定場景:A和B兩人在某個網上購物商場登錄帳號後,A買了一個HHKB的鍵盤,而B則購買了一把民謠吉他,這些信息都會被保存下來用途是:保存帳戶信息,登陸時詢問往後是否自動登陸,或者根據以前瀏覽,購買過的商品,分析用戶喜歡什麼類型的商品,作出精準推送web
那麼能不能用咱們以前學過的 HttpServletRequest 對象和 ServletContext 對象來保存這些數據呢?答案是否認的算法
不能用 HttpServletRequest 的緣由:咱們的一次會話中,存在屢次請求和響應,而瀏覽器客戶端的每一次請求都會產生一個 HttpServletRequest 對象,它只會保存這次請求的信息,例如放入購物車與購買付款是不一樣的請求,很顯然數據沒有獲得很好的保存處理數據庫
不能用 ServletContext 的緣由:ServletContext對象是被整個web應用所共享的,將數據都存到這裏,無疑會沒法區分具體信息的歸屬跨域
客戶端會話技術 —— Cookie數組
服務器會話技術 —— Session瀏覽器
Cookies 能夠簡單的理解爲服務器暫存在你瀏覽器中的一些信息文件,它將你在網站上所輸入的一些內容,或者一些選項記錄下來,當下一次你訪問同一個網站的時候,服務器就會主動去查詢這個cookie資料,若是存在的話,將會根據其中的內容,提供一些特別的功能,例如記住帳號密碼等tomcat
總結一下就是:
瀏覽器訪問服務器,若是服務器須要記錄該用戶的狀態,就用response向瀏覽器發送一個cookie,瀏覽器會把Cookie保存起來。當瀏覽器再次訪問服務器的時候,瀏覽器會把請求的網址以及Cookie一同提交給服務器
面的數據是HTTP對Cookie的規範,可是如今一些瀏覽器可能會對Cookie規範 作了一些擴展,例如每一個Cookie的大小爲8KB,最多可保存500個Cookie等
不一樣的瀏覽器之間是不共享Cookie的
//用於在其響應頭中增長一個相應的Set-Cookie頭字段 addCookie //用於獲取客戶端提交的Cookie GetCookie public Cookie(String name,String value) //該方法設置與 cookie 關聯的值。 setValue //該方法獲取與 cookie 關聯的值。 getValue //該方法設置 cookie 過時的時間(以秒爲單位)。若是不這樣設置,cookie只會在當前 session 會話中持續有效。 setMaxAge //該方法返回 cookie 的最大生存週期(以秒爲單位),默認狀況下,-1 表示 cookie 將持續下去,直到瀏覽器關閉 getMaxAge //該方法設置 cookie 適用的路徑。若是您不指定路徑,與當前頁面相同目錄下的(包括子目錄下的)全部 URL 都會返回 cookie。 setPath //該方法獲取 cookie 適用的路徑。 getPath //該方法設置 cookie 適用的域 setDomain //該方法獲取 cookie 適用的域 getDomain
Cookie cookie = new Cookie("xxx",URLEncoder.encode(name,"UTF-8"));
經過setMaxAge()方法能夠設置Cookie的有效期
Cookie存儲的方式相似於Map集合,分爲名字和值,只不過二者都是String類型的
修改
String name = "颳風這天"; Cookie cookie = new Cookie("country",URLEncoder.encode(name,"UTF-8"));
刪除
String name = "颳風這天"; Cookie cookie = new Cookie("country",URLEncoder.encode(name,"UTF-8")); cookie.setMaxAge(0); response.addCookie(cookie); printWriter.writer("Cookie已經被刪除了")
Cookie的domain屬性決定運行訪問Cookie的域名,Deomain的值規定爲「.域名」
Cookie的隱私安全機制決定Cookie是不可跨域名的。及時是同一級域名,不一樣的二級域名也不能交接,eg:www.ideal.com 和 www.image..com
若是我但願一級域名相同的網頁之間的Cookie之間能夠互相訪問,須要使用到domain方法
Cookie cookie = new Cookie("name","admin"); cookie.setMaxAge(1000); cookie.setDomain(".ideal.com); response.addCookie(cookie); printWriter.writer("使用www.ideal.com域名添加了一個Cookie,只要一級域名是ideal.com便可訪問")
Cookie的path屬性決定容許訪問Cookie的路徑
通常來講,Cookie發佈出來,整個網頁的資源均可以使用,可是若是隻須要某一個Servlet能夠獲取到Cookie,其餘的資源不能或不須要獲取
Cookie cookie = new Cookie("name","admin"); cookie.setPath("/Servlet); cookie.setMaxAge(1000); response.addCookie(cookie); printWriter.writer("該Cookie只能在Servlet1中能夠訪問到")
HTTP協議不只是無狀態的,並且是不安全的!若是不但願Cookie在非安全協議中傳輸,能夠設置Cookie的secure屬性爲true,瀏覽器只會在HTTPS和SSL等安全協議中傳輸該Cookie
設置secure屬性不會將Cookie的內容加密,若是想保證安全,最好使用md5算法加密
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //設置響應的消息體的數據格式以及編碼 resp.setContentType("text/html;charset=utf-8"); //獲取全部Cookie Cookie[] cookies = req.getCookies(); ////沒有cookie爲lastTime boolean flag = false; //遍歷cookie數組 if(cookies != null && cookies.length > 0){ for (Cookie cookie : cookies) { //獲取cookie的名稱 String name = cookie.getName(); //判斷名稱是不是:lastTime if("lastTime".equals(name)){ //非第一次訪問 flag = true;//有訪問記錄的time //設置Cookie的value //獲取當前時間的字符串,從新設置Cookie的值,從新發送cookie Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); String str_date = sdf.format(date); System.out.println("編碼前:"+str_date); //URL編碼 str_date = URLEncoder.encode(str_date,"utf-8"); System.out.println("編碼後:"+str_date); cookie.setValue(str_date); //設置cookie的存活時間 cookie.setMaxAge(60 * 60 * 24 * 30);//一個月 resp.addCookie(cookie); //響應數據 //獲取Cookie的value,時間 String value = cookie.getValue(); System.out.println("解碼前:"+value); //URL解碼: value = URLDecoder.decode(value,"utf-8"); System.out.println("解碼後:"+value); resp.getWriter().write("<h1>歡迎回來,您上次訪問時間爲:"+value+"</h1>"); break; } } } if(cookies == null || cookies.length == 0 || flag == false){ //沒有,第一次訪問 //設置Cookie的value //獲取當前時間的字符串,從新設置Cookie的值,從新發送cookie Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); String str_date = sdf.format(date); System.out.println("編碼前:"+str_date); //URL編碼 str_date = URLEncoder.encode(str_date,"utf-8"); System.out.println("編碼後:"+str_date); Cookie cookie = new Cookie("lastTime",str_date); //設置cookie的存活時間 cookie.setMaxAge(60 * 60 * 24 * 30);//一個月 resp.addCookie(cookie); resp.getWriter().write("<h1>您好,歡迎您首次訪問</h1>"); } }
Character[32]在ASSCI碼中表明空格 因此在日期表示格式中儘可能不要出現空格,但若想要要求出現空格,或者特殊字符,
此外呢,我麼你還能夠作一個模擬顯示上次瀏覽過商品記錄的Demo,自行練習
Session是另外一種記錄瀏覽器狀態的機制,Cookie保存在瀏覽器中,Session保存在服務器中。用戶使用瀏覽器訪問服務器的時候,服務把用戶的信息,以某種形式記錄在服務器,這就是Session
爲什麼使用Session由於Session能夠存儲對象,Cookie只能存儲字符串能夠解決不少Cookie解決不了的問題
//獲取Session被建立時間 long getCreationTime() //獲取Session的id String getId() //返回Session最後活躍的時間 long getLastAccessedTime() //獲取ServletContext對象 ServletContext getServletContext() //設置Session超時時間 void setMaxInactiveInterval(int var1) //獲取Session超時時間 int getMaxInactiveInterval() //獲取Session屬性 Object getAttribute(String var1) //獲取Session全部的屬性名 Enumeration getAttributeNames() //設置Session屬性 void setAttribute(String var1, Object var2) //移除Session屬性 void removeAttribute(String var1) //銷燬該Session void invalidate() //該Session是否爲新的 boolean isNew()
Session有着request和ServletContext相似的方法。其實Session也是一個域對象。Session做爲一種記錄瀏覽器狀態的機制,只要Session對象沒有被銷燬,Servlet之間就能夠經過Session對象實現通信
設置
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession httpSession = request.getSession(); httpSession.setAttribute("name", "test"); }
獲取
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession httpSession = request.getSession(); String value = (String) httpSession.getAttribute("name"); System.out.println(value); }
用戶第一次訪問服務器Servlet,jsp等動態資源就會自動建立Session,Session對象保存在內存裏,這也就爲何上面的例子能夠直接使用request對象獲取獲得Session對象
若是訪問HTML,Image等靜態資源Session不會被建立
Session生成後,只要用戶繼續訪問,服務器就會更新Session的最後訪問時間,不管是否對Session進行讀寫,服務器都會認爲Session活躍了一次。
因爲會有愈來愈多的用戶訪問服務器,所以Session也會愈來愈多。爲了防止內存溢出,服務器會把長時間沒有活躍的Session從內存中刪除,這個時間也就是Session的超時時間
Session的超時時間默認是30分鐘,有三種方式能夠對Session的超時時間進行修改
第一種方式:在tomcat/conf/web.xml文件中設置,時間值爲20分鐘,全部的WEB應用都有效————<session-timeout>20<session-timeout>
第二種方式:在單個的web.xml文件中設置,對單個web應用有效,若是有衝突,以本身的web應用爲準
第三種方式:經過setMaxInactiveInterval()方法設置
httpSession.setMaxInactiveInterval(60);
問題:我再Aservlet中設置了Session屬性,在Bservlet中獲取A的屬性
在瀏覽器中新建一個頁面再次訪問Bservlet 報空指針異常
如今問題來了:服務器是如何實現一個session爲一個用戶瀏覽器服務的?換個說法:爲何服務器可以爲不一樣的用戶瀏覽器提供不一樣session?
HTTP協議是無狀態的,Session不能依據HTTP鏈接來判斷是否爲同一個用戶。因而乎:服務器向用戶瀏覽器發送了一個名爲JESSIONID的Cookie,它的值是Session的id值。其實Session依據Cookie來識別是不是同一個用戶。
簡單來講:Session 之因此能夠識別不一樣的用戶,依靠的就是Cookie
該Cookie是服務器自動頒發給瀏覽器的,不用咱們手工建立的。該Cookie的maxAge值默認是-1,也就是說僅當前瀏覽器使用,不將該Cookie存在硬盤中
流程概述:
遇到兩種狀況:1.用戶瀏覽器禁用了Cookie絕大多數手機瀏覽器都不支持Cookie
Java Web提供瞭解決方法:URL地址重寫
HttpServletResponse類提供了兩個URL地址重寫的方法:
encodeURL(String url) encodeRedirectURL(String url)
須要值得注意的是:這兩個方法會自動判斷該瀏覽器是否支持Cookie,若是支持Cookie,重寫後的URL地址就不會帶有jsessionid了【固然了,即便瀏覽器支持Cookie,第一次輸出URL地址的時候仍是會出現jsessionid(由於沒有任何Cookie可帶)】
例子
String url = "/web-01/Servlet5"; response.sendRedirect(response.encodeURL(url));
URL地址重寫的原理:
將Session的id信息重寫到URL地址彙總,服務器解析重寫後URL獲取Session的id,這樣一來即便瀏覽器禁用掉了Cookie,可是Session的id經過服務端傳遞,仍是可使用Session來記錄用戶的狀態。
案例一:使用Session完成用戶簡單登陸
先建立User類
public class User { private String username = null; private String password = null; public User() { } public User(String username, String password) { super(); this.username = username; this.password = password; } ......各類set get方法
使用簡單的集合模擬一個數據庫
public class UserDB { private static List<User> list =new ArrayList<>(); static { list.add(new User("admin","888")); list.add(new User("aaa","111")); list.add(new User("bbb","222")); } //經過用戶名密碼查找用戶 public static User find(String username, String password) { for (User user:list) { if (user.getUsername().equals(username)&& user.getPassword().equals(password)) { return user; } } return null; } }
表單提交咱們寫在jsp裏面(模仿便可後期說jsp)
public class UserDB { private static List<User> list =new ArrayList<>(); static { list.add(new User("admin","888")); list.add(new User("aaa","111")); list.add(new User("bbb","222")); } //經過用戶名密碼查找用戶 public static User find(String username, String password) { for (User user:list) { if (user.getUsername().equals(username)&& user.getPassword().equals(password)) { return user; } } return null; } }
獲取表單提交的數據,查找數據庫是否有相對應的用戶名和密碼
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); User user = UserDB.find(username, password); //若是找不到,就是用戶名或者密碼出錯了 if (user == null) { response.getWriter().write("用戶名或者密碼錯誤,登錄失敗 !"); return; } //標誌着用戶已經登陸 HttpSession httpSession = request.getSession(); httpSession.setAttribute("user", user); //跳轉到其餘頁面,告訴用戶已經登陸成功 response.sendRedirect(response.encodeURL("test.jsp")); }
案例二:利用Session防止表單重複提交
重複提交的危害:
在投票的網頁上不停地提交,實現了刷票的效果。
註冊多個用戶,不斷髮帖子,擾亂正常發帖秩序。
常見的兩種重複提交
第一種:後退再提交
第二種:網絡延遲,屢次點擊提交按鈕
略圖
解決方案:
網絡延遲問題:
對於第二種網絡延而形成屢次提交數據給服務器,實際上是客戶端的問題,咱們可使用javaScript來防止
→ 當用戶第一次點擊提交按鈕是,把數據提交給服務器,當用戶再次點擊提交按鈕時,就不把數據提交給服務器了
監聽用監聽事件。只能讓用戶提交一次表單:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>表單提交</title> <script type="text/javascript"> //定義一個全局標識量:是否已經提交過表單數據 var isCommitted = false; function doSubmit() { //false表示的是沒有提交過,因而就可讓表單提交給Servlet if (isCommited == false){ is Commited = true; return true; }else{ return false; } } </script> </head> <body> <form action="/web-01/Lservlet" method="post" onsubmit="return doSubmit()"> 用戶名:<input type="text" name="username"><br /> <input type="submit" value="提交"> </form> </body> </html>
刷新後退再提交問題:
咱們知道Session能夠用來標識一個用戶是否登錄了。Session的原理也說了:不一樣的用戶瀏覽器會擁有不一樣的Session。而request和ServletContext爲何就不行呢?request的域對象只能是一次http請求,提交表單數據的時候request域對象的數據取不出來。ServletContext表明整個web應用,若是有幾個用戶瀏覽器同時訪問,ServletContext域對象的數據會被屢次覆蓋掉,也就是說域對象的數據就毫無心義了。
此時,咱們就想到了,在表單中還有一個隱藏域,能夠經過隱藏域把數據交給服務器。
A:判斷Session域對象的數據和jsp隱藏域提交的數據是否對應。
B:判斷隱藏域的數據是否爲空【若是爲空,就是直接訪問表單處理頁面的Servlet】
C:判斷Session的數據是否爲空【servlet判斷完是否重複提交,最好能立馬移除Session的數據,否則尚未移除的時候,客戶端那邊兒的請求又來了,就又能匹配了,產生了重複提交。若是Session域對象數據爲空,證實已經提交過數據了!】
D:咱們向Session域對象的存入數據到底是什麼呢?簡單的一個數字?好像也行啊。由於只要Session域對象的數據和jsp隱藏域帶過去的數據對得上號就好了呀,反正在Servlet上判斷完是否重複提交,會立馬把Session的數據移除掉的。更專業的作法是:向Session域對象存入的數據是一個隨機數【Token--令牌】
public class TokenProcessor { private TokenProcessor() { } private final static TokenProcessor TOKEN_PROCESSOR = new TokenProcessor(); public static TokenProcessor getInstance() { return TOKEN_PROCESSOR; } public String makeToken() { // 這個隨機生成出來的Token的長度是不肯定的 String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(99999999)); try { // 咱們想要隨機數的長度一致,就要獲取到數據指紋 MessageDigest messageDigest = MessageDigest.getInstance("md5"); byte[] md5 = messageDigest.digest(token.getBytes()); // 若是咱們直接 return new String(md5)出去,獲得的隨機數會亂碼 // 由於隨機數是任意的01010101010,在轉換成字符串的時候,會差gb2312的碼錶 // gb2312碼錶不必定支持該二進制數據,獲得的就是亂碼 // 因而通過base64編碼成了明文的數據 BASE64Encoder base64Encoder = new BASE64Encoder(); return base64Encoder.encode(md5); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } }
建立Token隨機數,利用getRequestDispatcher跳轉到jsp頁面(地址仍是Servlet的)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 生出隨機數 TokenProcessor tokenProcessor = TokenProcessor.getInstance(); String token = tokenProcessor.makeToken(); // 將隨機數存進Session中 request.getSession().setAttribute("token", token); // 跳轉到顯示頁面 request.getRequestDispatcher("/login3.jsp").forward(request, response);
Jsp隱藏域獲取到Session的值
<form action="/web-01/Mservlet" > 用戶名:<input type="text" name="username"> <input type="submit" value="提交" id="button"> <%--使用EL表達式取出session中的Token--%> <input type="hidden" name="token" value="${token}" > </form>
在處理表單提交頁面中判斷:jsp隱藏域是否有帶值過來,Session中的值是否爲空,Session中的值和jsp隱藏域帶過來的值是否相等
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter printWriter = response.getWriter(); String serverValue = (String) request.getSession().getAttribute("token"); String clienValue = request.getParameter("token"); if (serverValue != null && clienValue != null && serverValue.equals(clienValue)) { printWriter.write("處理請求"); // 清除Session域對象數據 request.getSession().removeAttribute("token"); } else { printWriter.write("請不要重複提交數據"); } }
實現原理是很是簡單的
在session域中存儲一個token
而後前臺頁面的隱藏域獲取獲得這個token
在第一次訪問的時候,咱們就判斷seesion有沒有值,若是有就比對。對比正確後咱們就處理請求,接着就把session存儲的數據給刪除了
等到再次訪問的時候,咱們session就沒有值了,就不受理前臺的請求了!
從存儲方式上比較
Cookie只能存儲字符串,若是要存儲非ASCII字符串還要對其編碼。
Session能夠存儲任何類型的數據,能夠把Session當作是一個容器
從隱私安全上比較
Cookie存儲在瀏覽器中,對客戶端是可見的。信息容易泄露出去。若是使用Cookie,最好將Cookie加密
Session存儲在服務器上,對客戶端是透明的。不存在敏感信息泄露問題。
從有效期上比較
Cookie保存在硬盤中,只須要設置maxAge屬性爲比較大的正整數,即便關閉瀏覽器,Cookie仍是存在的
Session的保存在服務器中,設置maxInactiveInterval屬性值來肯定Session的有效期。而且Session依賴於名爲JSESSIONID的Cookie,該Cookie默認的maxAge屬性爲-1。若是關閉了瀏覽器,該Session雖然沒有從服務器中消亡,但也就失效了。
從對服務器的負擔比較
Session是保存在服務器的,每一個用戶都會產生一個Session,若是是併發訪問的用戶很是多,是不能使用Session的,Session會消耗大量的內存。
Cookie是保存在客戶端的。不佔用服務器的資源。像baidu、Sina這樣的大型網站,通常都是使用Cookie來進行會話跟蹤。
從瀏覽器的支持上比較
若是瀏覽器禁用了Cookie,那麼Cookie是無用的了!
若是瀏覽器禁用了Cookie,Session能夠經過URL地址重寫來進行會話跟蹤。
從跨域名上比較
Cookie能夠設置domain屬性來實現跨域名
Session只在當前的域名內有效,不可跨域名
若是內容中有什麼不足,或者錯誤的地方,歡迎你們給我留言提出意見, 蟹蟹你們 !^_^
若是能幫到你的話,那就來關注我吧!(系列文章均會在公衆號第一時間更新)
在這裏的咱們素不相識,卻都在爲了本身的夢而努力 ❤一個堅持推送原創Java技術的公衆號:理想二旬不止