1. 回顾
Cookie是浏览器在本地持久化存储结构的一种机制.
1.1 Cookie的数据从哪里来?
服务器返回给浏览器的.
1.2 Cookie的数据是什么样的?
Cookie的数据是键值对结构.
并且这里的键值对都是程序员自定义的.
1.3 Cookie的作用是什么?
Cookie可以在浏览器这边存储一些"临时性的数据".
其实最典型的一种使用方式,就是用来存储"身份标识".(sessionId)
这里涉及到 Cookie和Session之间的联动.
后续在访问该网站的其它页面的时候,请求中就会自动带上刚才的sessionId,进一步服务器就可以知道当前是哪个用户在操作了.
1.4 Cookie到哪里去?
Cookie的内容会在下次访问该网站的时候,自动的被带到HTTP请求中.
1.5 Cookie怎么存的?
浏览器按照不同的"域名",分别存储Cookie.
域名和域名之间的Cookie是不能干扰的.
Cookie存储在硬盘上,往往会有一个超时时间.
2. Cookie操作
2.1 方法
①HttpServletRequest 类中的相关方法:
方法 | 描述 |
Cookie[] getCookies() | 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把 Cookie 中的格式解析成键值对. |
②HttpServletResponse 类中的相关方法:
方法 | 描述 |
void addCookie(Cookie cookie) | 把指定的 cookie 添加到响应中. |
把Cookie添加到响应中,在HTTP响应报文里,加了一个Set-Cookie header.
③Cookie 类中的相关方法:
每个 Cookie 对象就是一个键值对.
方法 | 描述 |
String getName() | 该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 Set Cooke 字段设置给浏览器的) |
String getValue() | 该方法获取与 cookie 关联的值 |
void setValue(String newValue) | 该方法设置与 cookie 关联的值。 |
2.2 方法实现
@WebServlet("/setcookie")
public class SetCookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//期望通过这个doGet方法,把一个自定义的Cookie数据返回到浏览器这边
Cookie cookie = new Cookie("date","2023-04-02");
resp.addCookie(cookie);
Cookie cookie2 = new Cookie("time","15:18");
resp.addCookie(cookie2);
resp.getWriter().write("setCookie ok");
}
}
@WebServlet("/getcookie")
public class GetCookieServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到这次请求的cookie
Cookie[] cookies = req.getCookies();
if (cookies != null){
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
}else {
System.out.println("请求中没有cookie");
}
resp.getWriter().write("ok");
}
}
①首先,发送getcookie请求:
此时并没有Cookie,自然getcookie请求里面也没有.
②下面发起setcookie请求:
请求这里并没有cookie内容:
响应这边:
访问setcookie请求的时候,代码中就会构造Cookie放在响应中.
进一步会写入浏览器,浏览器就会持久化保存.
③后续再次发送请求的时候,cookie内容就会出现在请求中:
再次发送,服务器就能拿到cookie内容了.(浏览器已经有了)
这个过程中,可以看到实际上真正发挥作用,还得是在服务器这边的逻辑中生效的.
3. 理解会话机制
3.1 Session相关方法
Servlet也提供了Session相关的支持.
实现登录功能,不需要直接使用Cookie AIP,直接使用Session的API就可以了.
①HttpServletRequest 类中的相关方法:
方法 | 描述 |
HttpSession getSession() | 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果 为 false, 则当不存在会话时返回 null |
getSession方法中,最核心的API.
效果有两方面:
1.如果当前用户没有session(会话),就会创建出session.
2.如果已经有了session,就能查询到这个session.
getSession背后具体做的事情:
①先读取请求中的Cookie,看Cookie里是否有JSESSIONID属性,以及值是什么.
(平时说的SessionId是一个广义的概念,不同的库中具体实现过程中会有一些细节的差异,在Servlet中,把这个属性具体叫做JSESSIONID)
如果没有,就认为需要创建新对话,
如果有,就拿着这个Id去查询看看当前的Session是否存在.
如果存在,就直接返回该Session
如果不存在,就准备创建新对话.
②当前确实需要创建对话,就会创建出一个Session对象,同时生成唯一的一个JSESSIONID,以该JSESSIONID为key,Session对象为value,把这个键值对给插入到服务器上述的哈希表中.
③刚才生成的JSESSION又会通过addCookie方法,加入到响应中.
此时响应里就会带有Set-Cookie字段,这里的值就是JSESSION=xxxx.
通过响应,就把JSESSION返回到浏览器这边了.
问:
什么情况下会有SessionId但没有Session?
服务器这边存储上述这些Session对象也是在内存中进行的.
比如把SessionId返回给服务器了,SessionId和Session对象是保存在服务器内存的,此时如果服务器重启,内存中的这些会话数据就没了.
但是浏览器中的SessionId这个Cookie还是存在的.
Session保存在内存中,这样的设定其实也不是很合理.
因此实践中往往会把会话保存在其它的介质上,来达到"持久化存储"目的.
②HttpSession 类中的相关方法:
一个 HttpSession 对象里面包含多个键值对.
方法 | 描述 |
Object getAttribute(String name) | 该方法返回在该 session 会话中具有指定名称的对象,如果没有 指定名称的对象,则返回 null. |
void setAttribute(String name, Object value) | 该方法使用指定的名称绑定一个对象到该 session 会话 |
boolean isNew() | 判定当前是否是新创建出的会话 |
Session存在的意义,也是为了让用户能够保存一些自定义的数据.
此处Session更像是一个Map<String.Object>.
此时,程序员想保持什么样的键值对,就可以直接进行设置了.
3.2 Session整体的存储的数据结构
Session在一个服务器上,存在很多份,每个用户都应该有一个自己的Session.
服务器同时会有多个用户,服务器就会使用Map的方式来组织多个Session.
多个客户端对应多个键值对,key是sessionId,value是Session对象.
这些会话里面,每个会话又可以存一些用户的键值对(自定义的).
4. 实现用户登陆
4.1 前端
当前,页面是一个form表单.
当用户点击登陆按钮,就会发起一个HTTP请求.
形如:
服务器这边就可以根据这个请求做出处理.
4.2 后端
4.2.1 登陆页面
@WebServlet("/login")
public class LoginServlet extends HelloServlet{
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取到用户的用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
if (username == null || password == null || username.equals("") || password.equals("")) {
//确定引用本身非空,内容非空
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("请求的参数不完整!");
return;
}
//2.验证用户名密码是否正确.
//正确的验证,是从数据库读取数据.
//注册账号,就会给数据库插入用户名和密码.
//登陆的时候,就是验证当前用户名是否存在,密码是否匹配.
//当前为了简单,先不引入数据库,直接通过硬编码的方式来判定用户密码
//此处约定,合法的用户名是zhangshan,密码123
if (!username.equals("zhangshan")){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("用户名错误!");
return;
}
if (!password.equals("123")){
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("密码错误!");
return;
}
//3.登陆成功!!
//此时就可以给用户创建会话
HttpSession session = req.getSession(true);
//在会话中,可以顺便保存点自定义的数据,比如保存一个登陆的时间戳.
//setAttribute后面的value是个Object,想存什么都可以.
session.setAttribute("username",username);
session.setAttribute("time",System.currentTimeMillis());
//4.让页面自动跳转到网站主页.
//此处约定主页的路径是index(也使用Servlet生成一个动态页面)
resp.sendRedirect("index");
}
}
4.2.2 跳转页面
//通过这个Servlet生成一个主页
@WebServlet("/index")
public class IndexServlet extends HelloServlet{
@Override
//重定向的请求是Get请求
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//先验证一下用户的登陆状态
//如果未登陆,就要求用户先登陆
HttpSession session = req.getSession(false);
if (session == null){
//用户未登陆
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("请先登陆!!!");
return;
}
//已经登陆成功.
//取出之前的attribute
String username = (String) session.getAttribute("username");
Long time = (Long) session.getAttribute("time");
System.out.println("username="+username + ", time= "+time);
//根据这样的内容去构造页面.
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("欢迎您, "+ username + "! 上次登陆时间: "+time);
}
}
4.2.3 总结
此时先访问index页面,是未登陆的状态:
此时故意输出用户名:
输入对的:
这个登陆时间,取决于我们的登陆时间,而不是访问时间.
总结:
这个程序就涉及到三个部分进行联动.
1.登陆页面(静态的html,使用form表单构造HTTP请求).
2.LoginServlet(doPost处理登陆的逻辑流程).
3.IndexServlet(doGet处理主页的生成).
在会话中存储的这些attribute,就相当于"埋下伏笔",后续其它页面中就可以使用到这里的东西.