HTTP中的Cookie、Set-Cookie

请求头Cookie、响应头Set-Cookie

但是,我们又需要在不同请求-响应之间,来区分请求-响应是不是同一个用户发起的。


比如:用户购买越多,折扣越多的活动,就需要我们分辨出不同请求-响应之间的逻辑关系

    // 专门用来发凭证(cookie)的
    @GetMapping("/set-cookie")
    public void setCookie(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // servlet 里的处理方式
        // 不要用中文,因为 cookie 的设置和携带是放在请求响应头中的,字符集编码是西文格式,不支持中文
        Cookie cookie1 = new Cookie("name", "chen");
        Cookie cookie2 = new Cookie("learning", "java");
        Cookie cookie3 = new Cookie("time", "2022");

        response.addCookie(cookie1);
        response.addCookie(cookie2);
        response.addCookie(cookie3);

        // 设置响应体的字符集编码
        response.setCharacterEncoding("utf-8");
        // 设置响应体的格式 —— 纯文本
        response.setContentType("text/plain");
        // 写响应内容
        response.getWriter().println("cookie 设置成功,请观察开发者工具的 网络面板 和 应用面板");
    }

    // 记录用户访问次数
    private final Map<String, Integer> countMap = new HashMap<>();  // 这个 map 就是就是房子
    private final Map<String, Map<String, Object>> 模拟session的结构 = new HashMap<>();

    // 模拟一个 session
    @GetMapping("/use-cookie")
    public void useCookie(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String id = null;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("id")) {
                    id = cookie.getValue();
                }
            }
        }

        Integer count = null;
        if (id == null) {
            // cookie 中没有携带 id,认为是用户第一次访问

            // 将当前时间戳当作这次用户的访问标识
            long ts = System.currentTimeMillis();
            System.out.println("是第一次访问");
            id = String.valueOf(ts);
            Cookie cookie = new Cookie("id", id);
            response.addCookie(cookie);

            count = 1;
            countMap.put(id, count);
        } else {
            // cookie 中携带了 id,说明之前是访问过的
            System.out.println("不是第一次访问");

            count = countMap.get(id);
            if (count == null) {
                // 由于程序重启,内存中的 countMap 的数据丢了
                // 所以当成第一次吧
                count = 1;
            } else {
                count += 1;
            }
            countMap.put(id, count);
        }

        System.out.println("现在统计的所有人的访问次数:");
        for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
            System.out.printf("%s => %d\r\n", entry.getKey(), entry.getValue());
        }


        // 设置响应体的字符集编码
        response.setCharacterEncoding("utf-8");
        // 设置响应体的格式 —— 纯文本
        response.setContentType("text/plain");
        // 写响应内容
        if (count == 1) {
            response.getWriter().println("第 1 次访问,您的 id 是: " + id);
        } else {
            response.getWriter().printf("欢迎 %s 的第 %d 次访问\r\n", id, count);
        }
    }

Session会话

 

Session是保存在服务器的,专属某次会话的,一组数据。可以跨请求访问到(生命周期是跨请求存在的)。通常: Session 中保存的数据也可以视为name-value
一般利用Cookie设置session-id,这样,客户端和服务器之间仍然使用cookie机制,只是cookie 只传递id即可。主要数据全部保存在服务器。

Cookie和Session在实际场景应用

Cookie和Session在实际场景中最常见的应用——在线用户管理(如何在一次HTTP请求-响应中判断用户是否登录,以及登录用户是谁)
用户管理:用户注册(建档)、用户登录(当前用户在线)、用户退出(当前用户下线)、用户注销(销档)、
获取当前登录用户(currentUser):判断用户是否在线;如果在线,得到当前用户是谁。

和Cookie、Session有关系的,其实是在线用户状态的维护:用户登录、用户退出、获取当前登录用户信息;当前用户保存Session中。

用户注册:为用户建档,为用户信息做持久化保存——在数据库中维护一条用户信息,
记录用户基本信息。用户登录时通过验证这份基本信息来证明“你”是“你”

用户名+密码的方案——设计一个最简单的数据库表:
用户表(users)

{该用户的唯一标识符(uid) 、

该用户的用名——登录时唯一验证,需要保持记录的唯一性(username)、

密码(暂时明文)}

用户注册示例

数据库准备

 

注册场景

    // 1. 支持 POST
    // 2. 资源路径是 /register.do
    @PostMapping("/register.do")
    // 3. 希望结果是重定向,所以,不需要使用 @ResponseBody 注解修饰方法
    // 4. 方法的返回值类型是 String,到时候会返回 "redirect:..."
    // 5. 需要读取用户提交的 <input type="text" name="username" placeholder="用户名"> 和 <input type="text" name="password" placeholder="密码">
    // 5. 所以,方法的形参有两个,分别使用 @RequestParam 修饰
    public String register(@RequestParam("username") String username, @RequestParam("password") String password) {
        System.out.println("用户注册:username = " + username + ", password = " + password);

        // 1. TODO: 本来应该要去完成的参数合法性校验
        // 2. 执行 SQL 语句
        String sql = "insert into users (username, password) values (?, ?)";
        try (Connection c = DBUtil.connection()) {
            try (PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setString(1, username);
                ps.setString(2, password);

                ps.executeUpdate();
            }
        } catch (SQLException exc) {
            System.out.println("用户注册失败");
            exc.printStackTrace(System.out);
                return "redirect:/register.html";   // 失败后重定向到 注册页
        }

        System.out.println("用户注册成功");
        return "redirect:/login.html";    // 目前用户最终看到 404
    }

