简述
比如访问某个网站,看了商城首页,进入商品详情页,评论页,最后一直到离开当前网站为止,期间这一段过程我们称之为一个会话。在会话过程中,不可避免的会产生一些数据,比如说你查看了哪些商品,比如你将哪个商品加入了购物车。这些数据其实是用户所特有的数据,但是,目前的问题是HTTP协议其实是一个无状态协议,通过HTTP协议请求报文是无法区别每个请求来自于哪个客户端。通过实验可以验证,不同的客户端发送过来的请求报文基本上是完全一模一样的。而现实中服务器却是能够知道与客户相关的特异性数据,这是因为引入了会话技术。
Cookie
是保存在客户端的。客户端技术,当客户端访问服务器时,服务器会生成一个用户所特有的数据,然后接下来通过响应报文响应头(Set-Cookie)再次传输给客户端。客户端随之会将该数据保存在本地等客户端下次再次去访问服务器时,那么它就会利用请求报文的请求头(Cookie)把该数据给带回服务器,利用这个数据,就知道来自于哪个客户端了。
package com.simon.status;
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("/status")
public class HttpStatus extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println(req.getRemoteAddr());
System.out.println(req.getMethod() + " " + req.getRequestURI()
+ " " + req.getProtocol());
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()){
String headName = headerNames.nextElement();
String headValue = req.getHeader(headName);
System.out.println(headName + ":" + headValue);
}
}
}
手动刷新案例
package com.simon.lastView;
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 ShowLastViewTime extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
if (cookies != null){
for (Cookie cookie : cookies) {
if ("last".equals(cookie.getName())){
String value = cookie.getValue();
Date date = new Date(Long.parseLong(value));
resp.getWriter().println(date);
}
}
}
Cookie cookie = new Cookie("last", System.currentTimeMillis() + "");
resp.addCookie(cookie);
}
}
回显案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/cs/login" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit">
</form>
</body>
</html>
package com.simon.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 {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
resp.getWriter().println("登录成功,即将跳转至个人主页...");
Cookie cookie = new Cookie("username", username);
//秒;默认存储在浏览器,一旦关闭,数据丢失;
//0表示删除数据;负数=没设置,默认存储在浏览器中
cookie.setMaxAge(30);
//表示只有到这个路径,cookie才会携带登录信息,其他的则没有
cookie.setPath(req.getContextPath()+"/info");
resp.addCookie(cookie);
resp.setHeader("refresh","2;url="+req.getContextPath()+"/info");
}
}
package com.simon.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 doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if ("username".equals(cookie.getName())){
response.getWriter().println("欢迎您,"+cookie.getValue());
}
}
}
}
在Login servlet中,设置了cookie的path,仅当访问info时才会携带
在info servlet中,我们设置了cookie的max-age=0,表示的是让客户端删除cookie
但是通过最终的结果发现,客户端其实并没有去删除cookie
为什么呢?
设置cookie的max-age=0的确是删除cookie 的方法
但是
如果cookie设置了path,那么删除的时候需要将path再写一遍,这个时候才可以删除
优缺点
优点:
1.客户端技术,数据存储在客户端的,缓解了服务器的压力
2.cookie很小,很灵活
缺点:
1.但是,数据存储在客户端,被攻击的可能性要高很多,数据泄露的可能性就很高,决定了cookie里面存储的一般都是非敏感数据
2.存储的数据容量有限制;value必须是字符串类型,比如想存储list等,object等,不太好去存储
当当购物车的实现
没有登录的情况下,可以自由添加商品到购物车的,但是此时购物车仅限于当前浏览器可以使用,换了另一个浏览器,那么商品就不存在了,此时使用cookie来存储数据的
如果你登录了你的账号,那么此时,会将本地购物车里面的数据和账户里面的购物车数据进行同步,此时本地浏览器里面的商品会进入到你的账户购物车中(数据库里面有一张表,持久化存储你的购物车里面的商品信息)
Session
简述
服务器技术。当客户端访问服务器时,服务器会生成一块内存区域,这个内存区域就和客户端做一个对应,接下来,当前客户端需要存取的数据都在这块内存空间进行,实现了数据的共享。
整个过程
客户端访问服务器时,在某个场景下,服务器会给当前的客户端生成一个session对象,该对象有一个唯一的标识ID,当服务器做出响应的时候,随即会把该ID通过Set-Cookie:JSESSIONID=xxx,发送给客户端,客户端会将该cookie保存下来;下次再次访问服务器时,会在请求头中以Cookie:JSESSIONID=xxxx形式再将session的id返回回去,可以通过session的id找到对应的session对象。
使用方法
@WebServlet("/session1")
public class SessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
//访问两次,地址相同
//org.apache.catalina.session.StandardSessionFacade@7a2e4ac1
//org.apache.catalina.session.StandardSessionFacade@7a2e4ac1
System.out.println(session);
}
}
怎么判断有没有session对象的? request.getSession()的执行逻辑:
其实全部依靠判断当前请求报文有没有携带有效的Cookie:JSESSIONID=xxxx,如果携带了一个有效的session id,当执行request.getSession,服务器会取出session的id,然后再session列表里面去搜寻对应的session对象,如果有的话,则返回,如果没有,依然会去创建一个新的,此时会生成一个Set-Cookie响应头,将Session的id返回给客户端;如果当前请求没有携带Cookie:JSESSIONID=xxx,那么就会直接创建一个新的session对象。
package com.simon.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 {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession();
session.setAttribute("username","zs");
}
}
package com.simon.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 {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
HttpSession session = req.getSession();
String username = (String) session.getAttribute("username");
//username:zs
System.out.println("username:" + username);
}
}
三个域的比较
总的来说,Context > Session > Request。
Context对象范围最大,因为有且只有一个。Session范围其次,取决于连接过来的客户端,每有一个客户端连接进来,那么就生成一个session对象,哪些可以共享session域?
只要是同一个客户端连接过来(只要每次都携带着同一个Cookie:JSESSIONID=xxx),不管访问哪个servlet都可以共享session域,根据凭证(Cookie:JSESSIONID=xxx)去拿到对应的账户。
request域范围最小,每次请求都会创建一个新的request对象。只有转发的两个组件之间可以共享request域,除此之外全部无法共享。
一个客户端(可理解为session域)可以发送多个请求(可理解为request域),host选择一个合适的context去处理(Context域)。
用session实现登录和跳转的案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/cs/slogin" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit">
</form>
</body>
</html>
package com.simon.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("/slogin")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("登录成功,即将跳转....");
String username = req.getParameter("username");
HttpSession session = req.getSession();
session.setAttribute("username",username);
resp.setHeader("refresh","2;url="+req.getContextPath()+"/skip");
}
}
package com.simon.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("/skip")
public class SkipServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
Object username = session.getAttribute("username");
resp.getWriter().println("欢迎您,"+username);
}
}
几点注意
①关闭浏览器,session会销毁吗,数据会丢失吗?
session对象没有销毁,数据丢失了。
session依赖于cookie,cookie的特性是啥,默认情况下仅当前会话有效,也就是说在浏览器开启这段时间内有效。关闭浏览器,那么Cookie:JSESSIONID就丢失了,如果再去访问服务器,就会创建一个新的session对象。但之前的session对象还在,只是不可达。
②关闭服务器,session会销毁吗,数据会丢失吗?
肯定会销毁,数据没有丢失。并且的SESSION的id也没有变需要特别注意一点:不要通过关闭IDEA的tomcat来验证,否则你得不出结论。
如何验证?
配置一个tomcat应用管理系统。
1.本地tomcat需要有一个manager应用不要删除
2.本地tomcat conf/tomcat-users.xml文件
先访问一个servlet,session里设置了一个参数,接着应用重启;访问第二个servlet,显示出的的session信息里,session域的数据依然可以拿到。
怎么解释呢?
持久化。
应用被关闭的时候,session里面的数据以及session的id全部序列化到本地硬盘中,当应用再次重启的时候,session里面的数据,以及session的id会全部注入到一个新的session对象中。
为什么不能通过重启IDEA的tomcat来验证呢?
因为我们发现关闭idea的tomcat的确会生成SESSION.ser文件,但是当启动的时候,该目录会被删除,因为IDEA的tomcat在每次启动的时候都会去复制本地的tomcat的配置文件,把原先的文件全部删除,用新的文件来替代。采用另外一种方式来验证?将你的应用用本地tomcat来部署。
购物车案例
IndexServlet
package com.simon.cart;
import javax.servlet.ServletConfig;
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 = 2)
public class IndexServlet extends HttpServlet {
//准备一些商品数据 用这种方式来模拟从数据里面获取数据
@Override
public void init() throws ServletException {
Product product1 = new Product("1", "Huawei", 5999.0, "Huawei");
Product product2 = new Product("2", "Xiaomi", 4543.0, "Xiaomi");
Product product3 = new Product("3", "iPhone", 9346.0, "iphone");
Product product4 = new Product("4", "Vivo", 4322.0, "Vivo");
ArrayList<Product> products = new ArrayList<>();
products.add(product1);
products.add(product2);
products.add(product3);
products.add(product4);
//servletContext方法实现整个Context域的所有方法共享
getServletContext().setAttribute("products",products);
}
//点击链接实现跳转
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
List<Product> products = (List<Product>) getServletContext()
.getAttribute("products");
for (Product product : products) {
String detail = resp.encodeURL(req.getContextPath()
+ "/detail?id=" + product.getId());
resp.getWriter().println("<div><a href='" + detail + "'>"
+ product.getName() + "</a></div>");
}
resp.setContentType("text/html;charset=utf-8");
}
}
AddCartServlet
package com.simon.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 {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//拿到id,加入购物车。by session
String id = req.getParameter("id");
HttpSession session = req.getSession();
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null){
cart = new ArrayList<>();
session.setAttribute("cart",cart);
}
cart.add(id);
resp.getWriter().println("加入购物车成功,即将跳转回首页...");
String index = resp.encodeURL(req.getContextPath() + "/index");
resp.setHeader("refresh", "2;url=" + index);
}
}
DetailServlet
package com.simon.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 java.io.IOException;
import java.util.List;
@WebServlet("/detail")
public class DetailServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
String id = req.getParameter("id");
List<Product> products = (List<Product>) getServletContext()
.getAttribute("products");
for (Product product : products) {
if (id.equals(product.getId())){
resp.getWriter().println(product);
}
}
//为了防止浏览器禁用cookie,通过URL重写的方式来解决
String index = resp.encodeURL(req.getContextPath() + "/index");
String addCart = resp.encodeURL(req.getContextPath() + "/addCart?id=" + id );
String viewCart = resp.encodeURL(req.getContextPath() + "/viewCart");
resp.getWriter().println("<a href='" + index + "'>返回首页</a>");
resp.getWriter().println("<a href='" + addCart + "'>加入购物车</a>");
resp.getWriter().println("<a href='" + viewCart + "'>查看购物车</a>");
}
}
ViewCartServlet
package com.simon.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 {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
HttpSession session = req.getSession();
String index = resp.encodeURL(req.getContextPath() + "/index");
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null){
resp.getWriter().println("购物车为空,返回首页....");
resp.setHeader("refresh", "2;url=" + index);
return;
}
List<Product> products = (List<Product>) getServletContext()
.getAttribute("products");
for (Product product : products) {
for (String id : cart) {
if (id.equals(product.getId())){
resp.getWriter().println(product);
}
}
}
resp.getWriter().println("<a href='" + index + "'>返回首页</a>");
}
}