【问题标题】:How to make dynamic chain of middleware in express.js如何在express.js中制作动态中间件链
【发布时间】:2015-05-19 08:44:56
【问题描述】:

我目前正在开发一个 API 管理器来控制现有 API 的项目。

它包含“之前”和“之后”中间件的列表,用于执行安全检查和日志记录等操作。和一个“服务”中间件对现有 API 进行 http 请求。但问题是我想让中间件的执行顺序是动态的,这意味着我可以加载一些配置文件来更改每次请求进入时中间件的执行顺序。

这是我之前的代码:

'use strict';
// Loading the express library
var express = require('express');
var app = express();

var service = require('./routes/index');


// Testing configurable middleware
var confirguration = {
    before1: {
        priority: 100,
        enable: true
    },
    before2: {
        priority: 80,
        enable: true
    },
    service: {
        priority: 50,
        enable: true
    },
    after1: {
        priority: 30,
        enable: true
    },
    after2: {
        priority: 10,
        enable: true
    }
}

var before1 = require('./example_middleware/before1');
var before2 = require('./example_middleware/before2');
var after1 = require('./example_middleware/after1');
var after2 = require('./example_middleware/after2');
// Fake request to simulate the /service
var fakeRequest = require('./example_middleware/fake_request');

// Function to sort the order of the middleware to be executed
var sortConfig = function(confirguration){
    var sortable = [];
    for (var middleware in confirguration)
        // To make middlewares configurable
        if (confirguration[middleware]['enable'] == true){
            sortable.push([middleware, confirguration[middleware]['priority']]);
        }

    sortable.sort(function(a, b) {return b[1] - a[1]});
    return sortable;
}

// var sortedConfig = [];
var middlewareSet = new Array();
app.use('/test', function(request, response, next){
    var middleware;
    var sortedConfig = sortConfig(confirguration);

    for (var i in sortedConfig){
        switch(sortedConfig[i][0]){
            case 'before1':
                middleware = before1;
                break;
            case 'before2':
                middleware = before2;
                break;
            case 'service':
                middleware = fakeRequest;
                break;
            case 'after1':
                middleware = after1;
                break;
            case 'after2':
                middleware = after2;
                break;
        }


        // console.log(sortedConfig[i][0]);
        // Execute the middleware in expected order
        middlewareSet.push(middleware);
    }
    // request.sortedConfig = sortedConfig;
    console.log(middlewareSet);
    console.log('middleware list sorted');
    next();
});

app.use('/test', middlewareSet);

但我不断收到来自 app.use() 最后一行的相同错误消息:

app.use() 需要中间件函数

如果我使用它会起作用:

app.use('/test', [before1, before2, fakeRequest, after1, after2]);

但它不是动态的,我误解了什么? express.js 中一定有办法做到这一点。

提前致谢。

编辑: 我根据 Ryan 的回答修改了我的代码,代码如下:

var async = require('async');
app.use('/test', configurableMiddleWare);

function configurableMiddleWare(req, res, next) {

    var operations = [];

    var middleware;

    var sortedConfig = sortConfig(confirguration);

   // push each middleware you want to run
    sortedConfig.forEach(function(fn) {

        switch(fn[0]){
            case 'before1':
                middleware = before1;
                break;
            case 'before2':
                middleware = before2;
                break;
            case 'service':
                middleware = fakeRequest;
                break;
            case 'after1':
                middleware = after1;
                break;
            case 'after2':
                middleware = after2;
                break;
        }

        operations.push(middleware); // could use fn.bind(null, req, res) to pass in vars  
    });

    console.log('middleware list sorted');
   // now actually invoke the middleware in series
    async.series(operations, function(err) {
        if(err) {
        // one of the functions passed back an error so handle it here
            return next(err);
        }
      // no errors so pass control back to express
        next();
    });

}

为了确保我的测试中间件没有犯任何错误,这里是其中之一的示例:

'use strict';

var express = require('express');
var router = express.Router();

router.route('/')
    .all(function(request, response, next){
        console.log('This is middleware BEFORE1');
        next();
    });


module.exports = router;

现在,当我运行我的应用程序时,我从 npm 收到以下错误:

TypeError: 无法调用未定义的方法 'indexOf'

