会话技术
我们编写一段代码,做一个实验,引出会话技术。
package com.cskaoyan.cookie;
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 java.io.IOException;
import java.util.Enumeration;
@WebServlet("/req")
public class RequestServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//打印一下客户端发送过来的HTTP请求报文信息以及客户端的主机地址信息
String remoteAddr = request.getRemoteAddr();
int remotePort = request.getRemotePort();
System.out.println("客户机地址:" + remoteAddr + "端口号:" + remotePort);
String method = request.getMethod();
String requestURI = request.getRequestURI();
String protocol = request.getProtocol();
System.out.println(method + " " + requestURI + " " + protocol);
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
System.out.println(headerName + ":" + headerValue);
}
}
}
使用本地的浏览器发起一个HTTP请求,抓取HTTP请求报文
客户机地址:192.168.1.26端口号:50735
GET /app/req HTTP/1.1
host:192.168.1.26
connection:keep-alive
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.73
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-encoding:gzip, deflate
accept-language:zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
使用另外一台机器(另外一个客户端),再次发起一个HTTP请求
客户机地址:192.168.1.3端口号:56214
GET /app/req HTTP/1.1
host:192.168.1.26
connection:keep-alive
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Linux; Android 10; HarmonyOS; LIO-AN00; HMSCore 6.0.1.306) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.3.300 Mobile Safari/537.36
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-encoding:gzip, deflate
accept-language:zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
通过去比较两个请求报文,发现有没有什么不同?
不同的客户端发送的HTTP请求报文,发现是一模一样的。
和我们之前的上网经历做比较,发现其实存在一个问题。比如我们在逛某个商城的时候,可以往购物车里面加入商品,接下来,下次访问时,发现商品依然还在。服务器可以帮助我们去保存记忆一些数据。
但是通过上面的实验,我们发现不同客户端发送的HTTP请求报文居然是一模一样的、那么这是怎么实现的呢?
既然客户端发送的HTTP请求报文都是完全相同的,服务器来说,应该是无法区分客户端才对,那为什么服务器能够区分客户端,并且给客户端保存一些数据呢?
会话,其实就是指的是对话,沟通的含义。HTTP协议是无状态性,表现在不同的客户端发送过来的请求报文都是完全相同的,服务器时无法区分多个HTTP请求报文是同一个客户端发送的还是不同客户端发送的。进而服务器也不可能给某个客户端去保存某些数据,但是实际上和我们的web访问经历是违背的,我们在上网的过程中,我们发现的确服务器时可以帮助客户端去保存某些数据的,那么是如何做到这一点的呢?
其实就是利用会话技术。会话的意思就是表示对话,沟通。对于标准的HTTP协议来说,你可以认为此时服务器是非常健忘的,它是不可能记住一些信息的,这个时候也就无法进行对话。会话技术其实就是为了能够解决HTTP协议的无状态性的特点。
对话:
A:你吃饭了嘛
B:吃过了
A:你吃的什么
B:吃的面
A:吃的什么面,宽面还是细面
B:大碗细面
A:多少钱
B:25块钱
对话能够正常进行下去的前提是啥:前提是能够知道所说的上下文的语义。
但是对于web访问过程而言,HTTP协议它是一个无状态性协议,体现在所有的客户端发送的HTTP请求报文都是完全相同的,服务器其实压根区分不了彼此,也就无法进行后续的操作。
客户端:我要查看xxx商品信息
服务器:返回了这个商品的信息
客户端:帮我把这个商品加入到我的购物车里面
服务器:晕了。(HTTP协议的无状态性,所有的客户端发送的请求报文都是完全相同的,无法区分彼此。这个商品指的是什么呢?我的购物车,我又是指的是谁呢?)
在标准的HTTP协议情况下,服务器时无法记住客户端的,是无法给客户端保存一些数据的,很不方便。后面进而引入了会话技术,会话技术的出现其实就是为了解决HTTP协议无状态这一不足,引入了会话技术之后,服务器就可以知道请求来自于哪个客户端,那么就可以给当前客户端保存一些数据。
客户端技术
代表是cookie。一小部分数据。该数据的产生是在服务器上面产生的,产生之后,数据会随着HTTP响应报文(响应头)传输给客户端,客户端会随即将该数据保存在浏览器中,当浏览器下次再去访问服务器时,浏览器就会把该cookie信息再次携带回给服务器(请求头),服务器就知道了请求来自于哪个客户端。
这块一开始没弄懂服务器是怎么通过cookie来区分是哪个客户端的,而且也不明白为什么一个字段就能区分,难道不应该用更长的数字序列才能区分吗?
后来通过项目实践,明白了其实就是通过cookie给你的这个信息,你能找到对应的用户的数据库内容,进而拿到用户的信息,这不就做到区分用户了吗。
cookie的本质:
数据的产生是在服务器,服务器产生这个数据之后,接下来通过set-Cookie响应头,将该数据返回给客户端,客户端会将该数据保存,当客户端下次再去访问服务器时,通过Cookie请求头将信息再次携带回给服务器,服务器取出cookie请求头里面的信息,就可以知道了是哪个客户端。
Cookie的使用
Creates a cookie, a small amount of information sent by a servlet to a Web browser, saved by the browser, and later sent back to the server. A cookie’s value can uniquely identify a client, so cookies are commonly used for session management.
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/cookie")
public class CookieServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//cookie是一个class,直接new一个cookie对象
Cookie cookie = new Cookie("username", "zhangsan");
//通过set-Cookie:usename=zhangsan发送给客户端
//实际上EE规范同样给我们提供了一个简便的方式,用来发送cookie给客户端
//开发者只需要调用该方法,那么服务器会帮我们去取出key和value值,然后生成set-Cookie响应头
response.addCookie(cookie);
}
}
通过HTTP去访问,应当去看什么东西?
1.第一次访问主要关注set-Cookie响应头(此时正常情况下来说,应该是不会有cookie请求头)
2.第二次访问主要关注Cookie请求头
案例一
使用cookie来实现登录功能,要求,用户输入用户名、密码进行登录,登录成功之后页面显示出当前用户的用户名。
在学习cookie之前,如果我希望可以在页面显示用户的用户名,可以怎么做?可以用转发。
login.html--------->LoginServlet(username reqeust域)---------转发到另外一个servlet(取出username)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app/login" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit">
</form>
</body>
</html>
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
//假设进行校验,用户名、密码校验通过 JDBC
Cookie cookie = new Cookie("username", username);
response.addCookie(cookie);
//登录完成之后进行页面的跳转
response.getWriter().println("登录成功,即将跳转至个人主页");
response.setHeader("refresh","2;url=" + request.getContextPath() + "/info");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/info")
public class InfoServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//从请求头里面取出cookie信息,显示在页面上
//该API的原理相当于在HTTP请求报文被拆封成request对象时,将请求头进行封装
//相当于获取指定的请求头,然后封装成cookie[]
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("username".equals(cookie.getName())){
response.getWriter().println(cookie.getValue());
}
}
}
}
}
案例二
显示上次访问当前页面的时间。
package com.cskaoyan.cookie;
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 java.io.IOException;
import java.util.Date;
@WebServlet("/last")
public class LastLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//生成一个cookie信息
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("lastLogin".equals(cookie.getName())){
String value = cookie.getValue();
// Long.parseLong----Integer.parsetInt "111111"--->11111
Date date = new Date(Long.parseLong(value));
response.getWriter().println(date);
}
}
}
// cookie的value指里面不可以有空格
Cookie cookie = new Cookie("lastLogin", System.currentTimeMillis() + "");
response.addCookie(cookie);
}
}
cookie设置
设置存活时间
如果cookie没有设置存活时间,则cookie默认情况下是存储在浏览器的内存中的,浏览器关闭,则cookie失效;如果希望cookie能够持久化保存,则应当设置一个时间。
Cookie cookie = new Cookie("lastLogin", System.currentTimeMillis() + "");
//单位是秒
cookie.setMaxAge(180);
response.addCookie(cookie);
设置正数,表示存活多少秒
设置负数,表示的是存活在浏览器内存中,和没有设置是一样的
设置0,表示的是删除cookie
package com.cskaoyan.cookie;
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 java.io.IOException;
import java.util.Date;
@WebServlet("/last")
public class LastLoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//生成一个cookie信息
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("lastLogin".equals(cookie.getName())){
String value = cookie.getValue();
// Long.parseLong----Integer.parsetInt "111111"--->11111
Date date = new Date(Long.parseLong(value));
response.getWriter().println(date);
//特别注意一点:这里设置maxAge=0只是服务器设置了cookie对象
//但是你要清楚cookie是保存在哪的?保存在客户端的?
//服务器是没法直接删除cookie的,服务器给客户端发送了一个指示,让客户端去删除cookie
response.addCookie(cookie);
}
}
}
// cookie的value指里面不可以有空格
Cookie cookie = new Cookie("lastLogin", System.currentTimeMillis() + "");
//单位是秒
// cookie.setMaxAge(180);
response.addCookie(cookie);
}
}
package com.cskaoyan.cookie;
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 java.io.IOException;
import java.util.Date;
@WebServlet("/last2")
public class LastLoginServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//生成一个cookie信息
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("lastLogin".equals(cookie.getName())){
String value = cookie.getValue();
// Long.parseLong----Integer.parsetInt "111111"--->11111
Date date = new Date(Long.parseLong(value));
response.getWriter().println(date);
//特别注意一点:这里设置maxAge=0只是服务器设置了cookie对象
//但是你要清楚cookie是保存在哪的?保存在客户端的?
//服务器是没法直接删除cookie的,服务器给客户端发送了一个指示,让客户端去删除cookie
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
}
}
首先在last中生成一个cookie信息,在last2中我们获取到该cookie信息,打印出来,同时设置MaxAge=0
同时还需要再次设置response.addCookie(cookie)将该cookie的设置返回给客户端,客户端拿到该设置以后就会删除该cookie,下次再次访问时,就不会携带cookie了、
设置路径
默认情况下,当访问当前主机下所有资源时都会携带cookie,可以设置一个path,仅当访问指定路径时才会携带cookie。有的页面,访问静态资源文件不需要携带cookie,只设置访问某个html时才携带cookie
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/path")
public class PathServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie cookie = new Cookie("name", "path");
cookie.setPath(request.getContextPath() + "/path2");
response.addCookie(cookie);
}
}
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/path2")
public class PathServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("name".equals(cookie.getName())){
response.getWriter().println(cookie.getValue());
}
}
}
}
}
在path中生成了一个cookie,设置了路径是访问path2会携带,也就是说访问path时,不会携带cookie的,只有访问path2时才会携带cookie。
其中,关于删除cookie有一个注意事项,如果cookie没有设置path,则直接设置MaxAge=0就表示删除cookie
如果cookie设置了path,在删除cookie时,还需要将原先的path再写一遍
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/path")
public class PathServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie cookie = new Cookie("name", "path");
cookie.setPath(request.getContextPath() + "/path2");
response.addCookie(cookie);
}
}
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/path2")
public class PathServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie : cookies) {
if("name".equals(cookie.getName())){
response.getWriter().println(cookie.getValue());
cookie.setMaxAge(0);
cookie.setPath(request.getContextPath() + "/path2");
response.addCookie(cookie);
}
}
}
}
}
正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。
1.可在同一应用服务器内共享方法:设置cookie.setPath("/");
本机tomcat/webapp下面有两个应用:cas和webapp_b,
1)原来在cas下面设置的cookie,在webapp_b下面获取不到,path默认是产生cookie的应用的路径。
2)若在cas下面设置cookie的时候,增加一条cookie.setPath("/");或者cookie.setPath("/webapp_b/");就可以在webapp_b下面获取到cas设置的cookie了。
3)此处的参数,是相对于应用服务器存放应用的文件夹的根目录而言的(比如tomcat下面的webapp),因此cookie.setPath("/");之后,可以在webapp文件夹下的所有应用共享cookie,而cookie.setPath("/webapp_b/");是指cas应用设置的cookie只能在webapp_b应用下的获得,即便是产生这个cookie的cas应用也不可以。
设置域名
总的原则是浏览器是不允许设置和当前域名无关的cookie的,比如当前域名 localhost,设置了一个域名叫做aaa.com的cookie,浏览器是不允许的。
比如你申请了一个aaa.com的域名
sub.aaa.com域名属于你的吗?是的
third.sub.aaa.com域名属于你的吗?也是的
如果你设置了一个父级域名的cookie,比如设置了一个cookie的domain是aaa.com,那么接下来当你访问当前域名的子域名时,浏览器会自动帮你携带cookie,可以实现cookie的共享
设置了一个域名叫做jd.com的cookie
当你访问product.jd.com时,account.jd.com时,search.jd.com时都可以携带cookie信息。
在hosts文件中做了如下设置
127.0.0.1 ccc.com
127.0.0.1 sub.ccc.com
127.0.0.1 third.sub.ccc.com
package com.cskaoyan.cookie;
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 java.io.IOException;
@WebServlet("/domain")
public class DomainServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Cookie cookie = new Cookie("key", "doamin");
//如果设置了改行代码,那么一定不用通过localhost来范文
cookie.setDomain("ccc.com");
response.addCookie(cookie);
}
}
操作流程:
ccc.com/app/domain 该地址下生成一个一个cookie
紧接着访问sub.ccc.com/app以及third.sub.ccc.com/app时,发现请求头中均携带了刚刚生成的cookie信息,那么可以用来进行全局性的cookie共享
集团系统的cookie信息共享,尤其是用户的登录状态。
客户端技术的优点:
1.存储在客户端,减轻服务器的压力
缺点:
1.只可以存储字符类型,如果希望存储对象,很不方便
2.cookie存储的容量一般有限制,如果数据很大,则无法存储
3.cookie存储在客户端,不是特别安全,只可以存储一些非敏感数据
服务器技术
代表是session。核心其实就是在服务器内存中开辟一块空间,来给用户存储数据。
当客户端访问服务器时,服务器会给当前用户的浏览器开辟一块内存空间,那么这个内存空间就和当前的这个浏览器做一个绑定,接下来,等后续浏览器再次访问该服务时,那么就利用这块内存空间来给浏览器来提供服务,可以用来存取数据。
每有一个浏览器访问,那么就会开辟一块内存空间。二者之间做了一个绑定。
实际上,上图只有一个问题,那么就是client和session对象(内存空间)如何关联在一起
实际上就是通过cookie来关联的。session底层依赖于cookie的
客户端访问服务时,服务器在某个场景下,会生成一个session对象(开辟一块内存空间),紧接着将该session对象的id值通过cookie返回给客户端(set-Cookie:JSESSIONID=xxxx),客户端下次访问时,就会将该id再次通过cookie请求头携带回来(Cookie:JSESSIONID=xxxx),服务器通过去取出cookie里面的JSESSIONID的值,就可以拿到对应的session对象,就可以进行数据的共享。
session的使用
1.创建session对象?
EE规范给我们提供了一个方法,叫做request.getSession();
HttpSession getSession()
Returns the current session associated with this request, or if the request does not have a session, creates one.
HttpSession getSession(boolean create)
Returns the current HttpSession
associated with this request or, if there is no current session and create
is true, returns a new session.
If create
is false
and the request has no valid HttpSession
, this method returns null
.
对于方式二,如果create是true,那么它和方式一是完全等价的。
对于方式二,如果create是false,那么它有则返回,没有则返回null,不会去创建。
package com.cskaoyan.session;
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;
import java.io.IOException;
@WebServlet("/session1")
public class SessionServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个session对象
HttpSession session = request.getSession();
System.out.println(session);
System.out.println(session.getId());
}
}
思考一个问题:
抓包关注什么?
第一次访问肯定会创建一个session对象,创建完session对象肯定会将session的id通过set-Cookie响应头发送给客户端。
第二次访问该servlet,还能不能看到创建过程?能或者不能其实取决于此时有没有session对象?如何判断有没有session对象呢?其实就是根据请求报文中请求头中有没有携带有效的JSESSIONID,如果携带了一个有效的JSESSIONID,那么就可以根据id找到session对象,找到的话,则不会创建新的session对象;如果没有携带一个有效的JSESSIONID,那么就找不到session对象,找不到的话,就会创建一个新的session对象。
2.session对象的使用
session其实也是一个域,session域。
方法和之前介绍的context域、request域的方法一模一样。
getSession()方法的执行逻辑
request.getSession()方法,可以创建或者返回一个session对象。如果当前请求有关联的session对象,那么就返回, 如果没有关联的session对象,则创建一个新的。什么叫关联呢?
就是看请求头中有没有携带有效的Cookie:JSESSIONID,如果携带了有效的id,那么就可以根据该id找到对应的session对象,则直接将该session对象返回,那么后续也就不会创建,也就不会有发送set-Cookie响应头的过程;如果没有携带有效的id,则会创建一个新的。
登录案例
使用session来实现。
package com.cskaoyan.session;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
//假设进行校验,用户名、密码校验通过 JDBC
HttpSession session = request.getSession();
session.setAttribute("username", username);
//登录完成之后进行页面的跳转
response.getWriter().println("登录成功,即将跳转至个人主页");
response.setHeader("refresh","2;url=" + request.getContextPath() + "/info");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
package com.cskaoyan.session;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/info")
public class InfoServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
response.getWriter().println(username);
}
}
三个域区别
context、request、session域
只要能够拿到同一个对象,那么就可以共享数据。
context域:整个应用中有且只有一个,无论哪个客户端访问,访问哪个servlet,拿到的都是同一个
request域:非常小。每次请求都会创建新的request对象,只有转发两个组件可以共享
session域:和用户具有强相关性。每个浏览器访问服务器,正常情况下来说,都会创建一个session对象,只要是同一个浏览器,那么访问不同servlet时,拿到的也都是同一个session对象。不同的浏览器拿到的肯定不是同一个session对象。
关闭浏览器,session会销毁吗,数据会丢失吗?
package com.cskaoyan.session;
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;
import java.io.IOException;
@WebServlet("/session2")
public class SessionServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个session对象
HttpSession session = request.getSession();
session.setAttribute("key", "zhangsan");
System.out.println(session);
System.out.println(session.getId());
}
}
package com.cskaoyan.session;
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;
import java.io.IOException;
@WebServlet("/session3")
public class SessionServlet3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个session对象
HttpSession session = request.getSession();
Object key = session.getAttribute("key");
System.out.println(key);
System.out.println(session);
System.out.println(session.getId());
}
}
// 访问session2
org.apache.catalina.session.StandardSessionFacade@179d29a5
F92C51A91C2E856E8ABC163EB7B75670
//访问session3
zhangsan
org.apache.catalina.session.StandardSessionFacade@179d29a5
F92C51A91C2E856E8ABC163EB7B75670
///-------关闭浏览器,再次访问session3
null
org.apache.catalina.session.StandardSessionFacade@5b3f2b69
680287DC762299FDC7AE13A662AAB7CB
session没有被销毁,但是数据此时已经取不到了,丢失了。
既然没有销毁,为什么打印session的地址和id都变了呢?
关闭浏览器之后,cookie默认情况下存在于浏览器内存中,关闭浏览器,则把cookie给丢失了,再次访问时不会携带cookie:JSESSIONID=xxx,如果不携带,则会创建一个新的session对象。
原先的session对象,此时还没有被销毁,类似于一种不可达的状态。
你在瑞士银行有一顿黄金,但是你把密码忘了。
关闭服务器,session会销毁吗,数据会丢失吗?
session对象会销毁。
数据不会丢失。
验证:千万不要通过关闭idea tomcat的关闭按钮或者重新部署来验证,否则你得不出结论。
可以使用tomcat提供的应用的后台管理系统来关闭应用
1.需要保障本地安装的tomcat里面有manger应用
2.需要在本地安装的tomcat conf/tomcat-users.xml文件中新增如下标签
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui"/>
更改完毕,重启服务器,访问manager
说明了啥?
原先session在服务器即将被关闭之前,将session的id以及session里面存储的数据,全部序列化到本地硬盘上面了
重启服务器时,重新分配了一些新的session对象,将这些sessionid以及数据塞入到新的对象中。
此外还可以采用另外一种方式来验证,直接用本地安装的tomcat来部署idea里面的应用。
session的生命周期
session对象的创建:
request.getSession() 来创建
使用:
session.setAttribute()
getAttribute
removeAttribute
session对象的销毁:
应用被卸载、服务器被关闭,但是此时数据不会丢失
数据会以序列化的形式持久化到本地硬盘上面
数据的销毁:
1.session的有效期到达(默认有效期是30min,如果30min没人访问,那么session数据被销毁,只要30min有人访问了,那么重新开始倒计时)
< session-config>
< session-timeout>30< /session-timeout>
< /session-config>
2.主动调用session.invalidate()方法(登录成功,session写入数据;注销按钮)
购物车案例
1.首页可以显示出几条商品信息(就用超链接显示商品信息)
2.当用户点击某个超链接时,进入到该商品的详情页,显示商品的信息(直接调用对象的toString),在该页面还有三个a标签。一个是返回首页,一个是加入购物车,一个是查看购物车
3.点击加入购物车按钮,会将当前商品加入到购物车中,回显加入购物车成功,返回首页
4.点击查看购物车,那么可以显示出当前购物车里面的商品信息(直接显示商品的toString)
关于商品详情页可以这么来设计,需要拿到商品的id,为什么呢?因为在商品详情页需要加载商品的详细信息,需要通过id拿到
response.getWriter().println("<a href='" + request.getContextPath() + "/goods?id=" + product.getId() + "'>" + product.getName() + "</a>");---如何获取通过request.getParameter来获取
response.getWriter().println("<a href='" + request.getContextPath() + "/goods/" + product.getId() + "'>" + product.getName() + "</a>");
设置一个servlet,url-pattern是 /goods /*
response.getWriter().println("<a href='" + request.getContextPath() + "/goods/" + product.getId() + ".html'>" + product.getName() + "</a>");
怎么处理呢? /goods/*
http://localhost/cart/goods/1
http://localhost/cart/goods/1.html
说明:不要求考虑数量问题。
package com.cskaoyan.cart;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet(value = "/index",loadOnStartup = 1)
public class IndexServlet extends HttpServlet {
@Override
public void init() throws ServletException {
List<Product> products = new ArrayList<>();
products.add(new Product("1","iphone12", 6999.0, "iphone"));
products.add(new Product("2", "huawei mate40 pro", 5888.0, "huawei"));
products.add(new Product("3","mi 11", 5699.0, "mi"));
products.add(new Product("4", "onePlus 10",5888.0, "one plus"));
getServletContext().setAttribute("products", products);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//需要将商品的名称显示给客户端
//用list来封装几个对象
ServletContext servletContext = getServletContext();
List<Product> products = (List<Product>) servletContext.getAttribute("products");
for (Product product : products) {
// response.getWriter().println("<a href='" + request.getContextPath() + "/goods/" + product.getId() + "'>" + product.getName() + "</a>");
response.getWriter().println("<a href='" + request.getContextPath() + "/goods/" + product.getId() + ".html'>" + product.getName() + "</a>");
}
}
}
package com.cskaoyan.cart;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.util.List;
@WebServlet("/goods/*")
public class GoodsServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//拿到商品的id /cart/goods/1.html
response.setContentType("text/html;charset=utf-8");
String requestURI = request.getRequestURI();
String path = requestURI.replace(request.getContextPath() + "/goods/", "");
String id = path.replace(".html", "");
//为了稳妥起见,可以做一个判断
if(id == null){
response.getWriter().println("不合法的请求地址");
return;
}
ServletContext servletContext = getServletContext();
List<Product> products = (List<Product>) servletContext.getAttribute("products");
for (Product product : products) {
if(id.equals(product.getId())){
response.getWriter().println(product);
}
}
//纵向选择 按住alt + 鼠标键拖拉
response.getWriter().println("<a href='" + request.getContextPath() + "/index" + "'>返回首页</a>");
response.getWriter().println("<a href='" + request.getContextPath() + "/addCart/" + id + "'>加入购物车</a>");
response.getWriter().println("<a href='" + request.getContextPath() + "/viewCart" + "'>查看购物车</a>");
}
}
package com.cskaoyan.cart;
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;
import java.io.IOException;
import java.util.List;
@WebServlet("/viewCart")
public class ViewCartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//查看购物车的逻辑 id---- context Product
HttpSession session = request.getSession();
List<String> cart = (List<String>) session.getAttribute("cart");
if(cart == null){
response.getWriter().println("购物车为空,去选购吧");
response.setHeader("refresh", "2;url=" + request.getContextPath() + "/index");
return;
}
response.getWriter().println("<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Title</title>\n" +
"</head>\n" +
"<body>");
List<Product> products = (List<Product>) getServletContext().getAttribute("products");
for (String id : cart) {
for (Product product : products) {
if(id.equals(product.getId())){
response.getWriter().println(product);
}
}
}
response.getWriter().println("<a href='" + request.getContextPath() + "/index" + "'>返回首页</a>");
response.getWriter().println("</body>\n" +
"</html>");
}
}
package com.cskaoyan.cart;
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/addCart/*")
public class AddCartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String id = requestURI.replace(request.getContextPath() + "/addCart/", "");
//需要将id关联的对象保存到购物车中
//这个时候你可以选择将id保存到购物车,或者将对象保存到购物车
//接下来的逻辑应该是啥?购物车是用户强相关性 session
HttpSession session = request.getSession();
// session.setAttribute("id", id);
List<String> cart = (List<String>) session.getAttribute("cart");
if(cart == null){
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
cart.add(id);
response.getWriter().println("add success");
//接下来跳转到首页即可
response.setHeader("refresh", "2;url=" + request.getContextPath() + "/index");
}
}
作业
关闭浏览器,依然可以访问到session域中的数据
关闭浏览器,为什么数据会丢失呢?cookie丢失了,JSESSIONID凭证丢失了,找不到原先的session,进而创建了一个新的session对象
只需要设置关闭浏览器,JSESSIONID的cookie不丢失即可。
package com.cskaoyan.session;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/session2")
public class SessionServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个session对象
HttpSession session = request.getSession();
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(180);
response.addCookie(cookie);
session.setAttribute("key", "zhangsan");
System.out.println(session);
System.out.println(session.getId());
}
}
禁用cookie之后的session策略
大家都知道,浏览器是有禁用cookie的功能的,如果禁用了cookie,session还能用吗?正常情况下来说就无法使用了,但是可以通过设置API,对地址进行重写,将jsessionid放置在地址栏中进行传递
response.encodeURL()
package com.cskaoyan.cart;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet(value = "/index",loadOnStartup = 1)
public class IndexServlet extends HttpServlet {
@Override
public void init() throws ServletException {
List<Product> products = new ArrayList<>();
products.add(new Product("1","iphone12", 6999.0, "iphone"));
products.add(new Product("2", "huawei mate40 pro", 5888.0, "huawei"));
products.add(new Product("3","mi 11", 5699.0, "mi"));
products.add(new Product("4", "onePlus 10",5888.0, "one plus"));
getServletContext().setAttribute("products", products);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//需要将商品的名称显示给客户端
//用list来封装几个对象
ServletContext servletContext = getServletContext();
List<Product> products = (List<Product>) servletContext.getAttribute("products");
for (Product product : products) {
String goods = response.encodeURL(request.getContextPath() + "/goods/" + product.getId() + ".html");
response.getWriter().println("<a href='" + goods + "'>" + product.getName() + "</a>");
}
}
}
package com.cskaoyan.cart;
import javax.servlet.ServletContext;
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 java.io.IOException;
import java.nio.IntBuffer;
import java.util.List;
@WebServlet("/goods/*")
public class GoodsServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//拿到商品的id /cart/goods/1.html
response.setContentType("text/html;charset=utf-8");
String requestURI = request.getRequestURI();
String path = requestURI.replace(request.getContextPath() + "/goods/", "");
String id = path.replace(".html", "");
int i = id.indexOf(";");
if(i != -1){
id = id.substring(0, i);
}
//为了稳妥起见,可以做一个判断
if(id == null){
response.getWriter().println("不合法的请求地址");
return;
}
ServletContext servletContext = getServletContext();
List<Product> products = (List<Product>) servletContext.getAttribute("products");
for (Product product : products) {
if(id.equals(product.getId())){
response.getWriter().println(product);
}
}
//纵向选择 按住alt + 鼠标键拖拉
String index = response.encodeURL(request.getContextPath() + "/index");
String addCart = response.encodeURL(request.getContextPath() + "/addCart/" + id);
String viewCart = response.encodeURL(request.getContextPath() + "/viewCart");
response.getWriter().println("<a href='" + index + "'>返回首页</a>");
response.getWriter().println("<a href='" + addCart + "'>加入购物车</a>");
response.getWriter().println("<a href='" + viewCart + "'>查看购物车</a>");
}
}
package com.cskaoyan.cart;
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;
import java.io.IOException;
import java.util.List;
@WebServlet("/viewCart")
public class ViewCartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//查看购物车的逻辑 id---- context Product
HttpSession session = request.getSession();
List<String> cart = (List<String>) session.getAttribute("cart");
if(cart == null){
response.getWriter().println("购物车为空,去选购吧");
String index = response.encodeURL(request.getContextPath() + "/index");
response.setHeader("refresh", "2;url=" + index);
return;
}
response.getWriter().println("<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>Title</title>\n" +
"</head>\n" +
"<body>");
List<Product> products = (List<Product>) getServletContext().getAttribute("products");
for (String id : cart) {
for (Product product : products) {
if(id.equals(product.getId())){
response.getWriter().println(product);
}
}
}
String index = response.encodeURL(request.getContextPath() + "/index");
response.getWriter().println("<a href='" + index + "'>返回首页</a>");
response.getWriter().println("</body>\n" +
"</html>");
}
}
package com.cskaoyan.cart;
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/addCart/*")
public class AddCartServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
// addCart/2;xxxxxx
String id = requestURI.replace(request.getContextPath() + "/addCart/", "");
int i = id.indexOf(";");
if(i != -1){
id = id.substring(0, i);
}
//需要将id关联的对象保存到购物车中
//这个时候你可以选择将id保存到购物车,或者将对象保存到购物车
//接下来的逻辑应该是啥?购物车是用户强相关性 session
HttpSession session = request.getSession();
// session.setAttribute("id", id);
List<String> cart = (List<String>) session.getAttribute("cart");
if(cart == null){
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
cart.add(id);
response.getWriter().println("add success");
//接下来跳转到首页即可
String index = response.encodeURL(request.getContextPath() + "/index");
response.setHeader("refresh", "2;url=" + index);
}
}
jsp不去展开了。jsp的原理其实就是做了我们之前response.getWriter().println()输出html标签的工作。