概述:會話是瀏覽器和服務器之間的屢次請求和響應javascript
也就是說,從瀏覽器訪問服務器開始,到訪問服務器結束,瀏覽器關閉爲止的這段時間內容產生的屢次請求和響應,合起來叫作瀏覽器和服務器之間的一次會話html
實際上會話問題解決的仍是客戶端與服務器之間的通訊問題,經過一些會話技術,能夠將每一個用戶的數據以例如cookie/session的形式存儲,方便之後用戶訪問web資源的時候使用java
假定場景:A和B兩人在某個網上購物商場登錄帳號後,A買了一個HHKB的鍵盤,而B則購買了一把民謠吉他,這些信息都會被保存下來web
用途是:保存帳戶信息,登陸時詢問往後是否自動登陸,或者根據以前瀏覽,購買過的商品,分析用戶喜歡什麼類型的商品,作出精準推送算法
那麼能不能用咱們以前學過的 HttpServletRequest 對象和 ServletContext 對象來保存這些數據呢?答案是否認的數據庫
不能用 HttpServletRequest的緣由:咱們的一次會話中,存在屢次請求和響應,而瀏覽器客戶端的每一次請求都會產生一個 HttpServletRequest 對象,它只會保存這次請求的信息,例如放入購物車與購買付款是不一樣的請求,很顯然數據沒有獲得很好的保存處理跨域
不能用 ServletContext 的緣由:ServletContext對象是被整個web應用所共享的,將數據都存到這裏,無疑會沒法區分具體信息的歸屬數組
客戶端會話技術 —— Cookie瀏覽器
服務器會話技術 —— Sessiontomcat
Cookies 能夠簡單的理解爲服務器暫存在你瀏覽器中的一些信息文件,它將你在網站上所輸入的一些內容,或者一些選項記錄下來,當下一次你訪問同一個網站的時候,服務器就會主動去查詢這個cookie資料,若是存在的話,將會根據其中的內容,提供一些特別的功能,例如記住帳號密碼等
總結一下就是:
瀏覽器訪問服務器,若是服務器須要記錄該用戶的狀態,就用response向瀏覽器發送一個cookie,瀏覽器會把Cookie保存起來。當瀏覽器再次訪問服務器的時候,瀏覽器會把請求的網址以及Cookie一同提交給服務器
Cookie大小上限爲4KB;
一個服務器最多在客戶端瀏覽器上保存20個Cookie;
一個瀏覽器最多保存300個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);
複製代碼
Session週期指的是不活動的時間,若是咱們設置Session是10s,在10s內,沒有訪問session,session中屬性失效,若是在9s的時候,你訪問了session,則會從新計時
若是重啓了tomcat,或者reload web應用,或者關機了,Session也會失效,咱們也能夠經過函數讓Session失效,invalidate()該方法是讓Session中的全部屬性失效,經常用於安全退出
若是你但願某個Session屬性失效,可使用方法removeAttribute
Cookie的生命週期就是按累積的時間來算的,無論用戶有沒有訪問過Session
問題:我再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技術的公衆號:理想二旬不止