登录场景

    // 准备 POST /login.do 动态资源
    @PostMapping("/login.do")
    // 要重定向,不需要 @ResponseBody 注解修饰方法
    // 返回值类型是 String
    // 要读取用户输入的用户名 + 密码信息,和注册一样
    // 要创建 session 对象,所以,形参中有 HttpServletRequest request
    public String login(
            @RequestParam("username") String usernameInput,
            @RequestParam("password") String passwordInput,
            HttpServletRequest request) {
        System.out.println("用户登录:username = " + usernameInput + ", password = " + passwordInput);
        // TODO: 参数合法性校验

        // 通过数据库查询该用户的信息
//        Integer uid = null;     // 使用 Integer 而不是 int,是 Integer 可以保存 null 这种特殊值
//        String username = null;
//        String password = null;
        User user = null;

        try (Connection c = DBUtil.connection()) {
            String sql = "select uid, username, password from users where username = ? and password = ?";
            try (PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setString(1, usernameInput); // 把用户填写的用户名添加到 SQL 参数
                ps.setString(2, passwordInput); // 同理,密码

                // 带结果的,并且结果只有 1 条 或者 0 条
                try (ResultSet rs = ps.executeQuery()) {
                    if (rs.next()) {
                        // 查询到了 1 条记录,说明用户名 + 密码正确
                        int uid = rs.getInt("uid");
                        String username = rs.getString("username");    // 实际上,这两个字段可以不查询
                        String password = rs.getString("password");    // 实际上,这两个字段可以不查询

                        user = new User(uid, username, password);
                    } else {
                        // 一条记录都没有查询到,这里什么都不干
                        // 这里加上 else 的目的是为了写这个注释,以后我就不写了
                    }
                }
            }
        } catch (SQLException exc) {
            System.out.println("登录失败 by 数据库 SQL 执行失败,如果符合 HTTP 语义,应该是 500 错误");
            exc.printStackTrace(System.out);
            return "redirect:/login.html";
        }

        // 根据 uid 或者 username 或者 password 值来判断用户是否登录成功了,其中一个就行了
        if (user == null) {
            // 登录失败了
            System.out.println("登录失败 by 用户输入的参数有错误,如果符合 HTTP 语义,应该是 4XX 错误");
            return "redirect:/login.html";
        }

        // 登录成功
        // 获取 session 对象
        HttpSession session = request.getSession(); // 不带参数,默认是 create = true
        // 由于之前很大可能是没有 session 的
        // 所以内部所作的工作是
        // 1. 创建一个随机的 <session-id>
        // 2. 为这个 <session-id> 分配一个 HttpSession 对象(Map<String, Object> 结构)
        // 3. 响应中设置 Cookie(Set-Cookie):JSESSIONID=<session-id>
        // 4. 所以,应该能在前端看到浏览器中有个 JSESSIONID 的 cookie
        // 把当前用户的信息(uid、username、password)保存到 session 中,key 我们随意指定

        // currentUser.uid、currentUser.username、currentUser.password
//        session.setAttribute("currentUser.uid", uid);
//        session.setAttribute("currentUser.username", username);
//        session.setAttribute("currentUser.password", password);
        session.setAttribute("currentUser", user);

        System.out.println("登录成功");
        return "redirect:/";    // 404
    }

    // GET /
    @GetMapping("/")
    // 在响应体中输入内容,所以 @ResponseBody 注解修饰方法 + 方法返回值类型是 String
    // 响应的 Content-Type 是 HTML,返回值直接当成 HTML 内容在对待
    // 由于需要获取 HttpSession 对象,所以,形参中有 HttpServletRequest request
    @ResponseBody
    public String getCurrentUser(HttpServletRequest request) {
//        Integer uid = null;
//        String username = null;
//        String password = null;
        User currentUser = null;

        HttpSession session = request.getSession(false);
        if (session != null) {
            System.out.println("有 session 对象");
            // 说明是有 Session 的
            // 获取的时候,必须保证 key 和 登录场景下 setAttribute 时的 key 一致
            // getAttribute(...) 的返回值类型是 Object
            // Object 是 Integer 的上级类型
            // Object -> Integer 是向下转型
            // 向下转型是不自然的,是有风险的,所以程序员(我们)明确告诉编译器是我要转的,职责我承担(有异常我自己弄)
            // 需要通过类型强制转换 (Integer)
//            uid = (Integer) session.getAttribute("currentUser.uid");
//            username = (String) session.getAttribute("currentUser.username");
//            password = (String) session.getAttribute("currentUser.password");
            currentUser = (User) session.getAttribute("currentUser");

            // 如果当时没有设置,getAttribute 的返回值是 null
        } else {
            System.out.println("没有 session 对象");
            // 反之没有 session,什么都不需要做
        }

        if (currentUser == null) {
            System.out.println("没有拿到 uid");
            // session 可能是 null 或者 session.getAttribute("currentUser.uid") 是 null
            // 不管是哪种可能,都代表本次请求-响应的用户,没有经历过登录的流程,也就是用户未登录
            return "用户未登录,请到 <a href='/login.html'>登录页</a> 进行登录";
        } else {
            System.out.println("拿到 uid 了");
//            return String.format("当前登录用户为: uid = %d, username = %s, password = %s", uid, username, password);
            return String.format("当前登录用户为: %s", currentUser);   // 这里虽然没写,但实际就是调用的 currnetUser.toString()
        }
    }

用户退出

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瘦皮猴117

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值