理解 Express 中的中间件

Understanding middleware in Express

我想弄清楚中间件在 Express 中是如何工作的。

虽然我了解中间件的概念,但我对中间件参数感到困惑。

这是来自 the offical docs regarding middleware 的示例:

app.use('/user/:id', function (req, res, next) {
   console.log('Request URL:', req.originalUrl)
   next()
    }, function (req, res, next) {
      console.log('Request Type:', req.method)
      next()
    })

在这个例子中,我可以看到有两个函数充当两个中间件,在处理这个特定路由之前一个接一个地执行。

但是传递给这些函数的参数是什么?

reqres 只是 "empty" 个对象吗?

如果是这样,我们如何才能引用 属性 req.originalUrl

如果不是,该对象及其属性来自哪里?

他们还在教程中使用 res.send,因此 res 对象似乎也有属性,而不是 "empty" 对象。

(我理解 next 是回调参数)。

如果我没理解错的话,令人困惑的部分是传递给中间件函数的对象吗?在您链接的文档中已经对这些进行了解释(见下文)。

  • "But what paramters are given to this functions ?."

Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.

(Source)

  • "Do req and res are just "empty" objects ?如果是这样,我们为什么要使用字段 req.orginaleUrl ?如果不是,那个对象在哪里, 它的字段来自?"

如果您点击链接,您会发现以下对请求对象的解释:

The req object represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on.

(Source)

你问题中提到的originalUrl属性是req对象的属性。

和响应对象:

The res object represents the HTTP response that an Express app sends when it gets an HTTP request.

send 是分配给 res 对象的方法,它将发送 HTTP 响应。

(Source)

Do req and res are just "empty" objects?

不,reqres 永远不会为空,实际上是相同的,它们被传递给每个中间件。您还可以修改 reqres 对象,修改将保留在所有下一个中间件中。

您可以在此处分别查看 reqres 上的所有可用字段 - request object and response object.

您始终可以在中间件的任何位置访问 reqres。如果你想结束请求响应周期,你可以只使用响应对象并发送像res.send(200)这样的响应。这将结束 req-res 循环,您无需调用 next().

But what paramters are given to this functions ?

您不需要向该函数传递任何参数。 Express 总是会将 reqresnext 传递给定义的任何中间件。这是您可以假定 express 使用和所有中间件都应遵循的格式。

请注意,如果您不结束 req-res 循环,则必须调用 next(),它将控制权传递给下一个中间件。如果中间件没有结束 req-res 循环,也没有调用 next(),请求将一直挂起,可能只是在客户端超时。

总结

request object 表示 HTTP 请求,具有请求查询字符串、参数、body、HTTP headers 等属性。

response object 表示 Express 应用在收到 HTTP 请求时发送的 HTTP 响应。

Middleware functions 是可以访问请求 object、响应 object 和应用程序 request-response 循环中的 next 函数的函数。 next 函数是 Express 路由器中的一个函数,调用时会执行当前中间件之后的中间件。

路由可以附加 chained methods(对于 GETPOSTDELETE 请求),这些路由将中间件函数作为参数。

requestobject是request最初接收到的数据,经过各种中间件函数可以修改,responseobject是发送出去的数据

示例中间件

下面是一个示例中间件函数,您可以将其复制并粘贴到应用程序的开头:

/**
 * An example of a middleware function that logs various values of the Express() request object.  
 * 
 * @constant  
 * @function  
 * @param  {object} req - The req object represents the HTTP request and has properties for the request query string, parameters, body, HTTP headers, and so on. In this documentation and by convention, the object is always referred to as req (and the HTTP response is res) but its actual name is determined by the parameters to the callback function in which you’re working.
 * @param  {object} res - The res object represents the HTTP response that an Express app sends when it gets an HTTP request.  In this documentation and by convention, the object is always referred to as res (and the HTTP request is req) but its actual name is determined by the parameters to the callback function in which you’re working.
 * @param  {Function} next - `next` is used as an argument in the middleware function, and subsequently invoked in the function with `next()`, to indicate the application should "move on" to the next piece of middleware defined in a route's chained method.  
 * @see {@link https://expressjs.com/en/4x/api.html#req|Express Request}  
 * @see {@link https://expressjs.com/en/4x/api.html#res|Express Response}  
 * @see {@link http://expressjs.com/en/guide/writing-middleware.html|Writing Middleware}  
 */
const my_logger = (req, res, next) => {
    console.log("req.headers: ");
    console.log(req.headers);
    console.log("req.originalUrl: " + req.originalUrl);
    console.log("req.path: " + req.path);
    console.log("req.hostname: " + req.hostname);
    console.log("req.query: " + JSON.stringify(req.query));
    console.log("req.route: " + JSON.stringify(req.route));
    console.log("req.secure: " + JSON.stringify(req.secure));
    console.log("req.ip: " + req.ip);
    console.log("req.method: " + req.method);
    console.log("req.params:");
    console.log(req.params);
    console.log("==========================");

    //if next() is not invoked below, app.use(myLogger) is the only middleware that will run and the app will hang
    next();

}

// called for all requests
app.use(my_logger);

示例路线

下面是一些示例路线。

路由有 chained methods 附加,以中间件函数作为参数。

// some example routes
app.route("/api/:api_version/pages")
    .get(api_pages_get);

app.route("/api/:api_version/topics")
    .get(api_topics_get)
    .post(api_login_required, api_topics_post) 
    .delete(api_login_required, api_topics_delete);   

app.route("/api/:api_version/topics/ratings")
    .post(api_login_required, api_topics_ratings_post);

在中间件函数中使用next()

在上面的例子中,你可以看到一些方法有两个中间件函数作为参数。

第一个 api_login_required 验证登录凭据,如果成功,则调用 next() 提示下一个中间件函数 运行。

看起来像这样:

const api_login_required = (req, res, next) => {

    // req.user exists if the user's request was previously verified, it is produced elsewhere in the code   
    if (req.user) {
        next();
    } else {
        return res.status(401).json({ message: 'Unauthorized user!' });
    }
    
}

没有next()

的中间件

但是,附加到 /api/:api_version/pages 的路由处理程序的 get() 方法只有一个中间件函数参数:api_pages_get.

如下图,api_pages_get没有调用next(),因为后面没有运行需要的中间件函数

它使用响应 object 到 return 响应的 send() and json() 方法。

const api_pages_get = async (req, res) => {

    var page_title = req.query.page_title;

    var collection = mongo_client.db("pages").collection("pages");
    var query = { page_title: page_title };
    var options = { projection: { page_title: 1, page_html: 1 } };

    try {
        var page = await collection.findOne(query);

        // if there is no result
        if (page === null) {
            res.status(404).send('404:  that page does not exist');
            return;
        }
        // if there is a result
        else if (page !== null) {
            res.json(page);
            return;
        }
    } catch (err) {
        console.log("api_pages_get() error: " + err);
        res.send(err);
        return;
    }

}

中间件注意事项

我之前写的一些其他笔记,供自己参考,可能会有帮助:

中间件或中间件函数可以访问在代码早期定义的 Express request and response objects and are passed as arguments to a route's chained method (or on all requests if passed as an argument to an instance of the use() 方法。

next 在中间件函数中用作参数,随后在函数中用 next() 调用,以指示应用程序应该“继续”到定义的下一个中间件路由的链式方法。

如果中间件函数不调用 next(),它不会移动到路由或方法处理程序中定义的下一个中间件。

此外,如果不使用next(),并且函数中未定义终止操作,即响应,应用程序将保持“挂起”状态。