Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Redis

Nâng cao Remix Sessions với Upstash Redis: Session Store an toàn, scalable

Là full stack web framework, Remix cung cấp APIs xử lý các use case web server phổ biến. Bài này tập trung vào sessions và tại sao/cách dùng Upstash để lưu trữ chúng.

Session là gì?

Remix docs có giới thiệu rất tốt về sessions https://remix.run/docs/en/v1/api/remix#sessions

Vài từ - sessions là cơ chế cho phép server và client chia sẻ user data/state. Ví dụ: tracking authentication state, shopping cart status, flash messages, v.v.

Tại sao dùng Upstash Redis?

Session data lưu trên server. Khi deploy lên serverless infrastructure hoặc PAAS (Heroku), bạn không thể persist data trên file system của server vì nó có thể thay đổi mỗi request (serverless) hoặc mỗi deployment (PAAS). Để persist data, bạn nên lưu user data trên external database. Upstash Redis DB là giải pháp tuyệt vời vì:

  • Giống Redis, Sessions bản chất có cấu trúc key:value nơi key là session id và value là serialized data.
  • Redis có cơ chế hết hạn built-in giảm nhu cầu cleanup expired sessions.
  • Session có thể chứa sensitive user data và Upstash Redis mã hóa tất cả dữ liệu lưu trữ.
  • Upstash dùng HTTP REST API đơn giản. HTTP là cách dễ nhất giao tiếp từ serverless infrastructure.

Cách dùng Upstash làm session provider trong Redis

Bài này dựa trên Redis Session Storage Using Upstash Example tôi viết. Clone Remix repo và thử đi.

Bước 1 - Lấy Upstash API keys

  • Vào https://upstash.com/ tạo tài khoản mới
  • Tạo Redis DB mới
  • Sao chép UPSTASH_REDIS_REST_URL & UPSTASH_REDIS_REST_TOKEN lưu vào file .env ở root Remix project.
  • Cài dotenv - $ npm install --save-dev dotenv - Cho phép inject env variables từ file .env vừa tạo.
  • Vào package.json thay script dev từ remix dev thành dotenv/config node_modules/.bin/remix dev.

Bước 2 - Triển khai core createSessionStorage tạo Upstash session implementation

Remix cung cấp API tuyệt vời build Session integration dùng createSessionStorage. Triển khai function này để tích hợp Upstash.

// sessions/upstash.server.ts
import * as crypto from "crypto";
import { createSessionStorage } from "remix";

const upstashRedisRestUrl = process.env.UPSTASH_REDIS_REST_URL;
const headers = {
  Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`,
  Accept: "application/json",
  "Content-Type": "application/json",
};

const expiresToSeconds = (expires) => {
  const now = new Date();
  const expiresDate = new Date(expires);
  const secondsDelta = expiresDate.getSeconds() - now.getSeconds();
  return secondsDelta < 0 ? 0 : secondsDelta;
};

// Xem thêm https://remix.run/docs/en/v1/api/remix#createsessionstorage
export function createUpstashSessionStorage({ cookie }: any) {
  return createSessionStorage({
    cookie,
    async createData(data, expires) {
      // Tạo random id - lấy từ core `createFileSessionStorage` Remix function.
      const randomBytes = crypto.randomBytes(8);
      const id = Buffer.from(randomBytes).toString("hex");
      // Gọi Upstash Redis HTTP API. Set expiration theo cookie `expired property.
      // Lưu ý dùng `expiresToSeconds` chuyển date sang giây.
      await fetch(
        `${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
        {
          method: "post",
          body: JSON.stringify({ data }),
          headers,
        }
      );
      return id;
    },
    async readData(id) {
      const response = await fetch(`${upstashRedisRestUrl}/get/${id}`, { headers });
      try {
        const { result } = await response.json();
        return JSON.parse(result).data;
      } catch (error) {
        return null;
      }
    },
    async updateData(id, data, expires) {
      await fetch(
        `${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
        {
          method: "post",
          body: JSON.stringify({ data }),
          headers,
        }
      );
    },
    async deleteData(id) {
      await fetch(`${upstashRedisRestUrl}/del/${id}`, {
        method: "post",
        headers,
      });
    },
  });
}

Giải thích: Tạo file sessions/upstash.server.ts. Export function createUpstashSessionStorage. Function nhận parameter cookie (xem sau) và dùng core Remix createSessionStorage factory function implement session provider mới.

Bên trong function triển khai protocol createSessionStorage cho tạo session mới (createData), đọc session value (readData), cập nhật session value (updateData) và xóa session (deleteData).

