2012-02-14 11 views
21

Sto costruendo un'applicazione Node.js con Connect/Express.js e voglio intercettare il (, vista opzionale) la funzione res.render per eseguire del codice prima di inoltrarlo al funzione originale di rendering.Node.js/Express.js - Come sovrascrivere/intercettare la funzione 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' }); 
}); 

Sembra un esempio forzato, ma si inserisce in un quadro generale che sto costruendo.

La mia conoscenza della programmazione orientata agli oggetti e ereditarietà prototipale su JavaScript è un po 'debole. Come potrei fare qualcosa di simile?


Aggiornamento: Dopo alcuni esperimenti mi si avvicinò con il seguente:

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

Sembra funzionare. Non sono sicuro che sia la soluzione migliore perché sto creando un nuovo oggetto wrapper di risposta per ogni richiesta, sarebbe un problema?

+1

Questo è normalmente il modo in cui lo si fa. Se lo metti in un middleware che viene utilizzato prima del router, puoi configurarlo in un unico posto. –

+0

Ho pensato di utilizzare il middleware. Non sono sicuro se voglio sovrascrivere il comportamento di risposta per l'intera applicazione, solo per le richieste che vengono instradate attraverso il mio framework. –

risposta

22

vecchia questione, ma ha trovato me stesso a chiedere il stessa cosa. Come intercettare res rendering? Uso di Express 4.0x qualcosa ora.

È possibile utilizzare/middleware scrittura. All'inizio il concetto era un po 'scoraggiante, ma dopo averlo letto ha avuto un po' più senso. E solo per qualche contesto per chiunque altro leggendo questo, la motivazione per sovrascrivere res.render era quella di fornire variabili di visualizzazione globali. Voglio che lo session sia disponibile in tutti i miei modelli senza che io debba digitarlo in tutti gli oggetti res.

Il formato di base middleware è.

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

Il prossimo param e chiamata di funzione sono cruciali per l'esecuzione. next è la funzione di callback, per consentire a più middleware di fare le loro cose senza bloccare. Per una migliore explanation read here

Questo può quindi essere utilizzato per sostituire il rendering logica

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

Ho provato questo codice, usando espresso 3.0.6. Dovrebbe funzionare con 4.x senza problemi. È inoltre possibile ignorare URL specifici combo con

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

Sostituisce la funzione ad ogni richiesta, esercitando pressione sul GC? Avrebbe più senso scavalcare il prototipo? – jocull

+0

Sostituisce su ogni richiesta. Sostituirà il prototipo tramite l'ombreggiamento delle proprietà. È possibile sovrascrivere il prototipo se lo si desidera, ma in genere si desidera proprietà sull'oggetto principale non nella catena del prototipo. La proprietà cerca il tempo è più veloce in questo modo. – Lex

+1

All'inizio non ho avuto questa risposta, ma dopo aver giocato un po 'su me stesso, ora capisco. Puoi verificare una variazione di Lex nel suo codice su https://github.com/mettamage/renderExtMiddleware - Non uso '_.extend (args)'. Il mio codice restituisce una vista normale * tranne * quando il client non è un browser, quindi restituisce json. –

7

L'oggetto response non dispone di un prototipo. Questo dovrebbe funzionare (prendendo l'idea di Ryan di metterlo in un middleware):

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

Tuttavia, potrebbe essere migliore per incidere il 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

Grazie per quello. Anche se questo sembra infrangere il modo in cui cerca la vista layout/master, cerca di cercare 'testViews/layout.ejs'. Ho dato un'occhiata all'origine e ho visto che un metodo _render è già definito sull'oggetto risposta, questo potrebbe causare problemi? –

+0

Sì, è corretto: anteporrà "testViews /" a tutte le viste. Non ha nulla a che fare con 'ServerResponse._render'. Cosa stai cercando di realizzare? –

+0

Tentativo di implementare l'hacking ServerRespone.prototype e non riuscito. Indovina che ha funzionato nel nodo 0.6/express 2? Hai idea di come farlo funzionare nel nodo 0.8 e Express 3? –

6

Non è una buona idea di utilizzare un middleware per ignorare una risposta o metodo di richiesta per ogni istanza di loro, perché il middleware è ottenere eseguito per ogni richiesta e ogni il tempo in cui viene chiamato si utilizza la CPU e la memoria perché si sta creando una nuova funzione.

Come forse sapete JavaScript è un linguaggio basato sui prototipi e ogni oggetto ha un prototipo, come oggetti di risposta e di richiesta. Guardando il codice (express 4.13.4) si possono trovare i loro prototipi:

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

Quindi, quando si desidera sovrascrivere un metodo per ogni singola istanza di una risposta è molto molto meglio ignorare che in esso è il prototipo come è fatto una volta è disponibile in ogni caso della risposta:

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

Non so perché questo commento non è accettato! è la risposta giusta in assoluto !. –

3

di recente ho ritrovato a dover fare la stessa cosa, per fornire un Google Analytics proprietà id e cookie di dominio specifico di configurazione per ciascuno dei miei modelli.

Ci sono un certo numero di grandi soluzioni qui.

Ho scelto di adottare qualcosa di molto vicino alla soluzione proposta da Lex, ma ho riscontrato problemi in cui le chiamate a res.render() non includevano già le opzioni esistenti. Per esempio, il codice seguente stava causando un'eccezione nella chiamata di estendere(), in quanto le opzioni è stato undefined:

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

ho aggiunto il seguente, che rappresenta le varie combinazioni di argomenti che sono possibili, tra cui il callback . Un approccio simile può essere utilizzato con le soluzioni proposte da altri.

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: scopre che mentre tutto questo potrebbe essere a portata di mano quando ho bisogno di correre in realtà un po 'di codice, c'è un modo estremamente semplice per realizzare quello che stavo cercando di fare. Ho guardato di nuovo la fonte e i documenti per Express, e si scopre che gli app.locals sono usati per rendere ogni modello. Quindi, nel mio caso, alla fine ho sostituito tutto il codice middleware precedente con i seguenti incarichi:

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

Due punti davvero buoni. La proprietà del luogo si adatterebbe alla maggior parte dei casi. E se non vengono passate opzioni per rendere la callback non definita, e si scatena l'inferno. – Lex

Problemi correlati