2012-02-14 2 views
21

Я создаю приложение Node.js с Connect/Express.js, и я хочу перехватить функцию res.render (view, option) для запуска некоторого кода перед отправкой его на исходную функцию рендеринга.Node.js/Express.js - Как переопределить/перехватить функцию res.render?

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 немного слабо. Как мне сделать что-то подобное?


Update: После нескольких экспериментов я придумал следующее:

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

Это похоже на работу. Не уверен, что это лучшее решение, поскольку я создаю новый объект оболочки ответа для каждого запроса, будет ли это проблемой?

+1

Как правило, вы это делаете. Если вы поместите это в промежуточное программное обеспечение, которое используется перед маршрутизатором, вы можете настроить его в одном месте. –

+0

Я думал об использовании промежуточного программного обеспечения. Не уверен, что я хочу переопределить поведение ответа для всего приложения, только для запросов, которые маршрутизируются через мою инфраструктуру. –

ответ

22

Старый вопрос, но обнаружил, что попросив то же самое. Как перехватить res render? Используя express 4.0x что-то сейчас.

Вы можете использовать/написать промежуточное программное обеспечение. Поначалу концепция была немного сложной, но после некоторого чтения это немного пошло. И только для некоторого контекста для тех, кто читает это, мотивация переопределения res.render заключалась в предоставлении глобальных переменных представления. Я хочу, чтобы session был доступен во всех моих шаблонах, без необходимости вводить его в каждом res-объекте.

Основной формат промежуточного программного обеспечения.

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

Следующий вызов функции param и function имеет решающее значение для выполнения. 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(); 
}); 

Я тестировал этот код, используя экспресс 3.0.6. Он должен работать с 4.x без проблем. Вы также можете переопределить конкретные URL-адрес комбинацию с

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

Означает ли это отмену функции по каждому запросу, оказывая давление на GC? Будет ли смысл переопределять прототип? – jocull

+0

Это переопределение по каждому запросу. Он переопределит прототип с помощью теневого копирования свойств. Можете переопределить прототип, если хотите, но обычно вы хотите, чтобы свойства на основном объекте не были глубокими в цепочке прототипов. Время поиска недвижимости быстрее, таким образом. – Lex

+1

Сначала я не получил этот ответ, но после того, как возился с самим собой, теперь я его получаю. Вы можете проверить вариант Lex своего кода на https://github.com/mettamage/renderExtMiddleware - я не использую '_.extend (args)'. Мой код отображает обычный вид * кроме *, когда клиент не является браузером, а затем возвращает json. –

7

Объект response не имеет прототипа. Это должно работать (принимая идею Райана сдачи его в ПО промежуточного слоя):

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

Спасибо за это. Хотя это, похоже, нарушает то, как он ищет макет/мастер-представление, он пытается искать «testViews/layout.ejs». Я посмотрел на источник и увидел, что метод _render уже определен в объекте ответа, может ли это вызвать проблемы? –

+0

Да, это правильно - он будет добавлять «testViews /» ко всем представлениям. Он не имеет ничего общего с 'ServerResponse._render'. Что вы пытаетесь достичь? –

+0

Пробовал взломать и сбросить ServerRespone.prototype. Угадайте, что это работало в узле 0.6/express 2? Вы знаете, как сделать эту работу в узле 0.8 и выразить 3? –

6

Это не очень хорошая идея использовать промежуточное программное обеспечение, чтобы переопределить ответ или метод запроса для каждого экземпляра из них, так как промежуточное программного обеспечения получить выполняются для каждого и каждого запроса и каждого время, когда это вызвано, вы используете CPU, а также память, потому что вы создаете новую функцию.

Как вы знаете, javascript - это язык, основанный на Prototype, и каждый объект имеет прототип, например, объекты ответа и запроса. Изучая код (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); 
}; 
+1

Я не знаю, почему этот комментарий не принят! это абсолютный правильный ответ !. –

3

недавно я обнаружил, что необходимости делать то же самое, чтобы обеспечить конфигурации конкретной Google Analytics ID объекта и печенья домен к каждому из моих шаблонов.

Здесь есть множество отличных решений.

Я решил пойти с чем-то очень близким к решению, предложенным Лексом, но столкнулся с проблемами, когда вызовы res.render() еще не включали существующие параметры. Например, следующий код вызывал исключение при вызове расширения(), потому что варианты были неопределенными:

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; 
+0

Два действительно хорошие моменты. Свойство locals будет соответствовать большинству случаев. И если никакие опции не переданы, чтобы сделать обратный вызов становится неопределенным, и все ад разрывается. – Lex

Смежные вопросы