Mỗi function dùng REST Upstash API tương tác với Redis Database.

Lưu ý:

  • Cookie truyền vào chứa cookie expiration date dạng js Date. Dùng expiresToSeconds chuyển sang giây - thứ Redis mong đợi.
  • Khi set cookie, đừng quên set expiration date. Redis tự xóa session khi hết hạn.
  • Dùng crypto tạo unique session id. Có thể dùng phương án khác. Tôi chọn cách này vì giống core createFileSessionStorage function.

Bước 3 - Dùng createSessionStorage trên app

Đã tạo session storage implementation, demo cách dùng.

Lưu ý từ đây không có gì đặc thù Upstash. Tất cả logic đóng gói trong sessions/upstash.server.ts.

// sessions.server.ts
import { createCookie } from "remix";
import { createUpstashSessionStorage } from "~/sessions/upstash.server";

// Set độ dài session. Ví dụ dùng duration rất ngắn để demo dễ dàng.
const EXPIRATION_DURATION_IN_SECONDS = 10;
const expires = new Date();
expires.setSeconds(expires.getSeconds() + EXPIRATION_DURATION_IN_SECONDS);

const sessionCookie = createCookie("__session", {
  secrets: ["r3m1xr0ck1"],
  sameSite: true,
  expires,
});

const { getSession, commitSession, destroySession } = createUpstashSessionStorage({ cookie: sessionCookie });
export { getSession, commitSession, destroySession };

Tạo file sessions.server.ts paste code trên. File export 3 functions getSession, commitSession, destroySession. Cho phép app tương tác với session. Cũng tạo cookie lưu reference đến session ở client.

Setup expiration duration theo business needs. Xem thêm MDN Cookies.

Sử dụng session trong Remix routes

Với Remix, định nghĩa session usage per route. Ví dụ dùng session trên routes/index.tsx. Chỉ demo cách dùng session API. Kết nối session với business logic cụ thể ngoài phạm vi bài này.

Nếu tìm ví dụ dùng session cho authentication xem đây

// routes/index.tsx
import type { LoaderFunction } from "remix";
import { json, useLoaderData } from "remix";
import { commitSession, getSession } from "~/sessions.server";

export const loader: LoaderFunction = async ({ request }) => {
  // Lấy session từ cookie
  const session = await getSession(request.headers.get("Cookie"));
  const myStoredData = session.get("myStoredData");
  // Nếu không tìm thấy session (chưa tạo hoặc hết hạn) tạo session mới.
  if (!myStoredData) {
    session.set("myStoredData", "Some data");
    return json(
      { message: "Created new session" },
      { headers: { "Set-Cookie": await commitSession(session) } }
    );
  }
  // Nếu session tìm thấy, hiển thị session info.
  return json({ message: `Showing Session info: ${myStoredData}` });
};

export default function () {
  const data = useLoaderData();
  return <div>{data.message}</div>;
}

Ví dụ demo xử lý 2 trạng thái session (User có session hoặc không). Nếu không có session, truy cập index sẽ tạo session mới lưu dummy data. Nếu có session hợp lệ (chưa hết hạn) route sẽ show session data.

Bước 4 - Deploy

Sau khi implement session dùng Upstash, bạn tự do chọn deployment strategy bất kỳ.

Đừng quên set Upstash environment variables.

Phụ lục

Dùng createFileSessionStorage cho local development và createUpstashSessionStorage cho staging/production

Có thể muốn develop offline. Khi develop local, thay createUpstashSessionStorage bằng createFileSessionStorage bằng cách detect NODE_ENV.

Sau khi test Upstash implementation works, sửa sessions/upstash.server.ts:

Thay đoạn này:

// from sessions/upstash.server.ts
const { getSession, commitSession, destroySession } = createUpstashSessionStorage({ cookie: sessionCookie });

Bằng:

// from sessions/upstash.server.ts
const { getSession, commitSession, destroySession } = (process.env.NODE_ENV === "development")
  ? createFileSessionStorage({ cookie: sessionCookie, dir: './sessions' })
  : createUpstashSessionStorage({ cookie: sessionCookie });

Bây giờ develop local sẽ dùng local file system thay vì gọi Upstash.

Kết luận

Trong bài này chúng ta thấy cách dùng Upstash Redis DB host session storage data. Remix API đóng gói cụ thể session storage implementation rất tốt, tích hợp straight forward. Muốn chơi với ví dụ, check ở Remix source code đây.

Enjoy.