cookie 是服务器通过 HTTP 响应发送给用户的一小段信息,用户的浏览器将在后续请求中将其返回。此技术是许多交互式网站的基本构建块,它添加了状态,以便你可以构建身份验证(参见 sessions)、购物车、用户偏好设置以及许多其他需要记住谁是 "已登录" 的功能。
¥A cookie is a small piece of information that your server sends someone in a HTTP response that their browser will send back on subsequent requests. This technique is a fundamental building block of many interactive websites that adds state so you can build authentication (see sessions), shopping carts, user preferences, and many other features that require remembering who is "logged in".
Remix 的 Cookie
接口提供了一个逻辑性强、可重复使用的 Cookie 元数据容器。
¥Remix's Cookie
interface provides a logical, reusable container for cookie metadata.
¥Using cookies
虽然你可以手动创建这些 Cookie,但更常见的是使用 会话存储。
¥While you may create these cookies manually, it is more common to use a session storage.
在 Remix 中,你通常会在 loader
和/或 action
函数中使用 Cookie(请参阅 mutations),因为这些函数是你需要读取和写入数据的地方。
¥In Remix, you will typically work with cookies in your loader
and/or action
functions (see mutations), since those are the places where you need to read and write data.
假设你的电商网站上有一个横幅广告,提示用户查看你当前正在销售的商品。横幅广告横跨主页顶部,侧面有一个按钮,用户可以关闭横幅广告,这样至少一周内都不会看到它。
¥Let's say you have a banner on your e-commerce site that prompts users to check out the items you currently have on sale. The banner spans the top of your homepage and includes a button on the side that allows the user to dismiss the banner so they don't see it for at least another week.
首先,创建一个 Cookie:
¥First, create a 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
并重新加载页面(不显示横幅)。
¥Then, you can import
the cookie and use it in your loader
and/or action
. The loader
in this case just checks the value of the user preference so you can use it in your component for deciding whether to render the banner. When the button is clicked, the <form>
calls the action
on the server and reloads the page without the banner.
注意:我们建议(目前)你在 *.server.ts
文件中创建应用所需的所有 Cookie,然后将它们 import
到路由模块中。这允许 Remix 编译器正确地从浏览器构建中移除不需要的导入。我们希望最终能消除这个警告。
¥Note: We recommend (for now) that you create all the cookies your app needs in a *.server.ts
file and import
them into your route modules. This allows the Remix compiler to correctly prune these imports out of the browser build where they are not needed. We hope to eventually remove this caveat.
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 attributes
Cookie 拥有 几个属性 属性,用于控制其过期时间、访问方式以及发送目的地。这些属性可以在 createCookie(name, options)
中指定,也可以在生成 Set-Cookie
标头的 serialize()
中指定。
¥Cookies have several attributes that control when they expire, how they are accessed, and where they are sent. Any of these attributes may be specified either in createCookie(name, options)
, or during serialize()
when the Set-Cookie
header is generated.
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" });
请阅读 关于这些属性的更多信息 以更好地了解它们的作用。
¥Please read more info about these attributes to get a better understanding of what they do.
¥Signing cookies
可以对 Cookie 进行签名,以便在收到 Cookie 时自动验证其内容。由于伪造 HTTP 标头相对容易,因此对于任何你不希望他人伪造的信息(例如身份验证信息,请参阅 sessions)来说,这都是一个好主意。
¥It is possible to sign a cookie to automatically verify its contents when it is received. Since it's relatively easy to spoof HTTP headers, this is a good idea for any information that you do not want someone to be able to fake, like authentication information (see sessions).
要对 Cookie 进行签名,请在首次创建 Cookie 时提供一个或多个 secrets
:
¥To sign a cookie, provide one or more secrets
when you first create the cookie:
const cookie = createCookie("user-prefs", {
secrets: ["s3cret1"],
});
具有一个或多个 secrets
属性的 Cookie 将以确保 Cookie 完整性的方式进行存储和验证。
¥Cookies that have one or more secrets
will be stored and verified in a way that ensures the cookie's integrity.
可以通过将新的密钥添加到 secrets
数组的前面来轮换密钥。使用旧密钥签名的 Cookie 仍可在 cookie.parse()
中成功解码,并且最新的密钥(数组中的第一个)将始终用于签名在 cookie.serialize()
中创建的传出 Cookie。
¥Secrets may be rotated by adding new secrets to the front of the secrets
array. Cookies that have been signed with old secrets will still be decoded successfully in cookie.parse()
, and the newest secret (the first one in the array) will always be used to sign outgoing cookies created in cookie.serialize()
.
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。
¥Creates a logical container for managing a browser cookie from the server.
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 文档。
¥To learn more about each attribute, please see the MDN Set-Cookie docs.
isCookie
如果对象是 Remix Cookie 容器,则返回 true
。
¥Returns true
if an object is a Remix cookie container.
import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true
createCookie
返回一个 Cookie 容器,它具有一些属性和方法。
¥A cookie container is returned from createCookie
and has a handful of properties and methods.
const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.
cookie.name
Cookie 的名称,用于 Cookie
和 Set-Cookie
的 HTTP 标头。
¥The name of the cookie, used in Cookie
and Set-Cookie
HTTP headers.
cookie.parse()
提取并返回给定 Cookie
标头中此 cookie 的值。
¥Extracts and returns the value of this cookie in a given Cookie
header.
const value = await cookie.parse(
request.headers.get("Cookie")
);
cookie.serialize()
序列化一个值并将其与此 Cookie 的选项组合以创建 Set-Cookie
标头,适用于传出的 Response
。
¥Serializes a value and combines it with this cookie's options to create a Set-Cookie
header, suitable for use in an outgoing Response
.
new Response("...", {
headers: {
"Set-Cookie": await cookie.serialize({
showBanner: true,
}),
},
});
cookie.isSigned
如果 cookie 使用任何 secrets
,则为 true
,否则为 false
。
¥Will be true
if the cookie uses any secrets
, false
otherwise.
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
。
¥The Date
on which this cookie expires. Note that if a cookie has both maxAge
and expires
, this value will be the date at the current time plus the maxAge
value since Max-Age
takes precedence over Expires
.
const cookie = createCookie("user-prefs", {
expires: new Date("2021-01-01"),
});
console.log(cookie.expires); // "2020-01-01T00:00:00.000Z"