简体   繁体   English

Remix:中间件模式在每个请求的加载器之前运行代码?

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

Is there a recommended pattern in Remix for running common code on every request, and potentially adding context data to the request? Remix 中是否有推荐的模式用于在每个请求上运行通用代码,并可能将上下文数据添加到请求中? Like a middleware?像中间件? A usecase for this might be to do logging or auth, for example.例如,一个用例可能是进行日志记录或身份验证。

The one thing I've seen that seems similar to this is loader context via the getLoadContext API.我看到的一件事似乎与此类似,是通过getLoadContext API加载程序上下文 This lets you populate a context object which is passed as an arg to all route loaders.这使您可以填充context object,该上下文作为 arg 传递给所有路由加载器。

It does work, and initially seems like the way to do this, but the docs for it say...它确实有效,最初似乎是这样做的方式,但它的文档说......

It's a way to bridge the gap between the adapter's request/response API with your Remix app这是一种在适配器的请求/响应 API 与您的 Remix 应用程序之间架起桥梁的方法

This API is an escape hatch, it's uncommon to need it这个 API 是一个逃生舱口,很少需要它

...which makes me think otherwise, because ...这让我不这么认为,因为

  • This API is explicitly for custom integrations with the server runtime.此 API 明确用于与服务器运行时的自定义集成。 But it doesn't seem like middlewares should be specific to the server runtime - they should just be part of the 'application' level as a Remix feature.但似乎中间件不应该特定于服务器运行时——它们应该只是作为 Remix 功能的“应用程序”级别的一部分。

  • Running middlewares is a pretty common pattern in web frameworks!运行中间件是 web 框架中非常常见的模式!

So, does Remix have any better pattern for middleware that runs before every loader?那么,对于在每个加载器之前运行的中间件,Remix 是否有更好的模式?

Instead of middleware, you can call a function directly inside the loader, this will also be more explicit.您可以直接在加载器内部调用 function 而不是中间件,这也将更加明确。 If you want to early return a response from those "middlewares" Remix let you throw the response object.如果您想尽早从那些“中间件”Remix 中返回响应,让您抛出响应 object。

For example, if you wanted to check the user has a certain role you could create this function:例如,如果您想检查用户是否具有某个角色,您可以创建此 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 });
}

And in any loader call it this way:在任何加载器中都这样称呼它:

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
};

Another example could be to require HTTPS另一个例子可能是要求 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
};

There is no way inside Remix to run code before loaders. Remix 内部无法在加载程序之前运行代码。

As you found out, there is the loader context but it runs even before remix starts to do its job (so you won't know which route modules are matched for example).正如您所发现的,有 loader 上下文,但它甚至在 remix 开始执行其工作之前就运行(例如,您将不知道匹配哪些路由模块)。

You can also run arbitrary code before handing the request to remix in the JS file where you use the adapter for the platform you're deploying to (this depend on the starter you used. This file doesn't exist if you've chosen remix server as your server)您还可以在将请求重新混合到 JS 文件中之前运行任意代码,在该文件中您将适配器用于您要部署到的平台(这取决于您使用的启动器。如果您选择了重新混合,则此文件不存在服务器作为您的服务器)

For now it should work for some use cases, but I agree this is a missing feature in remix for now.现在它应该适用于某些用例,但我同意这是目前 remix 中缺少的功能。

Inside app/root.tsxapp/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 {};
};

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

here is my middlewares implementation for remix with typescript,it's works well这是我与 typescript 混音的中间件实现,效果很好

ctx.return(something) === useLoaderData() 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 is same as koa; compose 和 koa 一样;

here is the compose's implementation这是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