Node.js / Express.js - 如何覆盖/拦截 res.render 函数?

发布于 2025-01-05 19:49:32 字数 910 浏览 0 评论 0原文

我正在使用 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' });
});

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

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

经过一些实验,我得出以下结论:

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' });
});

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

I'm building a Node.js app with Connect/Express.js and I want to intercept the res.render(view, option) function to run some code before forwarding it on to the original render function.

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' });
});

It looks like a contrived example, but it does fit in an overall framework I'm building.

My knowledge of OOP and Prototypal inheritance on JavaScript is a bit weak. How would I do something like this?

After some experimentation I came up with the following:

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' });
});

It seems to work. Not sure if it's the best solution as I'm creating a new response wrapper object for each request, would that be a problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

耶耶耶 2025-01-12 19:49:32

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

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

基本的中间件格式是。

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

下一个参数和函数调用对于执行至关重要。 next 是回调函数,允许多个中间件在不阻塞的情况下完成其任务。要获得更好的解释请阅读此处

这可以是用于覆盖渲染逻辑

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 ) {...} );

Old question, but found myself asking the same thing. How to intercept res render?
Using express 4.0x something now.

You can use/write middleware. The concept was a bit daunting to me at first, but after some reading it made a little more sense. And just for some context for anyone else reading this, the motivation for overriding res.render was to provide global view variables. I want session to be available in all my templates without me having to type it in every res object.

The basic middleware format is.

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

The next param and function call are crucial to execution. next is the callback function, to allow multiple middleware to do their thing without blocking. For a better explanation read here

This can then be used to override render logic

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();
} );

I've tested this code, using express 3.0.6. It should work with 4.x without issue.
You can also override specific URLs combos with

app.use( '/myspcificurl', function( req, res, next ) {...} );
不再见 2025-01-12 19:49:32

使用中间件来覆盖每个实例的响应或请求方法并不是一个好主意,因为中间件会针对每个请求执行,并且每次调用它时,您都会使用 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);
};

It's not a good idea to use a middleware to override a response or request method for each instance of them because the middleware is get executed for each and every request and each time it get called you use cpu as well as memory because you are creating a new function.

As you may know javascript is a Prototype-based language and every object has a prototype, like response and request objects. By looking at code (express 4.13.4) you can find their prototypes:

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

So when you want to override a method for every single instance of a response it's much much better to override it in it's prototype as it's done once is available in every instance of 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);
};
猫卆 2025-01-12 19:49:32

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);
};

The response object doesn't have a prototype. This should work (taking Ryan's idea of putting it in a middleware):

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

However, it might be better to hack the 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);
};
淡写薰衣草的香 2025-01-12 19:49:32

我最近发现自己需要做同样的事情,为我的每个模板提供特定于配置的 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;

I recently found myself needing to do the same thing, to provide a configuration-specific Google Analytics property id and cookie domain to each of my templates.

There are a number of great solutions here.

I chose to go with something very close to the solution proposed by Lex, but ran into problems where calls to res.render() did not already include existing options. For instance, the following code was causing an exception in the call to extend(), because options was undefined:

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

I added the following, which accounts for the various combinations of arguments that are possible, including the callback. A similar approach can be used with the solutions proposed by others.

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();
});

edit: Turns out that while this all might be handy when I need to actually run some code, there's a tremendously simpler way to accomplish what I was trying to do. I looked again at the source and docs for Express, and it turns out that the app.locals are used to render every template. So in my case, I ultimately replaced all of the middleware code above with the following assignments:

app.locals.gaPropertyID = config.googleAnalytics.propertyID;
app.locals.gaCookieDomain = config.googleAnalytics.cookieDomain;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文