1 | import 'logger/server'
|
2 | import async from 'async'
|
3 | import cookie from 'cookie'
|
4 | import bodyParser from 'body-parser'
|
5 | import cookieParser from 'cookie-parser'
|
6 | import express from 'express'
|
7 | import InternationalizationHandler from 'i18n/InternationalizationHandler'
|
8 | import UniversalPageHandler from 'handler/UniversalPageHandler'
|
9 | import path from 'path'
|
10 | import preconditions from 'preconditions'
|
11 | import querystring from 'querystring'
|
12 | import React from 'react'
|
13 | import { renderToStaticMarkup } from 'react-dom/server'
|
14 | import { Provider } from 'react-redux'
|
15 | import { Route, RouterContext, match } from 'react-router'
|
16 | import { createStore, combineReducers, applyMiddleware } from 'redux'
|
17 | import thunk from 'redux-thunk'
|
18 | import SimpleHtmlRenderer from 'render/SimpleHtmlRenderer'
|
19 | import { serverFetchReducer } from 'fetch/core'
|
20 | import resolve from 'es6-template-strings/resolve-to-string'
|
21 | import forceDomain from 'forcedomain'
|
22 | import url from 'url'
|
23 | import rfs from 'rotating-file-stream'
|
24 | import morgan from 'morgan'
|
25 | import fs from 'fs'
|
26 | import parseUrl from 'parseurl'
|
27 |
|
28 | let project = __PROJECT__
|
29 | let logger = global.Logger.getLogger('ExpressUniversalApplicationServer')
|
30 |
|
31 | let LOCALE_REGEX = /^\/([a-z]{2})-([a-z]{2})(.*)$/
|
32 | let LOCALE_DEFAULT = 'id-id'
|
33 |
|
34 | let QS_REGEX = /^\?(.*)$/
|
35 |
|
36 | const createStoreWithMiddleware = applyMiddleware(
|
37 | thunk
|
38 | )(createStore)
|
39 |
|
40 |
|
41 | process.on('uncaughtException', function (err) {
|
42 | console.log("[CRASH] [ERROR] error log: ", err)
|
43 | })
|
44 |
|
45 | class ExpressUniversalApplicationServer {
|
46 | constructor (options) {
|
47 | let pc = preconditions.instance(options)
|
48 | pc.shouldBeDefined('port', 'port must be defined.')
|
49 | pc.shouldBeDefined('rootApplicationPath', 'rootApplicationPath must be defined.')
|
50 | pc.shouldBeDefined('rootDeploymentApplicationPath', 'rootDeploymentApplicationPath must be defined.')
|
51 | this._options = options
|
52 | this._app = express()
|
53 | this._routes = this.getRoutes()
|
54 | this._reducers = this.getReducers()
|
55 | this._initialize()
|
56 | }
|
57 |
|
58 | _initialize () {
|
59 |
|
60 | this._setupForceDomain()
|
61 | this._setupAssetsServing()
|
62 | this._setupCookieParser()
|
63 | this._setupBodyParser()
|
64 | this._setupHtmlRenderer()
|
65 | this._setupInternationalizedRoutes()
|
66 | this._setupI18nHandler()
|
67 | }
|
68 |
|
69 | _setupRequestLogMiddleware () {
|
70 | let logDirectory = '/logs/' + project.applicationName
|
71 | fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory)
|
72 |
|
73 | function pad (num) {
|
74 | return (num > 9 ? '' : '0') + num
|
75 | }
|
76 |
|
77 | function generator (time, index) {
|
78 | logger.info('[GENERATOR] Request log rotation ' + time + '-' + index)
|
79 | if (!time) {
|
80 |
|
81 | }
|
82 |
|
83 | var yearMonth = time.getFullYear() + '-' + pad(time.getMonth() + 1)
|
84 | var day = pad(time.getDate())
|
85 | var hour = pad(time.getHours())
|
86 | var minute = pad(time.getMinutes())
|
87 | var seconds = pad(time.getSeconds())
|
88 |
|
89 | logger.info('[GENERATOR] ' + logDirectory + '/request-' + yearMonth + '-' + day + '-' + hour + '-' + minute + '-' + seconds + '.log.gz')
|
90 |
|
91 | return 'request-' + yearMonth + '-' + day + '-' + hour + '-' + minute + '-' + seconds + 'log.gz'
|
92 | }
|
93 |
|
94 | this._accessLogStream = rfs(generator, {
|
95 | path: logDirectory,
|
96 | compress: true,
|
97 | interval: '10s',
|
98 | rotate: 7
|
99 | })
|
100 |
|
101 | logger.info('Setting up morgan (Request Log Middleware)')
|
102 | this._app.use(morgan('combined', { stream: this._accessLogStream }))
|
103 | }
|
104 |
|
105 | _setupAssetsServing () {
|
106 | console.log(path.join(this._options.rootDeploymentApplicationPath, 'build', this._options.environment, 'client'))
|
107 |
|
108 | logger.info('Using ' + path.join(this._options.rootDeploymentApplicationPath, 'build', this._options.environment, 'client') + ', with /assets as assets serving routes')
|
109 | this._app.use('/assets', express.static(path.join(this._options.rootDeploymentApplicationPath, 'build', this._options.environment, 'client')))
|
110 |
|
111 | logger.info('Using ' + path.join(this._options.rootDeploymentApplicationPath, 'build', this._options.environment, 'server', 'debugging') + ', with /__dev/assets/server/debugging as server-debugging serving routes')
|
112 | this._app.use('/__dev/assets/server/debugging', express.static(path.join(this._options.rootDeploymentApplicationPath, 'build', this._options.environment, 'server', 'debugging')))
|
113 |
|
114 |
|
115 | logger.info('Using ' + path.join(this._options.rootDeploymentApplicationPath, 'assets') + ', with /assets/static as static non-compileable assets serving routes')
|
116 | this._app.use('/assets/static', express.static(path.join(this._options.rootDeploymentApplicationPath, 'assets')))
|
117 | }
|
118 |
|
119 | _setupCookieParser () {
|
120 | this._app.use(cookieParser())
|
121 | }
|
122 |
|
123 | _setupBodyParser () {
|
124 | this._app.use(bodyParser.urlencoded({ extended: true }))
|
125 | this._app.use(bodyParser.json())
|
126 | }
|
127 |
|
128 | _setupHtmlRenderer () {
|
129 | let htmlRendererOptions = {
|
130 | path: path.join(this._options.rootDeploymentApplicationPath, 'html'),
|
131 | cache: !(this._options.environment === 'development')
|
132 | }
|
133 | this._renderer = new SimpleHtmlRenderer(htmlRendererOptions)
|
134 | }
|
135 |
|
136 | _setupInternationalizedRoutes () {
|
137 | let finalRoutes = null
|
138 | let originalRoutes = this.getRoutes()
|
139 | let generatedRoutes = []
|
140 | this._options.locales.forEach((locale) => {
|
141 | let internationalRoute = (
|
142 | <Route key={locale} path={locale} component={InternationalizationHandler}>
|
143 | {originalRoutes}
|
144 | </Route>
|
145 | )
|
146 | generatedRoutes.push(internationalRoute)
|
147 | })
|
148 |
|
149 | generatedRoutes.push(originalRoutes)
|
150 |
|
151 | finalRoutes = (
|
152 | <Route path='/' component={UniversalPageHandler}>
|
153 | {
|
154 | generatedRoutes.map((route) => {
|
155 | return route
|
156 | })
|
157 | }
|
158 | </Route>
|
159 | )
|
160 |
|
161 | this._routes = finalRoutes
|
162 | }
|
163 |
|
164 | _setupI18nHandler () {
|
165 | this._app.use((req, res, next) => {
|
166 | let match = LOCALE_REGEX.exec(req.url)
|
167 | let url = null
|
168 | if (match != null) {
|
169 | let lang = match[1]
|
170 | let country = match[2]
|
171 | url = match[3]
|
172 | req.locale = lang + '-' + country
|
173 | } else {
|
174 | req.locale = LOCALE_DEFAULT
|
175 | }
|
176 | if (this._options.locales.indexOf(req.locale) >= 0) {
|
177 | next()
|
178 | } else {
|
179 | if (this._options.locales.length === 0) {
|
180 | next()
|
181 | } else {
|
182 | if (url == null || typeof url === 'undefined' || url.length === 0) {
|
183 | res.redirect('/')
|
184 | } else {
|
185 | res.redirect(url)
|
186 | }
|
187 | }
|
188 | }
|
189 | })
|
190 | }
|
191 |
|
192 | _isIP (host) {
|
193 | let ipRegex = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/
|
194 | return ipRegex.test(host)
|
195 | }
|
196 |
|
197 | _setupForceDomain () {
|
198 | let protocol = 'http'
|
199 | if (this._options.forceHttps === true) {
|
200 | protocol = 'https'
|
201 | }
|
202 | let hostName = this._stripProtocol(this._options.applicationHost)
|
203 | if (!this._isIP(hostName)) {
|
204 | let parsedApplicationHost = url.parse(this._options.applicationHost)
|
205 | if (parsedApplicationHost.port == null) {
|
206 | this._app.use(forceDomain({
|
207 | hostname: parsedApplicationHost.host,
|
208 | protocol: protocol
|
209 | }))
|
210 | }
|
211 | }
|
212 | }
|
213 |
|
214 | _stripProtocol (url) {
|
215 | if (url != null && typeof url !== 'undefined') {
|
216 | let result = url.replace(/.*?:\/\//g, '')
|
217 | return result
|
218 | }
|
219 | return null
|
220 | }
|
221 |
|
222 | _handleError500 (message, err, res) {
|
223 | logger.error(message, err)
|
224 | if (this._options.environment === 'production') {
|
225 | res.redirect(this.getErrorHandler())
|
226 | } else {
|
227 | let error = '<br />'
|
228 | if (this._options.environment === 'development') {
|
229 | if (err != null && typeof err !== 'undefined') {
|
230 | error += err.stack
|
231 | }
|
232 | }
|
233 | error = error.replace('\n', '<br />')
|
234 | this._renderer.render('500.html', (err, rendered) => {
|
235 | if (err) {
|
236 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 500.', err)
|
237 | return res.status(500).end('An unknown error occurred when trying to render error 500\n' + err.stack)
|
238 | } else {
|
239 | return res.status(500).end(resolve(rendered, { ERROR: error }))
|
240 | }
|
241 | })
|
242 | }
|
243 | }
|
244 |
|
245 | _handleNotFound404 (res) {
|
246 | this._renderer.render('404.html', (err, rendered) => {
|
247 | if (err) {
|
248 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 404.', err)
|
249 | return res.status(500).end('An unknown error occurred when trying to render error 404\n' + err.stack)
|
250 | } else {
|
251 | return res.status(404).end(resolve(rendered, {}))
|
252 | }
|
253 | })
|
254 | }
|
255 |
|
256 | _runFilterPreFetchStage (filterQueue, renderProps, renderedServerComponents, req, res, context) {
|
257 | if (filterQueue.length > 0) {
|
258 | let filterFnQueue = []
|
259 | filterQueue.reverse().forEach((filterFn) => {
|
260 | filterFnQueue.push((callback) => {
|
261 | let filterCallContext = {
|
262 | get: function () {
|
263 | return context
|
264 | },
|
265 | next: function (booleanResult) {
|
266 | if (typeof booleanResult === 'undefined' || booleanResult == null) {
|
267 | booleanResult = true
|
268 | }
|
269 | callback(null, booleanResult)
|
270 | },
|
271 | redirect: function (redirect) {
|
272 | callback({ type: 'REDIRECTION', redirect: redirect }, false)
|
273 | },
|
274 | notFound: function () {
|
275 | callback({ type: 'NOT_FOUND' }, false)
|
276 | },
|
277 | abortWithError: function () {
|
278 | callback({ type: 'SERVER_ERROR' }, false)
|
279 | }
|
280 | }
|
281 | filterFn(filterCallContext)
|
282 | })
|
283 | })
|
284 | async.series(filterFnQueue, (err, results) => {
|
285 | if (err) {
|
286 | if (err.type === 'REDIRECTION') {
|
287 | return res.redirect(err.redirect)
|
288 | } else if (err.type === 'NOT_FOUND') {
|
289 | this._renderer.render('404.html', (err, rendered) => {
|
290 | if (err) {
|
291 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 404.', err)
|
292 | return res.status(500).end('An unknown error occurred when trying to render error 404\n' + err.stack)
|
293 | } else {
|
294 | return res.status(404).end(resolve(rendered, {}))
|
295 | }
|
296 | })
|
297 | } else if (err.type === 'SERVER_ERROR') {
|
298 | this._renderer.render('500.html', (err, rendered) => {
|
299 | if (err) {
|
300 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 500.', err)
|
301 | return res.status(500).end('An unknown error occurred when trying to render error 500\n' + err.stack)
|
302 | } else {
|
303 | return res.status(500).end(resolve(rendered, {}))
|
304 | }
|
305 | })
|
306 | } else {
|
307 | this._renderer.render('500.html', (err, rendered) => {
|
308 | if (err) {
|
309 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 500.', err)
|
310 | return res.status(500).end('An unknown error occurred when trying to render error 500\n' + err.stack)
|
311 | } else {
|
312 | return res.status(500).end(resolve(rendered, {}))
|
313 | }
|
314 | })
|
315 | }
|
316 | } else {
|
317 | this._runFetchStage(
|
318 | renderProps,
|
319 | renderedServerComponents,
|
320 | req,
|
321 | res,
|
322 | context)
|
323 | }
|
324 | })
|
325 | } else {
|
326 | this._runFetchStage(
|
327 | renderProps,
|
328 | renderedServerComponents,
|
329 | req,
|
330 | res,
|
331 | context)
|
332 | }
|
333 | }
|
334 |
|
335 | _runFetchStage (renderProps, renderedServerComponents, req, res, context) {
|
336 | let fetchDataQueue = []
|
337 | let dataContextObject = {}
|
338 |
|
339 | renderedServerComponents.forEach((rsc) => {
|
340 | if (rsc.__fetchData != null && typeof rsc.__fetchData !== 'undefined') {
|
341 | let fnFetchCall = (callback) => {
|
342 | setTimeout(() => {
|
343 | rsc.__fetchData(dataContextObject, context, callback)
|
344 | }, 1)
|
345 | }
|
346 | fetchDataQueue.push(fnFetchCall)
|
347 | }
|
348 | })
|
349 |
|
350 | let reducers = this._reducers
|
351 |
|
352 | async.series(fetchDataQueue, (err, results) => {
|
353 | if (err) {
|
354 | return this._handleError500('[FETCH_DATA_ERROR] An unknown error occurred when trying to fetch data.', err, res)
|
355 | } else {
|
356 | let fetchedDataContext = {}
|
357 | results.forEach((result) => {
|
358 | fetchedDataContext = {
|
359 | ...fetchedDataContext,
|
360 | ...result
|
361 | }
|
362 | })
|
363 |
|
364 | let allReducers = {
|
365 | ...reducers,
|
366 | view: serverFetchReducer
|
367 | }
|
368 | const store = createStoreWithMiddleware(combineReducers(allReducers))
|
369 |
|
370 | store.dispatch({
|
371 | type: '__SET_VIEW_STATE__',
|
372 | data: fetchedDataContext
|
373 | })
|
374 |
|
375 | const InitialComponent = (
|
376 | <Provider store={store}>
|
377 | <RouterContext {...renderProps} />
|
378 | </Provider>
|
379 | )
|
380 |
|
381 | let postFetchFilterQueue = []
|
382 |
|
383 | renderedServerComponents.forEach((rsc) => {
|
384 | if (rsc.__postFetchFilter != null && typeof rsc.__postFetchFilter !== 'undefined') {
|
385 | postFetchFilterQueue = postFetchFilterQueue.concat(rsc.__postFetchFilter)
|
386 | }
|
387 | })
|
388 | this._runFilterPostFetchStage(
|
389 | postFetchFilterQueue,
|
390 | store,
|
391 | InitialComponent,
|
392 | renderedServerComponents,
|
393 | req,
|
394 | res,
|
395 | context)
|
396 | }
|
397 | })
|
398 | }
|
399 |
|
400 | _runFilterPostFetchStage (filterQueue, store, InitialComponent, renderedServerComponents, req, res, context) {
|
401 | if (filterQueue.length > 0) {
|
402 | let filterFnQueue = []
|
403 | filterQueue.reverse().forEach((filterFn) => {
|
404 | filterFnQueue.push((callback) => {
|
405 | let filterCallContext = {
|
406 | store: function () {
|
407 | return store.getState().view
|
408 | },
|
409 | get: function () {
|
410 | return context
|
411 | },
|
412 | next: function (booleanResult) {
|
413 | if (typeof booleanResult === 'undefined' || booleanResult == null) {
|
414 | booleanResult = true
|
415 | }
|
416 | callback(null, booleanResult)
|
417 | },
|
418 | redirect: function (redirect) {
|
419 | callback({ type: 'REDIRECTION', redirect: redirect }, false)
|
420 | },
|
421 | notFound: function () {
|
422 | callback({ type: 'NOT_FOUND' }, false)
|
423 | }
|
424 | }
|
425 | filterFn(filterCallContext)
|
426 | })
|
427 | })
|
428 | async.series(filterFnQueue, (err, results) => {
|
429 | if (err) {
|
430 | if (err.type === 'REDIRECTION') {
|
431 | return res.redirect(err.redirect)
|
432 | } else if (err.type === 'NOT_FOUND') {
|
433 | this._renderer.render('404.html', (err, rendered) => {
|
434 | if (err) {
|
435 | logger.error('[ERROR_RENDER_FATAL] An unknown error occurred when trying to render error 404.', err)
|
436 | return res.status(500).end('An unknown error occurred when trying to render error 404\n' + err.stack)
|
437 | } else {
|
438 | return res.status(404).end(resolve(rendered, {}))
|
439 | }
|
440 | })
|
441 | } else {
|
442 |
|
443 | }
|
444 | } else {
|
445 | this._runRenderStage(
|
446 | store,
|
447 | InitialComponent,
|
448 | renderedServerComponents,
|
449 | req,
|
450 | res,
|
451 | context)
|
452 | }
|
453 | })
|
454 | } else {
|
455 | this._runRenderStage(
|
456 | store,
|
457 | InitialComponent,
|
458 | renderedServerComponents,
|
459 | req,
|
460 | res,
|
461 | context)
|
462 | }
|
463 | }
|
464 |
|
465 | _createCookieSerializationOption (header) {
|
466 | let option = {
|
467 | path: header.path,
|
468 | domain: header.domain,
|
469 | version: header.version
|
470 | }
|
471 |
|
472 | if (header.maxAge > 0) {
|
473 | option.maxAge = header.maxAge
|
474 | }
|
475 |
|
476 | return option
|
477 | }
|
478 |
|
479 | _runSendResponseStage (res, PAGE_HTML, context) {
|
480 | let responseCookies = context.response.cookies
|
481 | let responseHeaders = context.response.headers.cookies
|
482 |
|
483 | Object.keys(responseCookies).forEach((krc) => {
|
484 | let __cookie = responseCookies[krc]
|
485 | let __cookieHeader = {}
|
486 | if (responseHeaders != null && typeof responseHeaders !== 'undefined') {
|
487 | __cookieHeader = responseHeaders[krc]
|
488 | }
|
489 | if (__cookie != null && typeof __cookie !== 'undefined') {
|
490 | let serializedCookie = cookie.serialize(krc, __cookie, this._createCookieSerializationOption(__cookieHeader))
|
491 | res.append('Set-Cookie', serializedCookie)
|
492 | }
|
493 | })
|
494 | res.end(PAGE_HTML)
|
495 | }
|
496 |
|
497 | _runRenderStage (store, InitialComponent, renderedServerComponents, req, res, context) {
|
498 | let finalRenderPage = 'main.html'
|
499 | renderedServerComponents.forEach((rsc) => {
|
500 | if (rsc.__renderPage != null && typeof rsc.__renderPage !== 'undefined') {
|
501 | finalRenderPage = rsc.__renderPage
|
502 | }
|
503 | })
|
504 | let renderBindFnQueue = []
|
505 | renderedServerComponents.forEach((rsc) => {
|
506 | if (rsc.__renderBindFn != null && typeof rsc.__renderBindFn !== 'undefined') {
|
507 | let bindFnCall = (callback) => {
|
508 | setTimeout(() => {
|
509 | rsc.__renderBindFn(store.getState(), context, callback)
|
510 | }, 1)
|
511 | }
|
512 | renderBindFnQueue.push(bindFnCall)
|
513 | }
|
514 | })
|
515 |
|
516 | if (renderBindFnQueue.length > 0) {
|
517 | async.series(renderBindFnQueue, (err, results) => {
|
518 | if (err) {
|
519 | return this._handleError500('[RENDER_BIND_PHASE] FATAL_ERROR in render data binding phase.', err, res)
|
520 | } else {
|
521 | this._renderer.render(finalRenderPage, (err, rendered) => {
|
522 | if (err) {
|
523 | return this._handleError500('[RENDER_VIEW_PHASE] FATAL_ERROR in render view template phase.', err, res)
|
524 | } else {
|
525 | let bindData = {}
|
526 | if (results != null && typeof results !== 'undefined') {
|
527 | results.forEach((r) => {
|
528 | bindData = {
|
529 | ...bindData,
|
530 | ...r
|
531 | }
|
532 | })
|
533 | }
|
534 | try {
|
535 | const HTML = renderToStaticMarkup(InitialComponent)
|
536 | const PAGE_HTML = resolve(rendered, {
|
537 | ...bindData,
|
538 | HTML: HTML,
|
539 | DATA: store.getState()
|
540 | }, { partial: true })
|
541 | return this._runSendResponseStage(res, PAGE_HTML, context)
|
542 | } catch (err) {
|
543 | return this._handleError500('[RENDER_STATIC_MARKUP_PHASE] FATAL_ERROR in render staticMarkup phase.', err, res)
|
544 | }
|
545 | }
|
546 | })
|
547 | }
|
548 | })
|
549 | } else {
|
550 | this._renderer.render(finalRenderPage, (err, rendered) => {
|
551 | if (err) {
|
552 | return this._handleError500('[RENDER_VIEW_PHASE] FATAL_ERROR in render view template phase.', err, res)
|
553 | } else {
|
554 | let bindData = {}
|
555 | try {
|
556 | const HTML = renderToStaticMarkup(InitialComponent)
|
557 | const PAGE_HTML = resolve(rendered, {
|
558 | ...bindData,
|
559 | HTML: HTML,
|
560 | DATA: store.getState()
|
561 | }, { partial: true })
|
562 | return this._runSendResponseStage(res, PAGE_HTML, context)
|
563 | } catch (err) {
|
564 | return this._handleError500('[RENDER_STATIC_MARKUP_PHASE] FATAL_ERROR in render staticMarkup phase.', err, res)
|
565 | }
|
566 | }
|
567 | })
|
568 | }
|
569 | }
|
570 |
|
571 | _importRequestCookie (context, req) {
|
572 | let cookies = req.cookies
|
573 | Object.keys(cookies).forEach((ck) => {
|
574 | context.request.cookies[ck] = req.cookies[ck]
|
575 | })
|
576 | }
|
577 |
|
578 | _handleHealthCheck(res) {
|
579 | return res.status(200).send('ok')
|
580 | }
|
581 |
|
582 | _setupRoutingHandler () {
|
583 | const routes = this._routes
|
584 | this._app.use((req, res) => {
|
585 | const location = req.url
|
586 | const pathname = parseUrl(req).pathname
|
587 | const locale = req.locale
|
588 |
|
589 | if (req.url == '/healthz') {
|
590 | return this._handleHealthCheck(res)
|
591 | }
|
592 |
|
593 | logger.info('[HANDLING_ROUTE] path: ' + req.url + ', locale: ' + locale)
|
594 | match({ routes, location }, (err, redirectLocation, renderProps) => {
|
595 | if (err) {
|
596 | return this._handleError500('[MATCH_ROUTE_FATAL_ERROR] An unknown error occurred when trying to match routes.', err, res)
|
597 | }
|
598 |
|
599 | if (!renderProps) {
|
600 | logger.info('[ROUTE_NOT_FOUND] path: ' + req.url + ', locale: ' + locale)
|
601 | return this._handleNotFound404(res)
|
602 | }
|
603 |
|
604 | let query = {}
|
605 | let queryStringMatch = QS_REGEX.exec(renderProps.location.search)
|
606 | let queryString = ''
|
607 | if (queryStringMatch != null && typeof queryStringMatch !== 'undefined') {
|
608 | queryString = queryStringMatch[1]
|
609 | }
|
610 | if (queryString != null && typeof queryString !== 'undefined') {
|
611 | query = querystring.parse(queryString)
|
612 | }
|
613 |
|
614 | let simplifiedRoutes = {
|
615 | name: renderProps.routes[renderProps.routes.length - 1].name,
|
616 | path: renderProps.routes[renderProps.routes.length - 1].path
|
617 | }
|
618 |
|
619 | let context = {
|
620 | host: this._options.applicationHost,
|
621 | url: req.url,
|
622 | path: pathname,
|
623 | locale: locale,
|
624 | params: renderProps.params,
|
625 | query: query,
|
626 | routes: simplifiedRoutes,
|
627 | environment: this._options.environment,
|
628 | server: true,
|
629 | client: false,
|
630 | useragent: req.useragent,
|
631 | request: {
|
632 | cookies: {},
|
633 | headers: {}
|
634 | },
|
635 | response: {
|
636 | cookies: {},
|
637 | headers: {}
|
638 | }
|
639 | }
|
640 |
|
641 | this._importRequestCookie(context, req)
|
642 |
|
643 | let renderedServerComponents = renderProps.components
|
644 | let preFetchFilterQueue = []
|
645 |
|
646 | renderedServerComponents.forEach((rsc) => {
|
647 | if (rsc.__preFetchFilter != null && typeof rsc.__preFetchFilter !== 'undefined') {
|
648 | preFetchFilterQueue = preFetchFilterQueue.concat(rsc.__preFetchFilter)
|
649 | }
|
650 | })
|
651 |
|
652 | this._runFilterPreFetchStage(
|
653 | preFetchFilterQueue,
|
654 | renderProps,
|
655 | renderedServerComponents,
|
656 | req,
|
657 | res,
|
658 | context
|
659 | )
|
660 | })
|
661 | })
|
662 | }
|
663 |
|
664 | app () {
|
665 | return this._app
|
666 | }
|
667 |
|
668 | getErrorHandler () {
|
669 | return '/'
|
670 | }
|
671 |
|
672 | run () {
|
673 | this._setupRoutingHandler()
|
674 | this._app.listen(this._options.port, (err) => {
|
675 | if (err) logger.error(err)
|
676 | else {
|
677 | logger.info('[EXPRESS_UNIVERSAL_APPLICATION_SERVER] Server listening on port : ' + this._options.port)
|
678 | }
|
679 | })
|
680 |
|
681 | let pingApp = express()
|
682 | pingApp.use('/ping', (req, res) => {
|
683 | res.end('pong')
|
684 | })
|
685 | pingApp.listen(this._options.pingPort, (err) => {
|
686 | if (err) logger.error(err)
|
687 | else {
|
688 | logger.info('[EXPRESS_UNIVERSAL_APPLICATION_SERVER] Ping server listening on port : ' + this._options.pingPort)
|
689 | }
|
690 | })
|
691 | }
|
692 | }
|
693 |
|
694 | export default ExpressUniversalApplicationServer
|