Javascript 如何在express.js中构建中间件动态链

Javascript 如何在express.js中构建中间件动态链,javascript,node.js,express,Javascript,Node.js,Express,我目前正在进行一个项目,以开发一个API管理器来控制现有的API 它包含一个“before”和“after”中间件列表,用于执行安全检查和日志记录等操作。以及一个“服务”中间件,用于对现有API执行http请求。但问题是,我希望中间件执行的顺序是动态的,这意味着我可以加载一些配置文件来更改每次请求传入时middleaware执行的顺序 这是我以前的代码: 'use strict'; // Loading the express library var express = require('exp

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

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

这是我以前的代码:

'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) at/Users/jialunliu/Documents/SOA_project/FAT-LADY/node_modules/async/lib/async.js:610:21 at/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) at_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(操作,函数(err){})


我一直收到这样的错误消息,说函数无法从这个函数数组中读取“操作”…

我认为你的思路是正确的,你只需要调整一些东西。我会在app.use()中注册一个顶级函数,然后在该函数中执行所有动态操作。将我的答案更新为工作示例。确保先安装async
npm安装--保存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()的更多信息,请参见

最后,我根据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”是什么意思。如果有人能帮我澄清这一点,我将不胜感激。

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

函数executeMiddlewareList(middlewareList、req、res、next){
eachSeries(中间件列表、函数(中间件、回调){
bind(null、req、res、回调)()
},函数(err){
if(err)返回res.status(500).json({error:err});
next();
})
}
函数testMid(数字){
返回函数(req、res、next){
调试日志('req.test from','req.test','to',编号);
要求测试=数量;
next();
}
}
router.get('/test',函数(req,res,next){
m、 EXECUTEMIDLEWARELIST([测试(1),测试(2)],req,res,next);
//输出:req.test从未定义到1
//要求测试从1到2
},功能(req,res){
//在ExecuteMidleWareList之后执行操作,req.test=2
})
连接顺序
:用于该特定目的的专用节点模块: 您只需使用模块
连接序列
,该模块就是为此而设计的:

npm install --save connect-sequence
见:

  • npmjs页面:
  • 或github项目:
然后,这里是一个用法示例:

/**
 * 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)
  }
}
这是开源的,欢迎公关!
如果您喜欢并使用connect sequence,但如果发现bug或需要一些新功能,请随时发布问题或提交请求

感谢您的回复,但是为什么不将“SortedAdminware”传递到async.series()函数中呢?我如何将已排序的中间件数组传递到您的函数中呢?我可以只使用一个全局变量吗?我应该如何从一个中间件发出错误并正确处理它?我尝试使用
next(err)
,但似乎错误处理不正确。很好的解决方案。然而,在这些中间件中不可能修改
req
res
,对吗?因为它们在执行任何东西之前就被绑定了……所以如果我在第一个执行的中间件中向
req
写入一些东西,那么它将在第二个中间件中丢失。有解决方案吗?@deyhle当你使用.bind(null,req,res)时,它正在传递一个