持续更新中。。。
了解会话管理的概念和基本原理
-
为什么要进行会话管理?
Web应用程序基于HTTP协议
(1)HTTP基于请求/响应
模式,所有请求都是相互独立的、无连续性的
(2)HTTP是无连接
的协议,限制每次连接只处理一个请求
(3)HTTP是无状态
的协议,协议对于事务处理没有记忆能力 -
会话管理的产生:
(1)对于简单的页面浏览或信息获取,HTTP协议即可胜任(例如浏览、查看在线图书目录)
(2)对于需要客户端和服务器端多次交互的网络应用,则必须记住客户端状态(例如网上的购物车、用户登录) -
会话
就是 一个客户端连续不断地和服务器端进行请求/响应的一系列交互 -
多次请求建立关联的方式称为
会话管理或会话跟踪
会话状态: 指服务器与浏览器在会话过程中产生的状态信息。
(HTTPServlet有一个全局的context对象**(ServletContext context = getServletContext()、 context.setAttribute())**,可以把所有信息存起来,但像淘宝那么多用户,都存进去,太多了。所以不建议使用。) -
会话的实现过程:
使用Cookie、隐藏域、URL重写实现会话管理
- HTTP有两大部分:请求和响应
(1)请求:包含请求头、请求行、请求体(仅限于post)
(2)响应:响应头、响应行、响应体(响应正文) - 使用Cookie
所有HTTP消息,不管是请求还是响应均包含头信息。
(1)当服务器返回响应给客户端时,Servlet容器把会话的信息添加到响应头信息中
(2)客户端浏览器接收到响应后提取头信息,并将其存储在本地机中,以后发送请求时会自动将该信息带回服务器端
浏览器存储在客户端机器上的头信息
称作Cookie, 它以**属性名=属性值;…**方式组成文本信息。 - 写入(1)创建Cookie对象:调用Cookie的构造方法,给出Cookie的名称和Cookie的值,二者都是字符串。上例中为
Cookie c = new Cookie("userName", "a1234")
(2)设置最大时效,如果要告诉浏览器将Cookie存储到磁盘上,而非仅保存在内存中,使用setMaxAge
方法(参数为秒数)。c.setMaxAge(60*60*24*7)//一周
(3)将Cookie放入到HTTP响应中。(如果没有这一步,不会有任何Cookie被送到浏览器)response.addCookie(c);
- 读取cookie:调用
request,getCookies
(得到Cookie对象组成的数组,)
举个例子:(上述图片上的基本操作)
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.smartcardio.TerminalFactorySpi;
import javax.xml.ws.Response;
@WebServlet("/c1")
public class CookieDemo1 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//1.创建Cookie对象
Cookie cookie1 = new Cookie("username", "xhx&");
//2.设置最大时效
cookie1.setMaxAge(60*60*24);
//3.添加到响应中
resp.addCookie(cookie1);
Cookie cookie2 = new Cookie("password", "123");
cookie2.setMaxAge(60*60*24);
resp.addCookie(cookie2);
PrintWriter writer = resp.getWriter();
writer.write("cookie, first!");
}
}
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* 获取cookie
* */
@WebServlet("/c2")
public class CookieDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
Cookie[] cookies = req.getCookies();
PrintWriter out = resp.getWriter();
if (cookies != null) {
System.out.println(cookies.length);
out.write("cookie的信息:" + "<br>");
for (Cookie cookie : cookies) {
out.write(cookie.getName() + ":" + cookie.getValue());
}
}
}
}
效果如下:
下面展示一个Cookie当一个计数器的栗子。
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.sun.jndi.url.iiopname.iiopnameURLContextFactory;
/*
* 如果第一次访问,创建Cookie,访问次数设为1
* 不是第一次,取出Cookie的值,次数+1
* */
@WebServlet("/CookieCounter")
public class CookieCounter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.设置响应编码
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
//2.获取请求
Cookie[] cookies = req.getCookies();
if (cookies == null) { // 第一次访问
out.println("这是第一次访问");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd/hh:mm");
Cookie c1 = new Cookie("date", sdf.format(new Date()));
c1.setMaxAge(60 * 60);
resp.addCookie(c1);
Cookie c2 = new Cookie("count", "1"); // value值只能是字符串!
c2.setMaxAge(60 * 60);
resp.addCookie(c2);
} else {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("date")) {
out.write("第一次访问的时间: " + cookies[i].getValue() + "<br>");
} else { // 次数加一
String counter = cookies[i].getValue();
int count = Integer.parseInt(counter);
count++;
out.write("第" + count + "次访问" + "<br>");
// 第一种方法:重写一个cookie代替
Cookie new_c2 = new Cookie("count", String.valueOf(count));
new_c2.setMaxAge(60 * 60);
resp.addCookie(new_c2);
// 第二种,setValue
// cookies[i].setValue(String.valueOf(count));
/* 注意只有setValue没有setName() */
// resp.addCookie(cookies[i]);
}
}
}
}
}
效果:
【tips:可能出现(java.lang.NumberFormatException: For input string: “xhx&”)类似这种报错:解决办法:清空浏览器的cookie数据。(在设置中)】
-
Cookie的优缺点:
优点:
(1)可配置到期规则,数据可持久保存
(2)不需要服务器资源,数据保存在客户端
(3)简单性,基于文本的Key-Value对
缺点:
(1)大小受到限制(总数:300; 站点:20; Cookie:4KB)
(2)用户可禁用客户端接收Cookie的功能
(3)潜在的安全风险。 -
使用隐藏的表单
(1)思想 :通过使用隐藏域,由浏览器主动告知服务器多次请求间必要的信息,如:在线问卷作答。
例如下图:
(2)优缺点:
1)优点:Cookie被禁用或者根本不支持的情况下一九能够工作
2)缺点:关掉网页后会遗失先前的请求结果;所有的页面必须是表单提交之后的结果。
使用URL重写
(1)思想:当服务器响应浏览器上一次请求时,将某些相关信息以超链接方式
响应给浏览器,超链接中包括请求参数信息。
例如:
举一个简单的例子:点击Servlet1中的链接,会跳转到Servlet2并且获得URL中的相关参数
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/Servlet1")
public class Servlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//重新编码URL
String path = response.encodeURL("/xueli02/Servlet2?name=xhx&passwd=123456");
PrintWriter out = response.getWriter();
out.write("<a href="+path+">click here</a>");
out.flush();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/Servlet2")
public class Servlet2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.write(request.getParameter("name"));
out.flush();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
练习:尝试实现三天免登录的功能:(但仅仅有这些代码,还不足以实现这个功能,因此在此只做一个记录。若有朝一日我能把这个写出来,再来此补充。(如果有大佬指点,那就更好了。。呜呜呜,菜死了))
一:LoginServlet.java:
如果是用户名admin,密码是123456,
(1)并且勾选自动登录,创建Cookie,存储用户名,有效期为3天。
(2)未勾选,不创建Cookie,请求转发到UserServlet.java
如果用户名密码错误,重定向到login.html
二:UserServlet.java:
如果用户名为空,重定向到login.html
如果不为空,显示用户名
三:IndexServlet.java:
获取Cookie,如果包含user:admin,跳转到UserServlet.java
否则,重定向到login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="uname"><br>
密码:<input type="text" name="upwd"><br>
自动登录:<input type="checkbox" name="is_login" value="auto"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
package cookie;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username=request.getParameter("uname");
String password =request.getParameter("upwd");
if(username.equals("admin")&&password.equals("123456")) {
String flag = request.getParameter("is_login");
if(flag.equals("auto")) {
Cookie cookie = new Cookie("name", username);
cookie.setMaxAge(60*60*24*3);
response.addCookie(cookie);
}
request.setAttribute("username", username);
request.getRequestDispatcher("user").forward(request, response);
}else {
response.sendRedirect("login.html");
}
}
}
package cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/UserServlet")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(request.getAttribute("username")==null) {
response.sendRedirect("login.html");
}else {
PrintWriter writer = response.getWriter();
writer.write(""+request.getAttribute("username"));
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
package cookie;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class IndexServlet
*/
@WebServlet("/IndexServlet")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
if(cookies!=null) {
for(Cookie cookie:cookies) {
String name=cookie.getName();
String value=cookie.getValue();
if(name.equals("name") && value.equals("admin")) {
request.setAttribute("username", value);
request.getRequestDispatcher("user").forward(request, response);
}
}
}
response.sendRedirect("login.html");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
Session会话管理的原理和技术实现
-
在Servlet中进行会话管理,可以使用
HttpServletRequest的getSession()方法
获取HttpSession对象(简称为Session),通过设置/获取服务器端Session对象的属性,来保留请求间的相关信息。如下图所示:
-
Servlet容器提供Session接口来代表服务器端和客户端的会话
当一个WEB服务器为客户端开始一个会话时,创建一个Session对(含有特殊ID,称为Session ID,默认用Cookie存放在浏览器中。在Tomacat中,Cookie的名称为JSESSIONID)临时存储,浏览器关闭就失效了
Session将数据存储在服务器的内存
中,供以后来自同一个客户端的请求使用。
第二次请求中(接下来的多次请求),请求头会附带JSESSIONID,服务器接收到请求后,调用相应的Servlet处理,同时跟据JSESSIONID,返回对应的Session对象。举一个例子:
package hebu.couse.xxx;
import java.io.IOException;
import javax.security.auth.message.callback.PrivateKeyCallback.Request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/SessionDemo1")
public class SessionDemo1 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
System.out.println(session.getId());
session.setAttribute("name", "xhx");
resp.sendRedirect("SessionDemo2");
}
}
package hebu.couse.xxx;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/SessionDemo2")
public class SessionDemo2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
System.out.println("2----"+session.getId());
response.getWriter().write((String)session.getAttribute("name"));
}
}
打印出来内容如下:
补充:如果session.getAttribute(“name”)不存在,返回null
举个例子:
当第一次输入用户名的时候,页面显示:这是第一次登录,及用户名。
后面访问,就直接显示用户名
package hebu.couse.xxx;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/Login")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置请求编码方式
request.setCharacterEncoding("utf-8");
//2.设置响应的编码方式:
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
//3.获取请求信息
String user_name = request.getParameter("uname");
//4.处理请求
if(user_name.equals("yyy")) {
HttpSession session = request.getSession();
System.out.println(session.getId());
String name=(String)session.getAttribute("user");
if(name == null){//第一次访问为空
writer.write("这是第一次访问");
writer.write("<h1>"+user_name+"</h1>");
session.setAttribute("user", user_name);
//session.setMaxInactiveInterval(60*60);
//在1小时之内,当前站点没有被访问,session对象就销毁了
//session.invalidate();//强制session失效,多用于退出
} else {
writer.write("<h1>"+user_name+"</h1>");
}
}
//5.返回响应
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="Login" method="post">
用户名:<input type="text" name="uname"><br>
密码:<input type="text" name="upwd"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
上述例子,可以发现,每次打印出来的ID是一样的。
我们还可以session.invalidate();//强制session失效,多用于退出
使用时机:
登录,把用户的信息保存在session中,供这个用户的其他请求使用。
总结:
Session解决了一个用户的不同请求的数据共享问题,只要JSSESSIONID不失效(浏览器关闭)和Session对象不失效(超时)
当前用户的对于该站点的任意Servlet请求在处理时都能获取同一个 session的对象。