【问题标题】:how can i chain multiple middlewares to one route?如何将多个中间件链接到一条路线?
【发布时间】:2022-10-06 01:13:27
【问题描述】:

我想像这样在 deno Oak 中链接 2 个中间件函数:

const addProductPanier = async(req:any,res:any) => {...}
const showPanier = async(ctx:any) => {...}

router.post(\'/OBV/panier\',addProductPanier).post(\'/OBV/panier\',showPanier);

我尝试了很多方法并在 Oak 文档中搜索了解决方案,但是 .post 中的字符串路径不能相同,我需要例如:

router.post(\'/OBV/panier\',addProductPanier).post(\'/OBV/panier/one\',showPanier);

我还尝试将 2 个中间件合并为一个,它在几天前工作,但由于某种未知原因,它不再工作,回复我 response.render() 不是一个函数。如您所见,我将它们分开,让 addProductPanier 将数据发送到我的数据库,并使用 showPanier 获取这些数据并使用 ctx.render() 将其显示在我的 html 页面上。

那么你知道如何将多个中间件链接到一个路由吗?

    标签: typescript ejs deno viewengine oak


    【解决方案1】:

    答案摘要

    您可以使用 Oak 的中间件组合函数 (composeMiddlware),也可以简单地将每个中间件函数作为位置参数提供给路由器方法。


    指导说明

    因为您的问题中没有minimal, reproducible example,所以我将在下面以简单的问候应用程序的形式提供一个,并用它来解决您的问题,详细说明在同一路线上编写中间件的两种方法。

    有用的参考文档:

    示例应用说明

    假设我们要创建一个 Web 服务器,它应该响应 /greet 处的 GET 请求,并允许一个 optional route parameter name 作为问候名称,所以路由看起来像这样:/greet/:name?。当该路由匹配时,服务器应该使用单独的中间件来:

    • 记录name路由参数的值(在服务器控制台中),然后
    • 以纯文本问候消息进行响应。

    上面描述的中间件函数可能如下所示:

    ./middleware.ts:

    import { type RouterMiddleware } from "https://deno.land/x/oak@v10.6.0/mod.ts";
    
    export const logName: RouterMiddleware<"/greet/:name?"> = (ctx, next) => {
      const { name } = ctx.params;
      console.log({ name });
      return next();
    };
    
    export const sendGreeting: RouterMiddleware<"/greet/:name?"> = (ctx, next) => {
      const name = ctx.params.name ?? "World";
      const msg = `Hello ${name}!`;
      ctx.response.body = msg;
      ctx.response.type = "text/plain";
      return next();
    };
    
    

    现在让我们创建一个将定义路由的模块。现在,我们将初始化一个路由器并将其导出,以便在继续设置时不会出现类型错误,但我们将回到这里探索两种组合方法:

    ./routes.ts:

    import { Router } from "https://deno.land/x/oak@v10.6.0/mod.ts";
    
    export const router = new Router();
    
    

    我们还创建一个模块,用于初始化和导出应用程序(以及在服务器启动时将启动消息打印到控制台的函数):

    ./app.ts:

    import { Application } from "https://deno.land/x/oak@v10.6.0/mod.ts";
    import { router } from "./routes.ts";
    
    // This is not necessary, but is potentially helpful to see in the console
    function printStartupMessage({ hostname, port, secure }: {
      hostname: string;
      port: number;
      secure?: boolean;
    }): void {
      const address = new URL(
        `http${secure ? "s" : ""}://${
          hostname === "0.0.0.0" ? "localhost" : hostname
        }:${port}/`,
      ).href;
      console.log(`Listening at ${address}`);
      console.log("Use ctrl+c to stop");
    }
    
    export const app = new Application();
    app.addEventListener("listen", printStartupMessage);
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    
    

    对于设置的最后一部分,我们将创建启动服务器的主应用程序入口点模块:

    ./main.ts:

    import { app } from "./app.ts";
    
    await app.listen({ port: 8000 });
    
    

    现在,让我们回到./routes.ts来探索组合方法:

    组合中间件函数

    可以组合中间件的第一种方法是使用 Oak 导出的函数来实现此目的:composeMiddlware

    我们的路由模块的修改版本如下所示:

    ./routes.ts:

    import {
      composeMiddleware,
      Router,
    } from "https://deno.land/x/oak@v10.6.0/mod.ts";
    
    import { logName, sendGreeting } from "./middleware.ts";
    
    export const router = new Router();
    const greetMiddleware = composeMiddleware([logName, sendGreeting]);
    router.get("/greet/:name?", greetMiddleware);
    
    

    或者,更简单地说,每个中间件函数可以按顺序作为位置参数提供给路由器方法:

    ./routes.ts:

    import { Router } from "https://deno.land/x/oak@v10.6.0/mod.ts";
    
    import { logName, sendGreeting } from "./middleware.ts";
    
    export const router = new Router();
    router.get("/greet/:name?", logName, sendGreeting);
    
    

    两者都产生相同的结果。

    测试应用程序

    使用适当的permissions 在终端控制台中启动应用程序以进行网络访问:

    % deno run --allow-net=0.0.0.0:8000 main.ts
    Listening at http://localhost:8000/
    Use ctrl+c to stop
    

    如果您在浏览器中导航到http://localhost:8000/greet,您应该会在视口中看到文本Hello World!,然后在终端控制台中返回带有{ name: undefined } 的行。

    同样,如果您导航到http://localhost:8000/greet/visitor,您应该会在视口中看到文本Hello visitor!,然后在终端控制台中返回带有{ name: "visitor" } 的行。

    【讨论】:

    • 感谢所有这些解释!我正在尝试遵循这两种方法,但我在您的中间件函数中看到您在函数末尾使用 next() ,这是强制性的吗?
    • 因为我的中间件函数之一,第一个“addProductPanier”使用异步(请求,响应),我不能像下一个那样添加第三个参数,我收到这个错误消息:类型参数'({请求,响应,下一个}: { request: any; response: any; next: any; }) => Promise<any>' 不可分配给“RouterMiddleware<"/OBV/panier", Record<string |数字,字符串 |未定义>,记录<字符串,任何>>'。参数'__0'和'context'的类型不兼容
    • @Fefe“……是强制性的吗?":是的(除非您想终止该函数的整个中间件链)。Oak 不支持中间件签名 (req, res) =&gt; any。Oak 的中间件签名始终为 (ctx, next) =&gt; unknown。阅读 documentation 可能对您有所帮助。
    • 好的,我明白了,现在我正在使用 (ctx,next) 签名。
    【解决方案2】:

    好的,所以我找到了解决方案。我的第一个中间件目的是将数据发送到我的数据库,然后第二个中间件从数据库中获取这些数据并使用 express 和 ejs 方法将其显示到 html 页面 ctx.render('./site/index.ejs',{数据:一些数据})。正如您在我的路线中看到的那样,我无法合并这两条路线,因为一条使用 async(req,res) 而另一条需要一个特定的渲染参数,即 ctx,如下所示: async(ctx:any) =>{ctx 。使成为(...)}。通过这种编写中间件函数的方式,我无法在您的答案中添加所需的“下一个”参数。

    我所做的是在第一个中间件“addProductPanier”中使用 response.redirect() 来指向我的 html 页面。 response.redirect() 被视为 GET。然后我像这样链接这两个中间件:

    router.post('/OBV/panier',addProductPanier).get('/OBV/panier',showPanier);

    它有效,我希望我很清楚感谢您的解释@jsejcksn

    【讨论】:

      猜你喜欢
      • 2021-07-23
      • 1970-01-01
      • 1970-01-01
      • 2015-10-08
      • 1970-01-01
      • 2021-08-20
      • 2013-05-21
      • 1970-01-01
      • 2020-02-19
      相关资源
      最近更新 更多