在 Function.proto.handle (/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/express/lib/router/index.js:130:28) 在路由器(/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/express/lib/router/index.js:35:12) 在 /Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:610:21 在 /Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:249:17 在迭代(/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:149:13) 在 async.eachSeries (/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:165:9) 在_asyncMap (/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:248:13) 在 Object.mapSeries (/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:231:23) 在 Object.async.series (/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:608:19) 在configurableMiddleWare (/Users/jialunliu/Documents/SOA_project/FAT-LADY/app.js:135:11)

来自线路 async.series(operations, function(err){})

我不断收到这种错误消息,说函数无法从这个函数数组“操作”中读取......

【问题讨论】:

    标签: javascript node.js express


    【解决方案1】:

    我认为你在正确的轨道上,你只需要调整一些东西。我会使用app.use() 注册一个顶级函数,然后在该函数中执行所有动态操作。更新我对一个工作示例的答案。一定要先安装异步npm install --save async

    // define all middleware functions
    var middleware = {
        mw1: function(req, res, next) {
            console.log('mw 1');
            next();
        },
        mw2: function(req, res, next) {
            console.log('mw 2');
            next();
        },
        mw3: function(req, res, next) {
            console.log('mw 3');
            next();
        },
        mw4: function(req, res, next) {
            console.log('mw 4');
            next();
        }
    
    };
    
    // register our "top level function"
    app.use(configurableMiddleware);
    var requestCount = 1; // this is just for the working example
    
    function configurableMiddleware(req, res, next) {
        var isEvenRequest = requestCount++ % 2 === 0; // simple logic to alternate which "configurable" middleware to use
    
        var operations; // in the real world you could build this array dynamically, for now we just hardcode two scenarios as an example
    
        // Each request to http://localhost:3000 will alternate which middleware is used, so you will see a different log each time
        if(isEvenRequest) {
            console.log('Even request should log mw2 and mw4');
            // .bind(null, req, res) makes sure that the middleware gets the request and response objects when they are invoked, 
            // as of this point they still haven't been invoked...
            operations = [middleware.mw2.bind(null, req, res), middleware.mw4.bind(null, req, res)];
        }
        else {
            console.log('Odd request should log mw1 and mw3');
            operations = [middleware.mw1.bind(null, req, res), middleware.mw3.bind(null, req, res)];
        }
    
        // invoke each middleware in series - you could also do async.parallel if the order of middleware doesn't matter
        // using the async module: https://github.com/caolan/async
        async.series(operations, function(err) {
            if(err) {
                console.log('There was a problem running the middleware!');
                return next(err);
            }
            // all middleware has been run
            next();
        });
    }
    

    有关 .bind() 的更多信息,请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

    【讨论】:

    • 感谢您的回复,但是为什么不直接将“sortedMiddleware”传递给 async.series() 函数呢?
    • 我如何将排序后的中间件数组传递给您的函数?我可以只使用全局变量吗?
    • 我应该如何从其中一个中间件发出错误并正确处理?我尝试使用next(err),但似乎没有正确处理错误。
    • 不错的解决方案。但是,无法在这些中间件中修改 reqres,对吗?因为它们在执行任何操作之前就被绑定了……所以如果我在第一个执行的中间件中向req 写一些东西,它将在第二个中间件中丢失。有什么解决办法吗?
    • @deyhle 当您使用 .bind(null, req, res) 时,它会传递对 reqres 对象的引用 - 因此,如果您对 req 中的第一个中间件您将能够在最后一个中间件中看到这种变化,因为它们都看到相同的 req 对象。
    【解决方案2】:

    最后,我根据 Ryan 找到了答案,代码如下所示:

    function configurableMiddleWare(req, res, next) {
    
        var operations = [];
    
        var middleware;
    
        var sortedConfig = sortConfig(confirguration);
    
       // push each middleware you want to run
        sortedConfig.forEach(function(fn) {
    
            switch(fn[0]){
                case 'before1':
                    middleware = before1;
                    break;
                case 'before2':
                    middleware = before2;
                    break;
                case 'service':
                    middleware = fakeRequest;
                    break;
                case 'after1':
                    middleware = after1;
                    break;
                case 'after2':
                    middleware = after2;
                    break;
            }
    
            console.log(fn[0]);
            console.log(middleware);
    
            operations.push(middleware.bind(null, req, res)); // could use fn.bind(null, req, res) to pass in vars  
        });
    
        console.log('middleware list sorted');
       // now actually invoke the middleware in series
        async.series(operations, function(err) {
            if(err) {
            // one of the functions passed back an error so handle it here
                return next(err);
            }
            console.log('middleware get executed');
            // no errors so pass control back to express
            next();
        });
    
    }
    
    app.use('/test', configurableMiddleWare);
    

    关键步骤确实是operations.push(middleware.bind(null, req, res)); 老实说,我不明白这到底是什么意思。我知道这是将“req”和“res”变量传递给中间件,但我不明白前面的“null”有什么意义。如果有人能帮助我澄清这一点,我们将不胜感激。

    【讨论】:

    • 第一个参数是上下文。类似于您使用 call 或 apply 来更改“this”的上下文。
    【解决方案3】:

    基于@Ryan 代码背后的想法,我想出了这个函数。它执行一个中间件列表,以便根据需要绑定变量,允许所有内容都由executeMiddlewareList([middleware1, middleware2...], req, res, next); 执行。为每个中间件req, res 传递和来自async.eachSeries 的回调。这意味着当在中间件内部调用next() 时,将从列表中处理下一个。如果中间件使用next(err) 抛出错误,将停止执行,您可以手动处理。

    function executeMiddlewareList (middlewareList, req, res, next) {
       async.eachSeries(middlewareList, function(middleware,callback) {
          middleware.bind(null,req,res,callback)()
       }, function(err) {
          if (err) return res.status(500).json({error: err});
          next();
       })
    }
    
    function testMid (number) {
       return function (req, res, next) {
          log.debug('req.test from', req.test, " to ", number);
          req.test=number;
          next();
       }
    }
    
    router.get('/test', function(req, res, next) {
       m.executeMiddlewareList([test(1), test(2)], req, res, next);
       //Output: req.test from undefined to 1
       //        req.test from 1 to 2
    }, function (req,res) {
        //Do stuff after the executeMiddlewareList, req.test = 2
    })

    【讨论】:

    • 谢谢!您的解决方案有效,而 Ryan 的解决方案不适用于异步中间件
    【解决方案4】:

    connect-sequence:用于该特定目的的专用节点模块:

    您可以只使用为此目的设计的模块connect-sequence

    npm install --save connect-sequence
    

    见:

    然后,这里是一个用法示例:

    /**
     * Product API
     * @module
     */
    
    var ConnectSequence = require('connect-sequence')
    var productsController = require('./products.controller')
    
    module.exports = productRouter
    
    function productRouter (app) {
      app.route('/api/products/:productId')
      .get(function (req, res, next) {
        // Create a ConnectSequence instance and setup it with the current `req`,
        // `res` objects and the `next` callback
        var seq = new ConnectSequence(req, res, next)
    
        // build the desired middlewares sequence thanks to:
        // - ConnectSequence#append(mid0, ..., mid1),
        // - ConnectSequence#appendList([mid0, ..., mid1])
        // - and ConnectSequence#appendIf(condition, mid)
    
        if (req.query.filter) {
          seq.append(productsController.filter)
        }
    
        if (req.query.format) {
          seq.append(
            productsController.validateFormat,
            productsController.beforeFormat,
            productsController.format,
            productsController.afterFormat
          )
        }
    
        // append the productsController.prepareResponse middleware to the sequence
        // only if the condition `req.query.format && req.formatedProduct` is true
        // at the moment where the middleware would be called.
        // So the condition is tested after the previous middleware is called and thus
        // if the previous modifies the `req` object, we can test it.
        seq.appendIf(isProductFormatted, productsController.prepareResponse)
    
        seq.append(productsController.sendResponse)
    
        // run the sequence
        seq.run()
      })
    
      app.param('productId', function (req, res, next, id) {
        // ... yield the product by ID and bind it to the req object
      })
    
      function isProductFormatted (req) {
        return Boolean(req.formatedProduct)
      }
    }
    

    这是开源的,欢迎 PR!

    如果您喜欢并使用连接序列,但如果您发现错误或需要一些新功能,请随时发布问题或提交拉取请求!

    【讨论】:

    • 这是错误的,我试过 Multer,Auth0,没有一个在每个订单中都有效
    猜你喜欢
    • 2013-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    • 2020-10-18
    • 2011-01-14
    相关资源
    最近更新 更多