【会话技术基础】
会话:一次会话中包含多次请求和响应。
一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止。
- 功能:在一次会话的范围内的多次请求间,共享数据
- 方式:
- 客户端会话技术:Cookie
- 服务器端会话技术:Session
一、 客户端会话技术:Cookie
概念:客户端会话技术,将数据保存到客户端
1. Cookie 快速入门
-
使用步骤:
- 创建 Cookie 对象,绑定数据:
new Cookie(String name,String value)
- 发送 Cookie 对象:
response.addCookie(Cookie cookie)
- 获取 Cookie 对象,拿到数据:
Cookie[] request.getCookies()
- 创建 Cookie 对象,绑定数据:
-
为方便创建 Servlet 实现类,在 IDEA 中修改代码模板,按下图方式操作
@WebServlet("/cookieTest01")
public class CookieTest01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建 Cookie 对象
Cookie cookie = new Cookie("msg", "Hello");
// 2. 发送 Cookie 对象
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
@WebServlet( "/cookieTest02")
public class CookieTest02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 3. 获取 Cookie 对象
Cookie[] cookies = request.getCookies();
// 获取数据,遍历 Cookies
if (cookies!=null){
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name+":"+value);
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
2. Cookie 实现原理
- 基于响应头 set-cookie 和请求头 cookie 实现
过程解释(以上述代码为例):
- 有客户端浏览器和服务器,在服务器中编写了两个资源 CookieTest01(发送 Cookie ) 和 CookieTest02(获取 Cookie );
- 此时客户端发送请求 CookieTest01 的资源,服务器做出响应,发送 Cookie 给客户端(此时 Cookie 中保存的数据是
msg:Hello
); - 响应的详细过程: Response 对象设置了一个响应头:
set-cookie:msg=Hello
(键值对形式) ; - 客户端将响应头携带的数据(
msg=Hello
)保存到客户端浏览器上,等到下一次发送请求时,客户端会将这个数据带到服务器; - 第二次请求的详细过程: Request 对象设施一个请求头:
cookie:msg=Hello
(键值对形式),在服务器中可以获取请求头中的数据,一般使用 JavaWeb 封装好的 API 进行操作
3. Cookie 的使用
- 一次可以发送多个 Cookie 对象
- Cookie 对象的存活时间
- Cookie 对象存储中文数据
- Cookie 对象的获取范围(数据共享)
(1) 一次发送多个 Cookie 对象
可以创建多个 Cookie 对象,使用 Response 对象调用多次
addCookie()
方法发送 Cookie 对象即可
@WebServlet( "/cookieTest03")
public class CookieTest03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie cookie01 = new Cookie("msg", "Hello");
Cookie cookie02 = new Cookie("name", "John");
response.addCookie(cookie01);
response.addCookie(cookie02);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(2)Cookie 对象的存活时间
- 默认情况下,当浏览器关闭后,Cookie 数据被销毁
- 持久化存储:
setMaxAgent(int seconds)
- 正数:将 Cookie 数据写到硬盘的文件中,进行持久化存储,传递的参数就是设置 Cookie 存活的时间
- 负数:默认值
- 零:删除 Cookie 信息
@WebServlet( "/cookieTest04")
public class CookieTest04 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建 Cookie 对象
Cookie cookie = new Cookie("msg", "setMaxAge()");
// 设置 Cookie 的存活时间
cookie.setMaxAge(30); // 将 Cookie 持久化到硬盘,30秒后就会自动删除 Cookie 文件
// 发送 Cookie
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3)Cookie 对象存储中文数据
- 在 Tomcat 8 之前 Cookie 中不能直接存储中文数据
- 需要将中文数据转码,但是一般采用 URL 编码(%E3)
- 在 Tomcat 8 之后 Cookie 中支持存储中文数据,但是特殊字符还是不支持,建议使用 URL 编码存储,URL 解码解析
(4)Cookie 对象的获取范围(数据共享)
- 在一个 Tomcat 服务器中,部署了多个 Web 项目,这些 Web 项目中 Cookie 在默认情况下不能共享数据
setPath(String path)
:设置 Cookie 的获取范围,默认情况下为设置的当前的虚拟目录。如果要进行数据共享。则可以将path
设置为"/"
@WebServlet("/cookieTest05")
public class CookieTest05 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie cookie = new Cookie("msg", "你好!");
// 设置 path ,让当前的服务器下的部署的所有项目共享 Cookie 信息
cookie.setPath("/");
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
- 不同的 Tomcat 服务器(不同的服务器)间的 Cookie 共享:
setDomain(String path)
:如果设置一级域名相同,那么多个服务器之间的 Cookie 可以共享数据(例如:setDomain(".baidu.com")
那么tieba.baidu.com
和news.baidu.com
中的 Cookie 就可以共享)
4. Cookie 的特点和作用
-
特点:
- Cookie 存储数据在客户端浏览器
- 浏览器对于单个 Cookie 的大小有限制(4kb)以及对同一个域名下的总 Cookie 数量也有限制(20个)
-
作用:
- Cookie 一般用于存储少量的不太敏感的数据
- 在不登陆的情况下,完成服务器对客户端的身份识别
5. 案例:记住上次访问时间
需求:
- 访问一个 Servlet 实现类,如果是第一次访问,则提示:您好,欢迎首次访问
- 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串
分析:
- 可以采用 Cookie 来完成
- 在服务器中创建 Servlet 实现类,该类中创建一个名为 lastTime 的 Cookie ,之后访问时判断浏览器中是否有这个 Cookie
- 存在:不是第一次访问
- 响应数据:欢迎回来,您上次访问的时间为:具体时间值
- 写回 Cookie :lastTime = 当前具体时间
- 不存在:是第一次访问
- 响应数据:您好,欢迎您首次访问
- 写回 Cookie :lastTime = 当前具体时间
- 存在:不是第一次访问
@WebServlet( "/cookieTest06")
public class CookieTest06 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置相应的消息体的数据格式以及编码
response.setContentType("text/html;charset=utf-8");
// 获取所有 Cookie
Cookie[] cookies = request.getCookies();
boolean flag=false; // 没有 Cookie 为 lastTime
// 遍历 Cookie 数组
if (cookies!=null&&cookies.length>0){
for (Cookie cookie : cookies) {
// 获取 Cookie 的名称
String name = cookie.getName();
// 判断名称是否是:lastTime
if ("lastTime".equals(name)){
// 有该 Cookie,不是第一次访问
flag=true; // 有 lastTime 的 Cookie
// 设置 Cookie 的 value
// 获取当前时间的字符串,重新设置 Cookie 的值,重新发送 Cookie
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = simpleDateFormat.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); // 一天
response.addCookie(cookie);
// 响应数据
// 获取 Cookie 的 value、时间
String value = cookie.getValue();
System.out.println("解码前:"+value);
// URL 解码
value= URLDecoder.decode(value,"utf-8");
System.out.println("解码后:"+value);
response.getWriter().write("<h1>欢迎回来,您上次访问时间为:"+value+"</h1>");
break;
}
}
}
if (cookies==null||cookies.length==0||flag==false){
// 没有,第一次访问
// 设置 Cookie 的 value
// 获取当前时间的字符串,重新设置 Cookie 的值,重新发送 Cookie
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date = simpleDateFormat.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); // 一天
response.addCookie(cookie);
response.getWriter().write("<h1>您好,欢迎您首次访问!</h1>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
二、 服务器端会话技术:Session
概念:服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中,使用 HttpSession 对象。
1. Session 快速入门
- 获取 HttpSession 对象:
HttpSession session = request.getSession();
- 使用 HttpSession 对象:
Object getAttribute(String name)
void setAttribute(String name,Object value)
void removeAttribute(String name)
@WebServlet("/sessionTest01")
public class SessionTest01 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 使用 Session 共享数据
// 获取 Session 对象
HttpSession session = request.getSession();
// 存储数据
session.setAttribute("msg","Hello,Session!");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
@WebServlet("/sessionTest02")
public class SessionTest02 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = request.getSession();
// 获取数据
Object msg = session.getAttribute("msg");
System.out.println(msg);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
2. Session 实现原理
服务器是如何确保在一次会话范围内,多次获取的 Session 对象使用一个的?
Session 的实现是依赖于 Cookie 的!(Cookie 是依赖于请求头和响应头实现的)
- 在服务器中,第一次获取 Session 时,没有 Cookie ,此时会在内存中创建一个新的 Session 对象,并且该 Session 对象具有一个唯一的 ID
- 当浏览器发送请求,服务器做出响应的时候,服务器会发送一个响应头:
set-cookie:JSESSIONID=ID 值
(set-cookie
头) - 之后客户端浏览器就将
set-cookie
头的信息存储到浏览器中 - 当浏览器访问该项目中的其他资源的时候就会携带着该
set-cookie
头的数据,通过一个叫做cookie
的请求头携带 - 之后请求头就会去内存中查找是否有和
cookie
信息中 ID 吻合的 Session 对象 - 之后服务器再根据
request.getSession()
方法找到该 Session 对象
3. Session 的使用
- 当客户端关闭后,服务器不关闭,两次获取的 Session 对象默认情况下,两次获取的 Session 对象不是同一个
- 如果需要相同,则可以创建 Cookie 对象,键名为 JSESSIONID ,设置最大存活时间,让 Cookie 持久化存储
@WebServlet("/sessionTest03")
public class SessionTest03 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取 Session 对象
HttpSession session = request.getSession();
System.out.println(session);
// 期望浏览器关闭后, Session 对象也能相同
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(60*60); // 设置存活时间为一个小时
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
-
当客户端不关闭,服务器关闭后,两次获取的 Session 对象不是同一个
- 但是如果要确保数据不丢失:(Tomcat 服务器可以自动做这两个过程,但是 IDEA 不能实现这两个过程(IDEA 只会实现钝化,无法活化),需要手动在本地的 Tomcat 服务器操作)
- Session 的钝化:在服务器正常关闭之前,将 Session 对象序列化到硬盘上
- Session 的活化:在服务器启动后,将 Session 文件转化为内存中的 Session 对象即可
- Tomcat 本地操作实现这两个过程:
将如图三个文件打成 war 包放在:\webapps
目录下,正常手动启动 Tomcat 服务器,项目就会被部署,正常手动关闭 Tomcat 服务器,Tomcat 服务器就会自动将 Session 对象序列化到硬盘上,在目录work\Catalina\localhost
中(SESSIONS.ser
文件,里面存储的就是 Session 对象),一旦服务器被再次启动,文件将会被自动读取,该文件被删除掉,Session 对象就会被还原到内存中,虽然地址值不同,但是数据和 ID 值都保持与原来一致
- 但是如果要确保数据不丢失:(Tomcat 服务器可以自动做这两个过程,但是 IDEA 不能实现这两个过程(IDEA 只会实现钝化,无法活化),需要手动在本地的 Tomcat 服务器操作)
-
Session 对象的失效时间(销毁)
- 服务器关闭
- Session 对象调用
invalidate()
方法 - Session 对象的默认失效时间为:30分钟(在
\conf\web.xml
文件中)
可以之后设置覆盖该设置
<session-config>
<session-timeout>30</session-timeout>
</session-config>
4. Session 的特点
- Session 用于存储一次会话的多次请求的数据,存储在服务器端
- Session 可以存储任意类型,任意大小的数据
- Session 与 Cookie 的区别:
- Session 存储数据在服务器端,Cookie 存储数据在客户端
- Session 没有数据大小限制,Cookie 有数据大小限制
- Session 数据安全,Cookie 相对于不安全
5. 案例:验证码
-
需求:
- 访问带有验证码的登录界面
login.jsp
- 用户输入用户名,密码以及验证码
- 如果用户名和密码有误,跳转到登录界面,提示:用户名或者密码错误!
- 如果验证码输入有误,跳转到登录界面,提示:验证码错误!
- 如果全部输入正确,则跳转到主页
success.jsp
,显示:用户名,欢迎您!
- 访问带有验证码的登录界面
-
分析:
- 设置 Request 对象的编码
- 获取参数 Map 集合
- 获取验证码
- 将用户信息封装到 User 对象中
- 判断程序生成的验证码和用户输入的验证码是否一致,从 Session 对象中获取程序生成的验证码
- 一致:再判断用户名和密码是否正确:(需要连接数据库,编写 UserDao 类判断)
- 正确:登录成功
- 使用 Session 对象存储数据
- 使用重定向跳转到
success.jsp
页面 - 不正确:给出提示信息,跳转到登录界面
- 不一致:
- 使用 Request 对象给用户提示信息:验证码错误
- 之后使用转发跳转登录界面
- 一致:再判断用户名和密码是否正确:(需要连接数据库,编写 UserDao 类判断)
- 在 Request 域对象中获取数据,在
login.jsp
页面中显示错误信息
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script>
window.οnlοad=function () {
document.getElementById("img").οnclick=function () {
this.src="/cookie_test/checkCodeServlet?time="+new Date().getTime();
}
}
</script>
<style>
div{
color: red;
}
</style>
</head>
<body>
<form action="/cookie_test/loginServlet" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>验证码</td>
<td><input type="text" name="checkCode"></td>
</tr>
<tr>
<td colspan="2"><img id="img" src="/cookie_test/checkCodeServlet"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="登录"></td>
</tr>
</table>
</form>
<div><%= request.getAttribute("cc_error")==null?"":request.getAttribute("cc_error") %></div>
<div><%= request.getAttribute("login_error")==null?"":request.getAttribute("login_error") %></div>
</body>
</html>
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width=100;
int height=50;
// 创建一个对象,在内存中的验证码图片对象
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 美化图片
// 填充背景色
Graphics graphics = image.getGraphics(); // 画笔对象
graphics.setColor(Color.CYAN); // 设置画笔颜色
graphics.fillRect(0,0,width,height);
// 画边框
graphics.setColor(Color.BLUE);
graphics.drawRect(0,0,width-1,height-1);
// 写验证码
String str="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// 生成随机角标
Random random = new Random();
StringBuilder stringBuilder=new StringBuilder();
for (int i = 1; i <= 4; i++) {
int index = random.nextInt(str.length());
// 获取字符,随机字符
char charAt = str.charAt(index);
stringBuilder.append(charAt);
graphics.drawString(charAt+"",width/5*i,height/2);
}
String checkCode_session = stringBuilder.toString();
// 将验证码存入 Session 达到共享数据的目的
request.getSession().setAttribute("checkCode_session",checkCode_session);
// 画干扰线
graphics.setColor(Color.pink);
// 随机生成坐标点
for (int i = 0; i < 10; i++) {
int x1 = random.nextInt(width);
int x2 = random.nextInt(width);
int y1 = random.nextInt(height);
int y2 = random.nextInt(height);
graphics.drawLine(x1,x2,y1,y2);
}
// 将图片输出到页面展示
ImageIO.write(image,"jpg",response.getOutputStream());
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置 Request 对象的编码
request.setCharacterEncoding("utf-8");
// 获取参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String checkCode = request.getParameter("checkCode");
// 先获取生成的验证码
HttpSession session = request.getSession();
String checkCode_session = (String) session.getAttribute("checkCode_session");
// 删除 Session 中存储的验证码
session.removeAttribute("checkCode_session");
// 判断验证码是否正确
if (checkCode_session!=null&&checkCode_session.equalsIgnoreCase(checkCode)){
// 忽略大小写判断,比较字符串
// 如果验证码正确,判断用户名和密码是否一致
if ("张三".equals(username)&&"123".equals(password)){
// 此时需要调用 UserDao 查询数据库
// 登录成功
// 存储信息,用户信息
session.setAttribute("user",username); // 应该是存储 User 对象
// 重定向到 success.jsp
response.sendRedirect(request.getContextPath()+"/success.jsp");
}else {
// 登录失败
// 存储提示信息到 Request
request.setAttribute("login_error","用户名或密码错误!");
// 转发到登录界面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}else {
// 验证码不一致
// 存储提示信息到 Request
request.setAttribute("cc_error","验证码错误!");
// 转发到登录界面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1><%= request.getSession().getAttribute("user") %>,欢迎您!</h1>
</body>
</html>