Expressjs中的中间件和app.use实际上是什么意思?

246

我看到几乎每个 Express 应用程序都有一个中间件 app.use 语句,但我还没有找到一个清晰简洁的解释什么是中间件以及 app.use 语句实际上在做什么。即使是 express 的官方文档本身也有点模糊不清。你能为我解释一下这些概念吗?


4
好的,我会尽力进行翻译。下面是需要翻译的内容:类似的问题供参考(虽然这个问题创建得更早):https://dev59.com/XGgu5IYBdhLWcg3wbWo9 - ericsoco
57
哈!这两个问题在评论中相互参照。 - Julian H. Lam
25
这是一个循环引用。 - Stephan Kristyn
1
我写了一篇关于express.js中间件的文章。这是链接:https://www.nodexplained.com/blog-detail/2017/12/31/write-and-implement-middlewares-in-express-application-part-1 - shrawan_lakhe
9个回答

117

中间件

我正在将中间件的概念分离到一个新项目中。

中间件允许您定义一系列应该通过的操作。Express服务器本身就是一个中间件栈。

// express
var app = express();
// middleware
var stack = middleware();

然后,您可以通过调用 .use 来向中间件栈添加层。

// express
app.use(express.static(..));
// middleware
stack.use(function(data, next) {
  next();
});

中间件堆栈中的一层是一个函数,它需要n个参数(对于express来说,是reqres),以及一个next函数。

中间件期望该层执行某些计算、修改参数,并调用next

除非你处理它,否则堆栈不会做任何事情。每当服务器捕获到传入的HTTP请求时,Express都会处理堆栈。使用中间件时,您需要手动处理堆栈。

// express, you need to do nothing
// middleware
stack.handle(someData);

一个更完整的例子:

var middleware = require("../src/middleware.js");

var stack = middleware(function(data, next) {
    data.foo = data.data*2;
    next();
}, function(data, next) {
    setTimeout(function() {
        data.async = true;
        next();
    }, 100)
}, function(data) {
    console.log(data);
});

stack.handle({
    "data": 42
})

在Express中,你可以定义一个操作堆栈,用于处理每个传入的HTTP请求。

与Connect不同,Express有全局中间件和特定路由中间件。这意味着你可以将中间件堆栈附加到每个传入的HTTP请求上,或仅将其附加到与某个路由交互的HTTP请求上。

Express和中间件的高级示例:

// middleware 

var stack = middleware(function(req, res, next) {
    users.getAll(function(err, users) {
        if (err) next(err);
        req.users = users;
        next();  
    });
}, function(req, res, next) {
    posts.getAll(function(err, posts) {
        if (err) next(err);
        req.posts = posts;
        next();
    })
}, function(req, res, next) {
    req.posts.forEach(function(post) {
        post.user = req.users[post.userId];
    });

    res.render("blog/posts", {
        "posts": req.posts
    });
});

var app = express.createServer();

app.get("/posts", function(req, res) {
   stack.handle(req, res); 
});

// express

var app = express.createServer();

app.get("/posts", [
    function(req, res, next) {
        users.getAll(function(err, users) {
            if (err) next(err);
            req.users = users;
            next();  
        });
    }, function(req, res, next) {
        posts.getAll(function(err, posts) {
            if (err) next(err);
            req.posts = posts;
            next();
        })
    }, function(req, res, next) {
        req.posts.forEach(function(post) {
            post.user = req.users[post.userId];
        });

        res.render("blog/posts", {
            "posts": req.posts
        });
    }
], function(req, res) {
   stack.handle(req, res); 
});

7
在这种情况下,"middleware"是指您自己的库还是Express的一部分? - iZ.
5
酷。我仍然有点困惑 app.use() 语法。中间件的实际返回值是什么,use 如何处理它? - iZ.
9
@iZ使用将其添加到堆栈中。然后每个请求都经过该堆栈。 - Raynos
7
@Raynos,你的项目“中间件”的链接已经失效了。 - Lee
1
@Raynos,但我仍然看到Express中使用了中间件?你说的“nuked”是什么意思? - Timo Huovinen
显示剩余9条评论

64

简化后,一个Web服务器可以被视为一个接受请求并输出响应的函数。因此,如果您将Web服务器视为函数,则可以将其组织成几个部分,并将它们分成较小的函数,以便它们的组合将是原始函数。

中间件是您可以与其他函数组合使用的较小函数,显而易见的好处是您可以重用它们。


36

在此我补充一点前面回答中未提到的内容。

现在应该很清楚了,中间件是在客户端请求服务器响应之间运行的一个或多个函数。最常见的中间件功能包括错误管理、数据库交互、从静态文件或其他资源获取信息。为了在中间件堆栈中继续向下执行,必须调用下一个回调函数,在中间件函数末尾可以看到这一点,以便在流程中移动到下一步。

