簡體   English   中英

Remix:中間件模式在每個請求的加載器之前運行代碼?

[英]Remix: middleware pattern to run code before loader on every request?

Remix 中是否有推薦的模式用於在每個請求上運行通用代碼,並可能將上下文數據添加到請求中? 像中間件? 例如,一個用例可能是進行日志記錄或身份驗證。

我看到的一件事似乎與此類似,是通過getLoadContext API加載程序上下文 這使您可以填充context object,該上下文作為 arg 傳遞給所有路由加載器。

它確實有效,最初似乎是這樣做的方式,但它的文檔說......

這是一種在適配器的請求/響應 API 與您的 Remix 應用程序之間架起橋梁的方法

這個 API 是一個逃生艙口,很少需要它

...這讓我不這么認為,因為

  • 此 API 明確用於與服務器運行時的自定義集成。 但似乎中間件不應該特定於服務器運行時——它們應該只是作為 Remix 功能的“應用程序”級別的一部分。

  • 運行中間件是 web 框架中非常常見的模式!

那么,對於在每個加載器之前運行的中間件,Remix 是否有更好的模式?

您可以直接在加載器內部調用 function 而不是中間件,這也將更加明確。 如果您想盡早從那些“中間件”Remix 中返回響應,讓您拋出響應 object。

例如,如果您想檢查用戶是否具有某個角色,您可以創建此 function:

async function verifyUserRole(request: Request, expectedRole: string) {
  let user = await getAuthenticatedUser(request); // somehow get the user
  if (user.role === expectedRole) return user;
  throw json({ message: "Forbidden" }, { status: 403 });
}

在任何加載器中都這樣稱呼它:

let loader: LoaderFunction = async ({ request }) => {
  let user = await verifyUserRole(request, "admin");
  // code here will only run if user is an admin
  // and you'll also get the user object at the same time
};

另一個例子可能是要求 HTTPS

function requireHTTPS(request: Request) {
  let url = new URL(request.url);
  if (url.protocol === "https:") return;
  url.protocol = "https:";
  throw redirect(url.toString());
}

let loader: LoaderFunction = async ({ request }) => {
  await requireHTTPS(request);
  // run your loader (or action) code here
};

Remix 內部無法在加載程序之前運行代碼。

正如您所發現的,有 loader 上下文,但它甚至在 remix 開始執行其工作之前就運行(例如,您將不知道匹配哪些路由模塊)。

您還可以在將請求重新混合到 JS 文件中之前運行任意代碼,在該文件中您將適配器用於您要部署到的平台(這取決於您使用的啟動器。如果您選擇了重新混合,則此文件不存在服務器作為您的服務器)

現在它應該適用於某些用例,但我同意這是目前 remix 中缺少的功能。

app/root.tsx里面

export let loader: LoaderFunction = ({ request }) => {

const url = new URL(request.url);
const hostname = url.hostname;
const proto = request.headers.get("X-Forwarded-Proto") ?? url.protocol;

url.host =
  request.headers.get("X-Forwarded-Host") ??
  request.headers.get("host") ??
  url.host;
  url.protocol = "https:";

if (proto === "http" && hostname !== "localhost") {
  return redirect(url.toString(), {
    headers: {
      "X-Forwarded-Proto": "https",
    },
  });
}
  return {};
};

來源: https://github.com/remix-run/remix-jokes/blob/8f786d9d7fa7ea62203e87c1e0bdaa9bda3b28af/app/root.tsx#L25-L46

這是我與 typescript 混音的中間件實現,效果很好

ctx.return(something) === useLoaderData()

import compose from '@utils/compose';
export default function Index() {
    const ctx = useLoaderData();
    return <div>{ctx.name}</div>;
}

type DefaultCtx = {
    name: string;
} & Request;

export const loader =(...args)=>compose<DefaultCtx>(
    async (ctx, next) => {
        ctx.name = 'first';
        await next();
    },
    async (ctx, next) => {
        ctx.name = 'secnod';
        await next();
    },
    async (ctx, next) => {
        ctx.name = 'third';
        ctx.return(ctx);
        await next();
    }
)(args);

compose 和 koa 一樣;

這是compose的實現

type Next = () => Promise<void>;
type Context = {};
type Middle<T = {}> = (ctx: Context & T, next: Next) => void;
const compose = <T>(...middlewares: Middle<T>[]) => {
    return middlewares.reverse().reduce(
        (dispatch, middleware) => {
            return async ctx =>
                middleware(ctx, async () => dispatch(ctx, async () => {}));
        },
        async () => {}
    );
};
export type Middleware<T = {}, P = unknown> = (
    ctx: Context & T & { return: (param: P) => void },
    next: Next
) => void;
const returnEarly: Middleware = async (ctx, next) => {
    return new Promise<any>(async resolve => {
        ctx.return = resolve;
        await next();
    });
};


const componseWithReturn = <T>(...middlewares: Middleware<T>[]) =>
    compose(returnEarly, ...middlewares) as (ctx: T) => void;

export default componseWithReturn;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM