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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9285880/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 06:11:03  来源:igfitidea点击:

Node.js / Express.js - How to override/intercept res.render function?

javascriptnode.jsexpressconnectprototypal-inheritance

提问by Sunday Ironfoot

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.

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

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?

我对 JavaScript 的 OOP 和原型继承的了解有点薄弱。我将如何做这样的事情?



Update: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?

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

回答by Lex

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

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

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 sessionto be available in all my templates without me having to type it in every res object.

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

The basic middleware format is.

基本的中间件格式是。

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

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

下一个参数和函数调用对执行至关重要。next是回调函数,允许多个中间件在不阻塞的情况下做他们的事情。如需更好的解释,请阅读此处

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

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

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

回答by Ali

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.

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

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:

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

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

回答by Linus Gustav Larsson Thiel

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

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

However, it might be better to hack the ServerResponse.prototype:

但是,破解 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);
};

回答by Timothy Johns

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.

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

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:

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

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:

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

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