【问题标题】:Node.js / Express.js - How to override/intercept res.render function?Node.js / Express.js - 如何覆盖/拦截 res.render 函数?
【发布时间】:2012-03-06 08:26:36
【问题描述】:

我正在使用 Connect/Express.js 构建一个 Node.js 应用程序,我想拦截 res.render(view, option) 函数以在将其转发到原始渲染函数之前运行一些代码。

app.get('/someUrl', function(req, res) {

    res.render = function(view, options, callback) {
        view = 'testViews/' + view;
        res.prototype.render(view, options, callback);
    };

    res.render('index', { title: 'Hello world' });
});

这看起来像是一个人为的例子,但它确实适合我正在构建的整体框架。

我对 JavaScript 上的 OOP 和原型继承的了解有点薄弱。我该怎么做这样的事情?


更新:经过一些实验,我想出了以下内容:

app.get('/someUrl', function(req, res) {

    var response = {};

    response.prototype = res;

    response.render = function(view, opts, fn, parent, sub){
        view = 'testViews/' + view;
        this.prototype.render(view, opts, fn, parent, sub);
    };

    response.render('index', { title: 'Hello world' });
});

它似乎工作。不确定这是否是最佳解决方案,因为我正在为每个请求创建一个新的响应包装对象,这会是一个问题吗?

【问题讨论】:

  • 这就是你通常的做法。如果你把它放在路由器之前使用的中间件中,你可以在一个地方设置它。
  • 我想到了使用中间件。不确定我是否要覆盖整个应用程序的响应行为,仅适用于通过我的框架路由的请求。

标签: javascript node.js express connect prototypal-inheritance


【解决方案1】:

response 对象没有原型。这应该可行(采用 Ryan 将其放入中间件的想法):

var wrapRender = function(req, res, next) {
  var _render = res.render;
  res.render = function(view, options, callback) {
    _render.call(res, "testViews/" + view, options, callback);
  };
};

但是,破解 ServerResponse.prototype 可能会更好:

var express = require("express")
  , http = require("http")
  , response = http.ServerResponse.prototype
  , _render = response.render;

response.render = function(view, options, callback) {
  _render.call(this, "testViews/" + view, options, callback);
};

【讨论】:

  • 谢谢。尽管这似乎也破坏了它寻找布局/主视图的方式,但它试图寻找“testViews/layout.ejs”。我确实查看了源代码,发现响应对象上已经定义了一个 _render 方法,这会导致问题吗?
  • 是的,这是正确的 - 它会将“testViews/”添加到所有视图中。它与ServerResponse._render 无关。你想完成什么?
  • 尝试实施 ServerRespone.prototype hack 并失败。猜猜这在节点 0.6/express 2 中有效吗?您知道如何在节点 0.8 中进行这项工作并表达 3 吗?
【解决方案2】:

老问题,但发现自己问同样的问题。如何拦截 res 渲染? 现在使用 express 4.0x 的东西。

您可以使用/编写中间件。这个概念起初对我来说有点令人生畏,但经过一些阅读之后,它变得更有意义了。对于其他阅读本文的人来说,重写 res.render 的动机是提供全局视图变量。我希望session 在我的所有模板中都可用,而不必在每个 res 对象中输入它。

基本的中间件格式是。

app.use( function( req, res, next ) {
    //....
    next();
} );

下一个参数和函数调用对于执行至关重要。 next 是回调函数,允许多个中间件在不阻塞的情况下做他们的事情。为了更好的explanation read here

这可以用来覆盖渲染逻辑

app.use( function( req, res, next ) {
    // grab reference of render
    var _render = res.render;
    // override logic
    res.render = function( view, options, fn ) {
        // do some custom logic
        _.extend( options, {session: true} );
        // continue with original render
        _render.call( this, view, options, fn );
    }
    next();
} );

我已经使用 express 3.0.6 测试了这段代码。它应该可以毫无问题地与 4.x 一起使用。 您还可以使用

覆盖特定的 URL 组合
app.use( '/myspcificurl', function( req, res, next ) {...} );

【讨论】:

  • 这是否会覆盖每个请求的函数,给 GC 施加压力?覆盖原型是否更有意义?
  • 这会覆盖每个请求。它将通过属性阴影覆盖原型。如果您愿意,可以覆盖原型,但通常您希望主对象上的属性不在原型链的深处。这种方式的属性查找时间更快。
  • 一开始我没有得到这个答案,但是在自己摆弄了很多之后,现在我明白了。您可以在github.com/mettamage/renderExtMiddleware 上查看 Lex 他的代码的变体——我不使用_.extend(args)。当客户端不是浏览器时,我的代码呈现普通视图 except,然后它返回 json。
  • 是的,可以更好地描述它。你的例子是使用默认的渲染函数,我不知道当不同的标题时它会返回 json 。上面的例子定义了一个新的函数来处理渲染。而_.extend 是完全可选的,我使用下划线库来扩展对象。
  • +1 用于提及下划线,这使答案更容易理解——我是 node.js 的新手。
【解决方案3】:

使用中间件来覆盖它们的每个实例的响应或请求方法并不是一个好主意,因为中间件会针对每个请求执行,并且每次调用它时都会使用 CPU 和内存,因为你正在创建一个新函数。

您可能知道 javascript 是一种基于原型的语言,每个对象都有一个原型,例如响应和请求对象。通过查看代码(express 4.13.4),您可以找到它们的原型:

req => express.request
res => express.response

因此,当您想为响应的每个实例覆盖一个方法时,最好在它的原型中覆盖它,因为它在每个响应实例中都可用:

var app = (global.express = require('express'))();
var render = express.response.render;
express.response.render = function(view, options, callback) {
    // desired code
    /** here this refer to the current res instance and you can even access req for this res: **/
    console.log(this.req);
    render.apply(this, arguments);
};

【讨论】:

  • 我不知道为什么这条评论不被接受!这是绝对正确的答案!。
【解决方案4】:

我最近发现自己需要做同样的事情,为我的每个模板提供特定于配置的 Google Analytics(分析)属性 ID 和 cookie 域。

这里有很多很棒的解决方案。

我选择采用与 Lex 提出的解决方案非常接近的方法,但遇到了调用 res.render() 尚未包含现有选项的问题。例如,以下代码在调用 extend() 时导致异常,因为未定义选项:

return res.render('admin/refreshes');

我添加了以下内容,它解释了可能的各种参数组合,包括回调。其他人提出的解决方案也可以使用类似的方法。

app.use(function(req, res, next) {
  var _render = res.render;
  res.render = function(view, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = {};
    } else if (!options) {
      options = {};
    }
    extend(options, {
      gaPropertyID: config.googleAnalytics.propertyID,
      gaCookieDomain: config.googleAnalytics.cookieDomain
    });
    _render.call(this, view, options, callback);
  }
  next();
});

编辑: 事实证明,当我需要实际运行一些代码时,这一切可能会很方便,但有一种非常简单的方法可以完成我想要做的事情。我再次查看了 Express 的源代码和文档,结果发现 app.locals 用于渲染每个模板。因此,就我而言,我最终将上面所有的中间件代码替换为以下分配:

app.locals.gaPropertyID = config.googleAnalytics.propertyID;
app.locals.gaCookieDomain = config.googleAnalytics.cookieDomain;

【讨论】:

  • 两个非常好的观点。 locals 属性适合大多数情况。如果没有传递任何选项来渲染回调,那么回调就会变得未定义,所有的地狱都会崩溃。
猜你喜欢
  • 1970-01-01
  • 2016-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-25
  • 1970-01-01
  • 2020-10-07
  • 2012-05-22
相关资源
最近更新 更多