一. 会话技术
1. 什么是会话?
会话可简单理解为:
用户开一个浏览器,点击多个超链接,访问服务器多个web资源,
然后关闭浏览器,整个过程称之为一个会话。
2. 会话过程中要解决的一些问题?
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。
例如:
用户点击超链接通过一个servlet购买了一个商品,程序应该想办法保存用户购买的商品,
以便于用户点结帐servlet时,结帐servlet可以得到用户购买的商品为用户结帐。
思考:用户购买的商品保存在request或servletContext中行不行?
request --> 作用域的范围太小(每次选择商品存一下,下次请求又不知道存了啥)
servletContext --> 作用域的范围太大(选择商品存一下,多个用户的数据混乱了)
3. 保存会话数据的两种技术:
Cookie(小饼干)
Cookie是客户端技术,程序把每个用户的数据以cookie的形式写给用户各自的浏览器。
当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。
这样,web资源处理的就是用户各自的数据了。
Session
Session是服务器端技术,利用这个技术,
服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,
由于session为用户浏览器独享,所以用户在访问服务器的web资源时,
可以把各自的数据放在各自的session中,
当用户再去访问服务器中的其它web资源时,
其它web资源再从用户各自的session中取出数据为用户服务。
(--> 不同的浏览器会存在服务器的不同位置上)
二. Cookie技术
1. Cookie的简单入门
response.setHeader("Set-Cookie", "name=bingxiang");
--> 这样拼接头信息太麻烦,Sun公司专门提供了一套API,便于Cookie的处理。
Date date = new Date();
Cookie c = new Cookie("lastTime",date.getTime()+"");
c.setMaxAge(3600*24*30);
c.setPath(request.getContextPath());// --> 这个是给浏览器用的, 所以要给上web应用名称。
//c.setDomain(".baidu.com");// --> 设置之后, 浏览器会拒绝接收该Cookie.
response.addCookie(c);
浏览器最终拼接的信息 --> Cookie:name=bingxiang; lastTime=1517844967488
2. 遍历Cookie的方法(注意判断非空)
response.setContentType("text/html;charset=utf-8");
Cookie[] cs = request.getCookies();
Cookie findC = null;
if(cs != null){ // --> 这句话很重要
for(Cookie c: cs){
if("lastTime".equals(c.getName())){
findC = c;
}
}
}
if(findC==null){
response.getWriter().write("您是第一次访问本网站");
}else{
String lastTime = findC.getValue();
response.getWriter().write("您上次访问本网站的时间是: " + lastTime);
}
3. Cookie的详细介绍
Cookie是基于set-Cookie响应头和Cookie请求头工作的,
服务器可以发送set-Cookie请求头命令浏览器保存一个cookie信息,
浏览器会在访问服务器时以Cookie请求头的方式带回之前保存的信息
request.getCookies();
response.addCookie(Cookie c);
// --> Cookie在构造的时候就需要设定好cookie的名字和值
new Cookie(String name,String value);
Cookie对象的方法:
--> 1. getName();
--> 2. getValue();
--> 3. setValue();
--> 4. setMaxAge与getMaxAge方法
-- 一个Cookie如果没有设置过MaxAge则这个Cookie是一个会话级别的Cookie,
这个Cookie信息打给浏览器后浏览器会将它保存在浏览器的内存中,
这意味着只要浏览器已关闭随着浏览器内存的销毁Cookie信息也就消失了.
-- 一个Cookie也可以设置MaxAge,浏览一一旦发现收到的Cookie被设置了MaxAge,
则会将这个Cookie信息以文件的形式保存在浏览器的临时文件夹中(不再是),
保存到指定的时间到来位置.这样一来即使多次开关浏览器,
由于这些浏览器都能在临时文件夹中看到cookie文件,
所以在cookie失效之前cookie信息都存在.
-- 想要命令浏览器删除一个Cookie,
发送一个同名同path的cookie,maxage设置为0,
浏览器以'名字+path'识别cookie,
发现同名同path,cookie覆盖后立即超时被删除,从而就删除了cookie.
--> 5. setPath与getPath方法
-- 用来通知浏览器在访问服务器中的哪个路径及其子路径时带着当前cookie信息过来,
如果不明确设置,则默认的路径是发送Cookie的Servlet所在的路径。
http://localhost/Day05/servlet/...(而不是http://localhost/Day05/servlet/ServletDemo1)
--> 6. setDomain与getDomain方法
-- 用来通知浏览器在访问哪个域名的时候带着当前的cookie信息.
但是要注意,
现代的浏览器一旦发现cookie设置过domain信息,
则会拒绝接受这个Cookie.我们平常不要设置这个方法
4. Cookie的具体案例
--- 显示用户上次浏览过的书。
1. Book的javaBean类
2. BookDao的数据接口类。
3. ServletDemo2 --> 列出当前看过书的Servlet
具体代码:
response.setContentType("text/html;charset=utf-8");
//1. 查询数据库中所有的书, 进行展示
Map<String,Book> map = BookDao.getBooks();
for(Map.Entry<String , Book> entry : map.entrySet()){
Book book = entry.getValue();
response.getWriter().write("<a href='"+request.getContextPath()+"/servlet/ServletDemo3?id="+book.getId()+"'>"+book.getName()+"</a><br>");
}
response.getWriter().write("<hr>");
//2.显示之前看过的书
Cookie [] cs = request.getCookies();
Cookie findC = null;
if(cs!=null){
for(Cookie c : cs){
if("last".equals(c.getName())){
findC = c;
}
}
}
if(findC == null){
response.getWriter().write("没有看过任何书!");
}else{
response.getWriter().write("您曾经浏览过的书:<br>");
String[] ids = findC.getValue().split(",");
for(String id : ids){
Book book = BookDao.getBook(id);
response.getWriter().write(book.getName()+"<br>");
}
}
4. ServletDemo3 --> 展示选中书的详细信息,并把当前书的id纪录到cookie中
具体代码:
response.setContentType("text/html;charset=utf-8");
//1.获取要看的书的id,查询数据库找出书,输出书的详细信息
String id = request.getParameter("id");
Book book = BookDao.getBook(id);
if(book==null){
response.getWriter().write("找不到这本书!");
return;
}else{
response.getWriter().write("<h1>书名:"+book.getName()+"</h1>");
response.getWriter().write("<h3>作者:"+book.getAuth()+"</h3>");
response.getWriter().write("<h3>售价:"+book.getPrice()+"</h3>");
response.getWriter().write("<h3>出版社:"+book.getPublish()+"</h3>");
response.getWriter().write("<h3>描述信息:"+book.getDescription()+"</h3>");
}
//2.发送cookie保存最后看过的书
// 什么都没看过 --- 1 --> 1
// 看过1 -- 2,1 --> 2,1
// 看过2,1--3,2,1 --> 3,2,1
// 看过3,2,1 -- 4,3,2 --> 4,3,2
// 看过4,3,2 --3,4,2 --> 3,4,2
String ids = "";
Cookie [] cs = request.getCookies();
Cookie findC = null;
if(cs!=null){
for(Cookie c : cs){
if("last".equals(c.getName())){
findC = c;
}
}
}
// ---> 算法
// 当前看的直接加到结果中,
// 再遍历之前的cookie,
// 如果历史纪录中的第一个和当前没有重复就添加到结果的后面,如果一样就不添加了
// 因为每次都是添加一个,所以只要和当前那本书不一样都可以添加,
if(findC == null){
//说明之前没有看过书的记录
ids += book.getId();
}else{
//说明之前有历史看过的书的记录,需要根据历史记录算一个新的记录出来
String [] olds = findC.getValue().split(",");
StringBuffer buffer = new StringBuffer();
buffer.append(book.getId()+","); // --> 添加当前看的书
for(int i = 0;i<olds.length && buffer.toString().split(",").length<3 ;i++){
String old = olds[i];
if(!old.equals(book.getId())){
buffer.append(old+",");
}
}
ids = buffer.substring(0, buffer.length()-1); // --> 删除第一个','号
}
Cookie lastC = new Cookie("last",ids);
lastC.setMaxAge(3600*24*30);
lastC.setPath(request.getContextPath());
response.addCookie(lastC);
5. Cookie的细节
1. 一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
2. 一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
4. 如果创建了一个cookie,并将他发送到浏览器,
默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。
若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。
将最大时效设为0则是命令浏览器删除该cookie。
注意,删除cookie时,path必须一致,否则不会删除(浏览器通过cookie的name+path来标识一个cookie)
5. 浏览器会用domain+path+name区分一个cookie(并且拒收第三方cookie)。而服务器以对象来区分cookie。
三. Session技术
1. session概述
1. 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),
注意:一个浏览器独占一个session对象(默认情况下)。
因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session(内存空间)中,
当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
2. session是一个域对象,作用范围为整个会话。
3. Session和Cookie的主要区别在于:
Cookie是把用户的数据写给用户的浏览器。
Session技术把用户的数据写到用户独占的session中。
2. session的生命周期
1. Session对象由服务器创建,
开发人员可以调用request对象的getSession()方法得到session对象,
如果服务器发现内存中没有该浏览器对应的session就会创建该session并返回,
如果已经存在则直接返回已有的session。
2. session并不会一直存在在内存中,
经过一段时间如果无人使用session将会被销毁,
一般这个时间为30分钟,
可以在web.xml中配置<session-config>设置该时间值。
3. 调用session的invalidate()也可以销毁session。
4. 如果服务器是正常关闭,还未超时的session会被以文件的形式保存在服务器的work目录下,这个过程叫做session的钝化.
下次再正常启动服务器时,钝化着的session会被恢复到内存中,这个过程叫做session的活化.
5. 作用:在会话范围内共享数据
3. session实现原理
服务器是如何实现一个session为一个用户浏览器服务的?
--> Session是基于Cookie的
--> 名字为JSESSIONID的Cookie的值是Session的唯一标识。
4. Session的案例(购物)
1. ServletDemo4 --> 购物车的Servlet
具体代码:
String prod = request.getParameter("prod");
prod = new String(prod.getBytes("iso8859-1"),"utf-8");
//为了保证下次打开浏览器的时候还知道购物车里面的商品,
//那么就要重新设置session对应的Cookie的最大生命范围,
//request.getSession().setAttribute("prod", prod);// 这样只能适用于一次会话范围
HttpSession session = request.getSession();
Cookie jc = new Cookie("JSESSIONID",session.getId()); // --> 特殊名字的Cookie
jc.setPath(request.getContextPath()); // --> 当前Web应用的名称
jc.setMaxAge(1800); // --> 30分钟就足够了
response.addCookie(jc);
session.setAttribute("prod", prod); // --> 存数据
2. ServletDemo5 --> 支付的Servlet
具体代码:
response.setContentType("text/html;charset=utf-8");
HttpSession session = request.getSession();
String prod = (String) session.getAttribute("prod");
response.getWriter().write("您购买的是"+prod+"价值99999999999元");
3. prodList.jsp --> 商品页面
具体代码:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" session="false"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<%
request.getSession();
String url1 = request.getContextPath()+"/servlet/ServletDemo4?prod=电视机";
url1 = response.encodeURL(url1);
String url2 = request.getContextPath()+"/servlet/ServletDemo4?prod=冰箱";
url2 = response.encodeURL(url2);
String url3 = request.getContextPath()+"/servlet/ServletDemo5";
url3 = response.encodeURL(url3);
%>
<a href="<%= url1 %>">电视机</a>
<a href="<%= url2 %>">冰箱</a>
<a href="<%= url3 %>">结账</a>
</body>
</html>
5. session 的原理:
request.getSession()方法会检查请求中有没有名字为'JSESSIONID'这个cookie,
如果没有则检查请求的URL后有没有以参数的形式带着JSESSIONID过来,
如果还找不到则认为这个浏览器没有对应的Session,
创建一个Session然后再在响应中添加JSESSIONID cookie,值就是这个Session的id。
默认情况下,
JSESSIONID 的path为当前web应用的名称,并且没有设置过MaxAge,是一个会话级别的cookie.
这意味着一旦关闭浏览器再新开浏览器时,由于JSESSIONID丢失,会找不到之前的Session。
我们可以手动的发送JSESSIONID cookie,名字和path设置的和自动发送时一样,
但是设置一下MaxAge,使浏览器除了在内存中保存JSESSIONID信息以外还在临时文件夹中以文件的形式保存,
这样即使重开浏览器仍然可以使用之前的session.
6. URL重写 ( 一般不常用 )
(如果用户设置了不接收Cookie,导致应该存储在Cookie的数据没有保存)
(可以使用URL重写解决这个问题)
由于没有了JSESSIONID这个Cookie,导致现在用户的浏览器被标识出来了。
让网站上所有的超链接都拼接上JSESSIONID.
SUN公司提供了两个简单的方法:
1. response.encodeRedirectURL(java.lang.String url)
--> 用于对sendRedirect(重定向)方法后的url地址进行重写。
2. response.encodeURL(java.lang.String url)
--> 用于对表单action和超链接的url地址进行重写
例如:
request.getSession(); --> 必须先获取Session,不然都没有创建,那就取不到JSESSIONID。
String url3 = request.getContextPath()+"/servlet/ServletDemo5";
url3 = response.encodeURL(url3);
经过URL重写后变成了:
url3 --> /ServletDemo/servlet/ServletDemo5;jsessionid=E047C1D781EB0DD09172B13598A4FBE2
注意:
request.getSession() --在URL重写之前一定要先创建出Session,才有Session id,才能进行重写
response.encodeURL()--- 一般的地址都用这个方法重写
response.encodeRedirectURL() --- 如果地址是用来进行重定向的则使用这个方法
url重写的方法一旦发现浏览器带回了任意cookie信息,
则认为客户端没有禁用cookie, 就不会再进行重写操作.
7. Session案例:
7.1 用户登录注销
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CYv0hrnG-1619937127827)(img2/4_3.png)]
request.getSession(false) --> 传入了一个参数
–> 这样如果之前有Session就能返回这个Session
–> 没有的话就返回null
7.2 防止表单重复提交
前台校验 --> 是给用户一个友好的体验
后台检验 --> 才是真正的数据校验
1. 注册页面代码:
<%
// http://localhost:8080/ServletDemo/resubmit/index.jsp
Random r = new Random();
int valinum = r.nextInt();
session.setAttribute("valinum",valinum+"");
System.out.println("jsp_valinum: " + valinum);
%>
<form action="${pageContext.request.contextPath }/servlet/ResubServlet" method="POST">
用户名:<input type="text" name="username"/>
<input type="hidden" name="valinum" value="<%=valinum %>"/>
<input type="submit" value="注册"/>
</form>
2. 后台校验代码:
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
try {
Thread.sleep(4*1000); // 模拟网络延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String username = request.getParameter("username");
//获取传过来的验证随机数
String valinum = request.getParameter("valinum");
//获取存在Session中存储的验证随机数
String valinum2 = (String) request.getSession().getAttribute("valinum");
// 表单提交两次得到的验证随机数应该是一样的
// 但是注册一次后,就将Session中的验证数变为了null
// 此时两个验证随机数数不一致了,就说明重复注册了。
System.out.println("param_valinum: " + valinum);
System.out.println("session_valinum2: " + valinum2);
if(valinum2 != null && !"".equals(valinum2) && valinum.equals(valinum2)){
request.getSession().removeAttribute("valinum"); // --> 将验证随机数清除
System.out.println("向数据库中注册一次:"+username);
}else{
response.getWriter().write("from web:不要重复提交!!");
}
7.3 实现一次性验证码
四. Cookie和Session总结
cookie是客户端技术
数据保存在客户端, 这个信息可以保存很长时间
数据随时有可能被清空, 所以cookie保存的数据是不太靠谱的
数据被保存在了客户端, 随时有可能被人看走,
如果将一些敏感信息比如用户名密码等信息存在cookie中, 可能有安全问题。
session是服务器端技术
数据保存在服务区端, 相对来说比较稳定和安全,
占用服务器内存, 所以一般存活的时间不会太长,超过超时时间就会被销毁.
我们要根据服务器的压力和session的使用情况合理设置session的超时时间,
既能保证session的存活时间够用, 同时不用的session可以及时销毁减少对服务器内存的占用.
备注
1. 注意:即使禁止了Cookie,浏览器对localhost是不阻止的,要想试验就要用127.0.0.1进行。
2. response.encodeURL(url3); --> 这个方法非常的智能
--> 他会检查服务器有没有带JSESSIONID这个Cookie,
--> 如果带了就不重新URL,没带JSESSIONID就重写一下。
3. Session什么时候会销毁?
1. --> 30分钟到了
2. --> 调用session的invalidate()方法
3. --> 服务器非正常关闭,没保存上。(正常关闭服务器,Cookie会被保存成文件)