概述:会话是浏览器和服务器之间的屡次请求和响应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技术的公众号:理想二旬不止