8.1.4 Cookie

284 阅读4分钟

背景

       本文将介绍 Nodejs 相关技术,让更多前端技术人员突破技术壁垒,向全栈靠拢
尝试坚持学习新技术、写技术文章,学而时习之,不亦说乎!

1、一叶障目,不见泰山,对技术怀有一颗敬畏之心
2、脚踏实地,一步一个脚印,最后肯定有回报
3、水滴石穿、熟能生巧,多敲多 debug

初始 cookie

在大多数 Web 应用中,能依靠请求路径和请求参数来做大部分的工作,但是 HTTP 是一个无状态的协议,但是有些业务是需要一定的状态来区分不同用户之间的身份。最早的用来区别所用的技术就是 Cookie (曲奇饼)

Cookie 最早由文本浏览器 Lynx 合作开发者 Liu Montulli 在 1994 年网景公司开发 Netscape 浏览器的第一个版本时发明。它能记录服务器与客户端之间的状态,最早的用处就是用来判断用户是否第一次访问网站。在 1997 年形状规范 RFC 2109, 目前最新的规范为 RFC 6265, 它是一个由浏览器和服务器共同协作实现的规范

Cookie 的处理有以下几步

  1. 服务器向客户端发送 Cookie
  2. 浏览器将 Cookie 保存(保存在本地,小文本文件)
  3. 之后每次浏览器发起请求时,都会将 Cookie 放请求头传给服务器

浏览器查看 Cookie

image.png

根据实际情况,查看到 Cookie 值得格式是 key=value; key2=value2, ... 形式的,因为要达到易用效果,我们要做封包和解包两部分代码

开始动手

目录结构

├── ch8
│   ├── 8.1.4Cookie.js
├── package.json
├── index.js

代码内容

// ./ch8/8.1.4Cookie.js

/**
 * 解包, 将字符串解析成对象, 挂载到 req 上供后续使用
 * @param {*} req 
 * @param {*} res 
 * @returns {object}
 */
const parseCookie = (req, res) => {
    let cookie = req.headers.cookie;
    const cookies = {};
    req.cookies = cookies;
    
    if(!cookie) {
        return cookies;
    }
    
    cookie = cookie.replace(";", "&");
    const searchParams = new URLSearchParams(cookie);
    const entries = searchParams.entries();
    
    for(const [key, value] of entries) {
        cookies[key.trim()] = value;
    }
    
    return cookies;
}

/**
 * 封包, 将 key、value、cookie options 封装字符串格式
 * @param {string} name 
 * @param {string} val 
 * @param {object} opt 
 * @returns {string}
 */
const serialize = (name, val, opts) => {
    const pairs = [name + "=" + encodeURIComponent(val)];
    opts = opts || {};
    if (opt.maxAge) pairs.push('Max-Age=' + opt.maxAge);
    if (opt.domain) pairs.push('Domain=' + opt.domain);
    if (opt.path) pairs.push('Path=' + opt.path);
    if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());
    if (opt.httpOnly) pairs.push('HttpOnly');
    if (opt.secure) pairs.push('Secure');
    return pairs.join("; ");
}

module.exports = {
    parseCookie,
    serialize
}
// ./index.js

const http = require("http");
const { parseCookie, serialize } = require("./ch8/8.1.4Cookie.js");

const _headers = {
    "Content-Type": "text/html; charset=UTF-8"
}

const server = http.createServer((req, res) => {
    parseCookie(req, res);
    handle(req, res);
});

const handle = (req, res) => {
    const cookies = req.cookies;
    if(!cookies.isVisit) {
        /*
         * 第一次请求,没有携带对应的 cookie, 
         * 服务器向客户端发送 cookie, 客户端收到后, 会保存到本地
         * 下次请求会将保存的 cookie 放请求头携带给服务器
         */
        res.setHeader("Set-Cookie", serialize("isVisit", "1"));
        res.writeHead(200, Object.assign(_headers));
        res.end("欢迎第一次访问");
    } else {
        res.writeHead(200, Object.assign(_headers));
        res.end("欢迎再次访问");
    }
}

// 监听 9527 端口
server.listen(9527, "127.0.0.1");

执行命令

node ./index.js

浏览器访问

http://127.0.0.1:9527

浏览器表现(第一次访问)

image.png 请求头没有携带 cookie 信息到服务器
响应头携带 Set-Cookie:isVisit=1 给浏览器, 浏览器会保存到本地

浏览器表现(第二次访问)

image.png 请求头携带 Cookie:isVisit=1 给服务器

image.png F12控制台 --> Application --> Storage --> Cookies 可以查看到本地保存了哪些值

补充

Cookie 格式
Set-Cookie: name=value; Path=/; Expires=Sun, 23-Apr-23 09:01:35 GMT; Domain=.domain.com;

  • path 表示这个 cookie 影响到的路径,当前访问的路径不满足该匹配时,浏览器则不发送这个 Cookie
  • Expires 和 Max-Age 标识这个 cookie 何时过期,如果不设置,则是会话级别的,即关闭浏览器就会删除这个 cookie, 如果设置了过期时间, 浏览器会把 cookie 内容写到磁盘中并保存
  • HttpOnly 告知浏览器不允许通过脚本 document.cookie 去更改这个 Cookie 值,实时上,设置 HttpOnly 之后,这个值在 document.cookie 中不可见。但是在 HTTP 请求的过程中,依然会发送这个 Cookie 到服务器端。
  • Secure 当设置为 true 时候,在 HTTP 中是无效的,在 HTTPS 中才有效

Cookie 的性能影响

虽然 Cookie 解决了 HTTP 无状态的燃眉之急,但是也有它的弊端,由于浏览器每次请求都会发送本地储存的 Cookie 到服务器端,一旦设置的 Cookie 过多,将会导致报头较大。大多数的 Cookie 并不需要每次都用得上,因为这会浪费部分宽带。最为严重的问题就是可以在前端直接修改,因此数据就极容易篡改和伪造。

下一篇 Session 将优化以上的问题