Ремикс: шаблон промежуточного программного обеспечения для запуска кода перед загрузчиком при каждом запросе?
Есть ли в Remix рекомендуемый шаблон для запуска общего кода при каждом запросе и, возможно, добавления данных контекста в запрос? Как промежуточное ПО? Примером использования для этого может быть, например, ведение журнала или авторизация.
Единственное, что я видел, похожее на это, - это через
getLoadContext
API. Это позволяет вам заполнить
context
объект, который передается в качестве аргумента всем загрузчикам маршрутов.
Он действительно работает, и сначала кажется, что это способ сделать это, но контекст загрузчикав документации к нему говорится ...
Это способ преодолеть разрыв между API запроса / ответа адаптера и вашим приложением Remix.
Этот API - спасательный люк, он редко может понадобиться
... что заставляет меня думать иначе, потому что
Этот API предназначен специально для пользовательской интеграции со средой выполнения сервера. Но не похоже, что промежуточное ПО должно быть специфичным для среды выполнения сервера - оно должно быть просто частью уровня «приложения» как функция Remix.
Запуск промежуточного программного обеспечения - довольно распространенный шаблон в веб-фреймворках!
Итак, есть ли у Remix лучший образец для промежуточного программного обеспечения, которое запускается перед каждым загрузчиком?
4 ответа
Вместо промежуточного ПО можно вызвать функцию прямо внутри загрузчика, это тоже будет более явно. Если вы хотите досрочно вернуть ответ от этих «промежуточных программ», Remix позволит вам выбросить объект ответа.
Например, если вы хотите проверить, что у пользователя есть определенная роль, вы можете создать эту функцию:
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 нет возможности запускать код перед загрузчиками.
Как вы узнали, контекст загрузчика есть, но он запускается еще до того, как remix начнет выполнять свою работу (так что вы, например, не будете знать, какие модули маршрута совпадают).
Вы также можете запустить произвольный код перед передачей запроса на ремикс в файле JS, где вы используете адаптер для платформы, на которую вы развертываете (это зависит от используемого вами стартера. Этот файл не существует, если вы выбрали ремикс сервер как ваш сервер)
На данный момент это должно работать для некоторых вариантов использования, но я согласен, что на данный момент эта функция отсутствует в ремиксе.
Внутри
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 {};
};
вот моя реализация промежуточного программного обеспечения для ремикса с машинописью, она работает хорошо
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);
состав такой же, как коа;
вот реализация композиции
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;