您可以使用app.use方法,并按此处的方式设置流程:

var express = require('express'),
    app = express.createServer(),                                                                                                                                                 
    port = 1337;

function middleHandler(req, res, next) {
    console.log("execute middle ware");
    next();
}

app.use(function (req, res, next) {
    console.log("first middle ware");                                                                                                             
    next();
});

app.use(function (req, res, next) {
    console.log("second middle ware");                                                                                                             
    next();
});

app.get('/', middleHandler, function (req, res) {
    console.log("end middleware function");
    res.send("page render finished");
});

app.listen(port);
console.log('start server');

但你也可以采用另一种方法,将每个中间件作为函数参数传递。以下是MooTools Nodejs网站的示例,其中中间件获取Twitter、Github和Blog流,然后将response发送回客户端。注意,在app.get('/', githubEvents, twitter, getLatestBlog, function(req, res){中如何将函数作为参数传递。使用app.get仅针对GET请求调用,而app.use将针对所有请求调用。

// github, twitter & blog feeds
var githubEvents = require('./middleware/githubEvents')({
    org: 'mootools'
});
var twitter = require('./middleware/twitter')();
var blogData = require('./blog/data');
function getLatestBlog(req, res, next){
    blogData.get(function(err, blog) {
        if (err) next(err);
        res.locals.lastBlogPost = blog.posts[0];
        next();
    });
}

// home
app.get('/', githubEvents, twitter, getLatestBlog, function(req, res){
    res.render('index', {
        title: 'MooTools',
        site: 'mootools',
        lastBlogPost: res.locals.lastBlogPost,
        tweetFeed: res.locals.twitter
    });
});

2
我正在寻找一个答案,即Express.js是否支持基于路由(而不是基于Router)的中间件挂载?看起来你在你的回答中展示了它。 - sçuçu
你能解释一下你上面的例子吗?你是如何将这么多函数传递给app.get(...)的,它们的调用顺序是什么? - Tanner Summers
2
嗨@TannerSummers,.get() 方法接受3种类型的参数:第一个、最后一个和中间的参数。在内部,它检测是否有超过2个参数,并使用那些(中间的)作为中间件函数,从左到右依次调用它们。 - Sergio
1
我不确定这两种风格是否有区别。这篇文章帮助澄清了 - 谢谢! - java-addict301

28

expressjs 指南 对你的问题有很好的回答,我强烈建议你阅读一下,我在这里发布指南的一个简短片段,该指南非常好。

编写用于 Express 应用程序的中间件

概述

中间件函数是具有访问请求对象 (req)、响应对象 (res) 和应用程序请求-响应周期中的下一个函数的功能。下一个函数是 Express 路由器中的一个函数,当调用它时,执行当前中间件后继的中间件。

中间件函数可以执行以下任务:

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件。

如果当前中间件函数不结束请求-响应周期,则必须调用next()将控制传递给下一个中间件函数。否则,请求将被挂起。

enter image description here

示例

下面是一个简单的“Hello World” Express应用程序示例。本文的其余部分将定义并添加两个中间件函数到应用程序中:一个名为myLogger的函数,它打印一个简单的日志消息,另一个名为requestTime1的函数,它显示HTTP请求的时间戳。

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)   

中间件函数 myLogger

以下是一个名为“myLogger”的中间件函数的简单示例。当应用程序的请求通过它时,此函数将仅打印“LOGGED”。中间件函数分配给名为myLogger的变量。

var myLogger = function (req, res, next) {
  console.log('LOGGED')
  next()
}

注意上面调用的{{next()}}。调用此函数会在应用程序中调用下一个中间件函数。{{next()}}函数不是Node.js或Express API的一部分,而是传递给中间件函数的第三个参数。{{next()}}函数可以被命名为任何名称,但按照惯例它总是被命名为“next”。为避免混淆,请始终使用此约定。
要加载中间件函数,请调用{{app.use()}},指定中间件函数。例如,以下代码在根路径(/)路由之前加载了{{myLogger}}中间件函数。
var express = require('express')
var app = express()

var myLogger = function (req, res, next) {
  console.log('LOGGED')
  next()
}

app.use(myLogger)

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(3000)

每次应用程序收到请求时,它会将消息“LOGGED”打印到终端。
中间件加载的顺序很重要:先加载的中间件函数也会先执行。
如果myLogger在根路径路由之后加载,则请求永远不会到达它,应用程序不会打印“LOGGED”,因为根路径的路由处理程序终止了请求响应周期。
中间件函数myLogger只需打印一条消息,然后通过调用next()函数将请求传递给堆栈中的下一个中间件函数。


  1. 这篇文章只包含我的日志中间件,如果需要更多信息,请参考原始的expressjs指南here


1
非常好的解释。 - Drumbeg
它在Express网站上可用,链接在这里http://expressjs.com/en/guide/writing-middleware.html,非常好。我想知道为什么迄今为止没有人提到它。 - Suraj Jain
2
不错。这是我在这里看到的最清晰的解释,而且很奇怪没有人引用它! - Drumbeg
1
很好地解释了。 - Rehan Shikkalgar
中间件加载的顺序很重要:首先加载的中间件函数也会首先执行。这是非常重要的注意事项。其他答案都没有提到这一点。对于只在Python上工作过的初学者来说,这非常重要,因为可能从未遇到过这些问题。 - Trect

14

=====非常简单的解释=====

中间件通常在Express.js框架的上下文中使用,并且是node.js的一个基本概念。简而言之,它基本上是一个具有对应用程序请求和响应对象访问权限的函数。我希望通过这种方式思考中间件:在请求被应用程序处理之前,请求经过一系列的“检查/预筛选”。例如,中间件可以很好地确定请求是否经过身份验证,如果请求未经过身份验证,则返回登录页面或记录每个请求。许多第三方中间件可用于实现各种功能。

简单的中间件示例:

var app = express();
app.use(function(req,res,next)){
    console.log("Request URL - "req.url);
    next();
}

以上的代码将会在每个请求进来时执行,并记录请求的URL,next()方法实际上允许程序继续执行。如果不调用next()函数,程序将不会继续执行,并将在中间件的执行处停止。

中间件的一些注意事项:

  1. 应用程序中中间件的顺序很重要,因为请求会按顺序经过每个中间件。
  2. 在你的中间件函数中忘记调用next()方法可以停止处理请求。
  3. 任何对中间件函数中的req和res对象所做的更改都会使这些更改对使用req和res的应用程序的其他部分可用。

1
非常感谢!这是目前为止最好的解释,让我理解了这个问题。我有一个问题,我正在阅读一些带有中间件的代码,它没有调用 next() 而是 return next()。这有什么区别吗? - KansaiRobot
非常感谢您的赞美之词,朋友... 我们使用 next() 是因为我们希望调用下一个中间件,我认为 next()return next() 不应该有任何区别! 不过这还取决于代码是什么... - Vaibhav KB

7
中间件是在输入/源之后执行的函数,然后产生一个输出,这个输出可以是最终输出或者下一个中间件使用的输入,直到循环结束。
就像一个产品通过装配线,在沿途被修改,直到完成、评估或被拒绝。
中间件需要一些值来工作(即参数值),基于某些逻辑,中间件将调用或不调用下一个中间件,或向客户端发送响应。
如果您仍然无法理解中间件的概念,则它在某种程度上类似于修饰器或链式命令模式。

5
中间件是Express js路由层调用的一系列函数的子集,在用户定义的处理程序被调用之前执行。中间件函数可以完全访问请求和响应对象,并可以修改它们之一。

中间件链总是按照其定义顺序精确调用,因此您需要确切知道特定中间件正在做什么。在中间件函数完成后,通过将其下一个参数作为函数调用下一个链中的函数。
执行完整个链后,调用用户请求处理程序。

1

保持简单,伙计!

注意:答案与ExpressJS内置中间件有关,但是中间件有不同的定义和用例。

从我的角度来看,中间件充当实用程序或辅助函数,但其激活和使用完全是可选的,通过使用app.use('path', /* define or use builtin middleware */),我们不需要编写一些代码来执行每个客户端HTTP请求所需的非常常见的任务,例如处理cookie、CSRF令牌等,在大多数应用程序中都很常见,因此中间件可以帮助我们在一些堆栈、序列或操作顺序中为每个客户端HTTP请求执行所有这些操作,然后将处理结果作为客户端请求的单个单位提供

例如:

接受客户端请求并根据他们的请求提供回复是Web服务器技术的本质。

假设我们对Web服务器的根URI发出GET HTTP请求,只需要"Hello, world!"文本作为响应,这是一个非常简单的情况,不需要任何其他东西。但是,如果我们要检查当前登录的用户,然后用"Hello, 用户名!"来响应,就需要更多的东西了。在这种情况下,我们需要一个中间件来处理所有客户端请求元数据,并提供从客户端请求获取的标识信息,然后根据该信息唯一地识别我们当前的用户,可以向他/她响应一些相关数据。
希望能对某些人有所帮助!

-1
在非常基本的术语中,如果我想这样解释它,我是从 traversymedia 的 YouTube 频道学习的 express crash course。
好的,所以中间件是一个函数,在你调用路由之后执行的。
var logger = function(req, res, next){
   console.log('logging...');
   next();
}

app.use(logger);

这个日志记录器函数每次刷新页面时都会执行,这意味着您可以在其中编写任何您需要在页面渲染后执行的操作,例如任何操作API调用、重置事项等。并且将此中间件放置在路由函数之前非常重要,中间件的顺序非常重要,否则它将无法正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接