Node.js / Express.js – 如何重写/拦截res.render函数?

我正在用Connect / Express.js构build一个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' }); }); 

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

我对JavaScript和OOP和Prototypalinheritance的知识有点弱。 我将如何做这样的事情?


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

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

这似乎工作。 不知道是否是最好的解决scheme,因为我为每个请求创build一个新的响应包装对象,那会是一个问题吗?

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

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

基本的中间件格式是。

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

下一个参数和函数调用对执行至关重要。 next就是callback函数,允许多个中间件做他们没有阻塞的事情。 在这里阅读更好的解释

这可以用来覆盖渲染逻辑

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

我已经testing了这个代码,使用Express 3.0.6。 它应该与4.x一起工作,没有问题。 您也可以覆盖特定的URL组合

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

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

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

正如你可能知道的JavaScript是一个基于原型的语言,每个对象都有一个原型,如响应和请求对象。 通过查看代码( express 4.13.4 ),你可以find他们的原型:

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

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

这里有很多很棒的解决scheme。

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

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

我添加了以下内容,这些内容说明了可能的参数的各种组合,包括callback。 他人提出的解决scheme可以采用类似的方法。

 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的源代码和文档,事实certificate,app.locals用于呈现每个模板。 所以在我的情况下,我最终用以下分配replace了上面所有的中间件代码:

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