@@ -8,6 +8,10 @@ import promClient from 'prom-client'
88import l10n from '../client/l10n'
99import render from '../client/run-server'
1010
11+ if ( ! process . env . API_URL ) {
12+ throw new Error ( 'API_URL environment variable is required but not defined.' ) ;
13+ }
14+
1115const themes = [ 'light' , 'dark' ]
1216 , langs = Object . keys ( l10n )
1317 , baseHref = process . env . BASE_HREF || '/'
@@ -56,17 +60,30 @@ if (app.settings.env == 'development')
5660app . use ( require ( 'cookie-parser' ) ( ) )
5761app . use ( require ( 'body-parser' ) . urlencoded ( { extended : false } ) )
5862
59- app . use ( ( req , res , next ) => {
60- // TODO: optimize /block-height/nnn (no need to render the whole app just to get the redirect)
63+ const queue = process . env . MAX_PENDING_RENDERS && require ( 'express-queue' ) ( {
64+ activeLimit : 1 , // handled by the master process, see below
65+ queuedLimit : parseInt ( process . env . MAX_PENDING_RENDERS , 10 )
66+ } ) ;
6167
68+ app . use ( ( req , res , next ) => {
69+ // Middleware to check theme and lang cookies
6270 let theme = req . query . theme || req . cookies . theme || 'dark'
6371 if ( ! themes . includes ( theme ) ) theme = 'light'
6472 if ( req . query . theme && req . cookies . theme !== theme ) res . cookie ( 'theme' , theme )
6573
6674 let lang = req . query . lang || req . cookies . lang || 'en'
6775 if ( ! langs . includes ( lang ) ) lang = 'en'
6876 if ( req . query . lang && req . cookies . lang !== lang ) res . cookie ( 'lang' , lang )
77+ req . renderOpts = { theme, lang }
78+ next ( )
79+ } )
80+
81+ if ( queue ) app . use ( queue )
82+
83+ app . use ( ( req , res , next ) => {
84+ // TODO: optimize /block-height/nnn (no need to render the whole app just to get the redirect)
6985
86+ // IPC-based queuing for cluster mode
7087 if ( typeof process . send === 'function' ) {
7188 const requestId = ++ requestCounter
7289 process . send ( { type : 'startRender' , requestId } )
@@ -77,8 +94,9 @@ app.use((req, res, next) => {
7794 clearTimeout ( timeout )
7895 process . removeListener ( 'message' , handler )
7996 if ( msg . type === 'renderAllowed' ) {
80- doRender ( )
97+ doRender ( req , res , next )
8198 } else if ( msg . type === 'renderDenied' ) {
99+ // received when the master's render queue is full
82100 res . status ( 503 ) . send ( 'Server overloaded' )
83101 }
84102 }
@@ -93,40 +111,45 @@ app.use((req, res, next) => {
93111 }
94112 } , 5000 ) // 5 second timeout
95113 } else {
96- doRender ( )
114+ // standalone mode
115+ doRender ( req , res , next )
97116 }
117+ } )
98118
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- }
119+ function doRender ( req , res , next ) {
120+ activeRenders . inc ( )
121+ const end = renderDuration . startTimer ( )
122+ let metricsUpdated = false
123+ render ( req . _parsedUrl . pathname , req . _parsedUrl . query || '' , req . body , { ...req . renderOpts , isHead : req . method === 'HEAD' } , ( err , resp ) => {
124+ if ( ! metricsUpdated ) {
125+ metricsUpdated = true
126+ // inform the master process that we're done rendering and can accept new requests
127+ if ( typeof process . send === 'function' ) process . send ( { type : 'endRender' } )
128+ // and tell express-queue that we're ready for the next one
129+ if ( queue ) queue . next ( )
130+ activeRenders . dec ( )
131+ end ( )
132+ totalRenders . inc ( )
133+ }
117134
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- } )
135+ if ( err ) return next ( err )
136+ if ( resp . redirect ) return res . redirect ( 301 , baseHref + resp . redirect )
137+ if ( resp . errorCode ) {
138+ console . error ( `Failed with code ${ resp . errorCode } :` , resp )
139+ return res . sendStatus ( resp . errorCode )
140+ }
141+
142+ res . status ( resp . status || 200 )
143+ res . render ( indexView , {
144+ prerender_title : resp . title
145+ , prerender_html : resp . html
146+ , canon_url : canonBase ? canonBase + req . url : null
147+ , noscript : true
148+ , ...req . renderOpts
149+ , t : l10n [ req . renderOpts . lang ]
127150 } )
128- }
129- } )
151+ } )
152+ }
130153
131154// Cleanup socket file from previous executions
132155if ( process . env . SOCKET_PATH ) {
0 commit comments