一个 Cookie 是服务器在 HTTP 响应中发送给用户的一小段信息,用户的浏览器会在后续请求中将其发送回服务器。这种技术是许多交互式网站的基本构建块,它添加了状态,因此您可以构建身份验证(请参阅 会话)、购物车、用户偏好以及许多其他需要记住谁“已登录”的功能。
Remix 的 Cookie
接口为 Cookie 元数据提供了一个逻辑的、可重用的容器。
虽然您可以手动创建这些 Cookie,但更常见的是使用 会话存储。
在 Remix 中,您通常会在 loader
和/或 action
函数中(请参阅变异)处理 Cookie,因为这些是您需要读取和写入数据的地方。
假设您的电商网站上有一个横幅,提示用户查看您目前正在促销的商品。该横幅跨越主页顶部,并在侧面包含一个按钮,允许用户关闭横幅,以便他们至少一周内不再看到它。
首先,创建一个 Cookie
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
maxAge: 604_800, // one week
});
然后,您可以 import
该 Cookie 并将其用于您的 loader
和/或 action
。在这种情况下,loader
只是检查用户偏好的值,以便您可以在组件中使用它来决定是否渲染横幅。当单击按钮时,<form>
会调用服务器上的 action
并重新加载页面,此时不再显示横幅。
注意:我们建议(目前)您在 *.server.ts
文件中创建应用程序所需的所有 Cookie,并在路由模块中 import
它们。这允许 Remix 编译器正确地从浏览器构建中删除这些导入,因为它们在浏览器构建中不需要。我们希望最终能够消除此限制。
import type {
ActionFunctionArgs,
LoaderFunctionArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import {
useLoaderData,
Link,
Form,
} from "@remix-run/react";
import { userPrefs } from "~/cookies.server";
export async function loader({
request,
}: LoaderFunctionArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
return json({ showBanner: cookie.showBanner });
}
export async function action({
request,
}: ActionFunctionArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
const bodyParams = await request.formData();
if (bodyParams.get("bannerVisibility") === "hidden") {
cookie.showBanner = false;
}
return redirect("/", {
headers: {
"Set-Cookie": await userPrefs.serialize(cookie),
},
});
}
export default function Home() {
const { showBanner } = useLoaderData<typeof loader>();
return (
<div>
{showBanner ? (
<div>
<Link to="/sale">Don't miss our sale!</Link>
<Form method="post">
<input
type="hidden"
name="bannerVisibility"
value="hidden"
/>
<button type="submit">Hide</button>
</Form>
</div>
) : null}
<h1>Welcome!</h1>
</div>
);
}
Cookie 有 多个属性,用于控制它们的过期时间、访问方式以及发送位置。任何这些属性都可以在 createCookie(name, options)
中或在生成 Set-Cookie
标头时进行 serialize()
期间指定。
const cookie = createCookie("user-prefs", {
// These are defaults for this cookie.
path: "/",
sameSite: "lax",
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 60_000),
maxAge: 60,
});
// You can either use the defaults:
cookie.serialize(userPrefs);
// Or override individual ones as needed:
cookie.serialize(userPrefs, { sameSite: "strict" });
请阅读 有关这些属性的更多信息,以更好地了解它们的作用。
可以对 Cookie 进行签名,以便在接收时自动验证其内容。由于伪造 HTTP 标头相对容易,因此对于您不希望其他人伪造的任何信息(例如身份验证信息(请参阅 会话))来说,这是一个好主意。
要对 Cookie 进行签名,请在首次创建 Cookie 时提供一个或多个 secrets
。
const cookie = createCookie("user-prefs", {
secrets: ["s3cret1"],
});
具有一个或多个 secrets
的 Cookie 将以确保 Cookie 完整性的方式进行存储和验证。
可以通过将新的 secrets 添加到 secrets
数组的前面来轮换 Secrets。使用旧 Secrets 签名的 Cookie 仍将在 cookie.parse()
中成功解码,并且最新的 Secret(数组中的第一个)将始终用于对 cookie.serialize()
中创建的传出 Cookie 进行签名。
export const cookie = createCookie("user-prefs", {
secrets: ["n3wsecr3t", "olds3cret"],
});
import { cookie } from "~/cookies.server";
export async function loader({
request,
}: LoaderFunctionArgs) {
const oldCookie = request.headers.get("Cookie");
// oldCookie may have been signed with "olds3cret", but still parses ok
const value = await cookie.parse(oldCookie);
new Response("...", {
headers: {
// Set-Cookie is signed with "n3wsecr3t"
"Set-Cookie": await cookie.serialize(value),
},
});
}
createCookie
创建用于从服务器管理浏览器 Cookie 的逻辑容器。
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("cookie-name", {
// all of these are optional defaults that can be overridden at runtime
expires: new Date(Date.now() + 60_000),
httpOnly: true,
maxAge: 60,
path: "/",
sameSite: "lax",
secrets: ["s3cret1"],
secure: true,
});
要详细了解每个属性,请参阅 MDN Set-Cookie 文档。
isCookie
如果对象是 Remix Cookie 容器,则返回 true
。
import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true
Cookie 容器由 createCookie
返回,并具有一些属性和方法。
const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.
cookie.name
Cookie 的名称,用于 Cookie
和 Set-Cookie
HTTP 标头。
cookie.parse()
提取并返回给定 Cookie
标头中此 Cookie 的值。
const value = await cookie.parse(
request.headers.get("Cookie")
);
cookie.serialize()
序列化值并将其与此 Cookie 的选项组合以创建 Set-Cookie
标头,适用于传出 Response
。
new Response("...", {
headers: {
"Set-Cookie": await cookie.serialize({
showBanner: true,
}),
},
});
cookie.isSigned
如果 Cookie 使用任何 secrets
,则为 true
,否则为 false
。
let cookie = createCookie("user-prefs");
console.log(cookie.isSigned); // false
cookie = createCookie("user-prefs", {
secrets: ["soopersekrit"],
});
console.log(cookie.isSigned); // true
cookie.expires
此 Cookie 过期的 Date
。请注意,如果 Cookie 同时具有 maxAge
和 expires
,则此值将为当前时间加上 maxAge
值的日期,因为 Max-Age
的优先级高于 Expires
。
const cookie = createCookie("user-prefs", {
expires: new Date("2021-01-01"),
});
console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"