@@ -12,7 +12,7 @@ const themes = ['light', 'dark']
1212 , langs = Object . keys ( l10n )
1313 , baseHref = process . env . BASE_HREF || '/'
1414 , canonBase = process . env . CANONICAL_URL ? process . env . CANONICAL_URL . replace ( / \/ $ / , '' ) : null
15- , apiUrl = process . env . API_URL . replace ( / \/ $ / , '' )
15+ , apiUrl = process . env . API_URL ? process . env . API_URL . replace ( / \/ $ / , '' ) : ''
1616
1717const rpath = p => path . join ( __dirname , p )
1818
@@ -56,17 +56,30 @@ if (app.settings.env == 'development')
5656app . use ( require ( 'cookie-parser' ) ( ) )
5757app . use ( require ( 'body-parser' ) . urlencoded ( { extended : false } ) )
5858
59- app . use ( ( req , res , next ) => {
60- // TODO: optimize /block-height/nnn (no need to render the whole app just to get the redirect)
59+ const queue = process . env . MAX_PENDING_RENDERS && require ( 'express-queue' ) ( {
60+ activeLimit : 1 , // handled by the master process, see below
61+ queuedLimit : process . env . MAX_PENDING_RENDERS
62+ } ) ;
6163
64+ app . use ( ( req , res , next ) => {
65+ // Middleware to check theme and lang cookies
6266 let theme = req . query . theme || req . cookies . theme || 'dark'
6367 if ( ! themes . includes ( theme ) ) theme = 'light'
6468 if ( req . query . theme && req . cookies . theme !== theme ) res . cookie ( 'theme' , theme )
6569
6670 let lang = req . query . lang || req . cookies . lang || 'en'
6771 if ( ! langs . includes ( lang ) ) lang = 'en'
6872 if ( req . query . lang && req . cookies . lang !== lang ) res . cookie ( 'lang' , lang )
73+ req . renderOpts = { theme, lang }
74+ next ( )
75+ } )
76+
77+ if ( queue ) app . use ( queue )
6978
79+ app . use ( ( req , res , next ) => {
80+ // TODO: optimize /block-height/nnn (no need to render the whole app just to get the redirect)
81+
82+ // IPC-based queuing for cluster mode
7083 if ( typeof process . send === 'function' ) {
7184 const requestId = ++ requestCounter
7285 process . send ( { type : 'startRender' , requestId } )
@@ -77,8 +90,9 @@ app.use((req, res, next) => {
7790 clearTimeout ( timeout )
7891 process . removeListener ( 'message' , handler )
7992 if ( msg . type === 'renderAllowed' ) {
80- doRender ( )
93+ doRender ( req , res , next )
8194 } else if ( msg . type === 'renderDenied' ) {
95+ // received when the master's render queue is full
8296 res . status ( 503 ) . send ( 'Server overloaded' )
8397 }
8498 }
@@ -90,43 +104,49 @@ app.use((req, res, next) => {
90104 process . removeListener ( 'message' , handler )
91105 console . error ( 'IPC timeout for request' , requestId )
92106 res . status ( 500 ) . send ( 'Internal server error' )
107+ if ( typeof process . send === 'function' ) process . send ( { type : 'endRender' } )
93108 }
94109 } , 5000 ) // 5 second timeout
95110 } else {
96- doRender ( )
111+ // standalone mode
112+ doRender ( req , res , next )
97113 }
114+ } )
98115
99- function doRender ( ) {
100- activeRenders . inc ( )
101- const end = renderDuration . startTimer ( )
102- let metricsUpdated = false
103- render ( req . _parsedUrl . pathname , req . _parsedUrl . query || '' , req . body , { theme, lang, isHead : req . method === 'HEAD' } , ( err , resp ) => {
104- if ( ! metricsUpdated ) {
105- metricsUpdated = true
106- if ( typeof process . send === 'function' ) process . send ( { type : 'endRender' } )
107- activeRenders . dec ( )
108- end ( )
109- totalRenders . inc ( )
110- }
111- if ( err ) return next ( err )
112- if ( resp . redirect ) return res . redirect ( 301 , baseHref + resp . redirect )
113- if ( resp . errorCode ) {
114- console . error ( `Failed with code ${ resp . errorCode } :` , resp )
115- return res . sendStatus ( resp . errorCode )
116- }
116+ function doRender ( req , res , next ) {
117+ activeRenders . inc ( )
118+ const end = renderDuration . startTimer ( )
119+ let metricsUpdated = false
120+ render ( req . _parsedUrl . pathname , req . _parsedUrl . query || '' , req . body , { ...req . renderOpts , isHead : req . method === 'HEAD' } , ( err , resp ) => {
121+ if ( ! metricsUpdated ) {
122+ metricsUpdated = true
123+ // inform the master process that we're done rendering and can accept new requests
124+ if ( typeof process . send === 'function' ) process . send ( { type : 'endRender' } )
125+ // and tell express-queue that we're ready for the next one
126+ if ( queue ) queue . next ( )
127+ activeRenders . dec ( )
128+ end ( )
129+ totalRenders . inc ( )
130+ }
117131
118- res . status ( resp . status || 200 )
119- res . render ( indexView , {
120- prerender_title : resp . title
121- , prerender_html : resp . html
122- , canon_url : canonBase ? canonBase + req . url : null
123- , noscript : true
124- , theme
125- , t : l10n [ lang ]
126- } )
132+ if ( err ) return next ( err )
133+ if ( resp . redirect ) return res . redirect ( 301 , baseHref + resp . redirect )
134+ if ( resp . errorCode ) {
135+ console . error ( `Failed with code ${ resp . errorCode } :` , resp )
136+ return res . sendStatus ( resp . errorCode )
137+ }
138+
139+ res . status ( resp . status || 200 )
140+ res . render ( indexView , {
141+ prerender_title : resp . title
142+ , prerender_html : resp . html
143+ , canon_url : canonBase ? canonBase + req . url : null
144+ , noscript : true
145+ , ...req . renderOpts
146+ , t : l10n [ req . renderOpts . lang ]
127147 } )
128- }
129- } )
148+ } )
149+ }
130150
131151// Cleanup socket file from previous executions
132152if ( process . env . SOCKET_PATH ) {
0 commit comments