Menu
CoddyTech

用 Cookie 记住信息

本课属于 Coddy Node.js 学习旅程的“身份验证与安全”单元。第 8 课,共 24 课。

HTTP 不会在请求之间记住你:每个请求都是独立的,服务器无法判断它们是否来自同一个浏览器。Cookie 可以解决这个问题。服务器会通过 Set-Cookie 标头进行响应,而浏览器在之后向该网站发送的每个请求中,都会通过 Cookie 标头发回这个值。

res.cookie(name, value) 用于设置一个 Cookie。req.get('cookie') 会读取浏览器发送的 Cookie 标头,并将其作为一行文本返回。Express 不会替你读取 Cookie 标头(cookie-parser 包可以做到),因此 cookies.js 为你提供了 parseCookies。每个值都会以文本形式传入。

app.get('/theme/:name', (req, res) => {
    res.cookie('theme', req.params.name);
    res.send('Saved');
});

app.get('/', (req, res) => {
    const { theme = 'light' } = parseCookies(req.get('cookie'));
    res.send(`Your theme is ${theme}`);
});
challenge icon

挑战

简单

GET /visit 应统计此浏览器调用它的次数。服务器不会保存计数。计数会存放在 Cookie 中,浏览器会在每次请求时将它带回来。

  1. 在 server.js 的 GET /visit 中,按照本课所示,使用 parseCookies 读取浏览器发送的 Cookie。计数存放在名为 visits 的 Cookie 中。它以文本形式传来,例如 "2"。首次访问时,该 Cookie 不存在,此时计数为 0。
  2. 将计数作为数字加 1,这样 "2" 会变成 3,而不是 "21"。
  3. 将 visits Cookie 设置为新的计数,这样浏览器下次就会把它发送回来。
  4. 以数字而不是文本的形式返回新的计数:{"visits": 3}。

试试看:按“运行”。发送 GET /visit:{"visits": 1}。再发送一次:{"visits": 2}。第三次发送:{"visits": 3}。打开该响应的 Headers 选项卡:Set-Cookie 显示 visits=3; Path=/。

亲手试试

import express from 'express';
import { parseCookies } from './cookies.js';
import { notes, users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';

const app = express();
app.use(express.json());

app.post('/signup', async (req, res) => {
    const username = String(req.body.username ?? '').trim().toLowerCase();
    const password = String(req.body.password ?? '');
    if (!username || password.length < 8) {
        return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
    }
    if (await users.findOne({ username })) {
        return res.status(409).json({ error: 'That username is taken' });
    }
    await users.insertOne({ username, passwordHash: await hashPassword(password) });
    res.status(201).json({ username });
});

app.post('/login', async (req, res) => {
    const username = String(req.body.username ?? '').trim().toLowerCase();
    const user = await users.findOne({ username });
    if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
        return res.status(401).json({ error: 'Wrong username or password' });
    }
    res.json({ username: user.username });
});

// 统计这个浏览器调用 GET /visit 的次数。
app.get('/visit', (req, res) => {
    const visits = 1;
    res.json({ visits });
});

// 笔记。目前任何人都可以读取和写入笔记。
app.get('/notes', async (req, res) => {
    res.json(await notes.find().toArray());
});

app.post('/notes', async (req, res) => {
    const note = { text: String(req.body.text ?? '') };
    await notes.insertOne(note);
    res.status(201).json(note);
});

app.listen(3000, () => {
    console.log('Vault is running on port 3000');
});
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

身份验证与安全中的所有课

自己练习: Express 在线编辑器