UNPKG

4.08 kBJavaScriptView Raw
1'use strict'
2
3const logError = require('./logError')
4const interpolate = require('./interpolate')
5const {
6 $configurationInterface,
7 $configurationRequests,
8 $dispatcherEnd,
9 $mappingMethod,
10 $requestPromise,
11 $requestRedirectCount,
12 $responseEnded
13} = require('./symbols')
14
15function hookEnd (response) {
16 if (!response.end[$dispatcherEnd]) {
17 const end = response.end
18 response.end = function () {
19 this[$responseEnded] = true
20 return end.apply(this, arguments)
21 }
22 response.end[$dispatcherEnd] = true
23 }
24 return response
25}
26
27function redirected () {
28 const end = new Date()
29 const redirectedInfos = Object.assign(this.emitParameters, {
30 end,
31 timeSpent: end - this.emitParameters.start,
32 statusCode: this.response.statusCode
33 })
34 this.eventEmitter.emit('redirected', redirectedInfos)
35 this.resolve(redirectedInfos)
36}
37
38function error (reason) {
39 let statusCode
40 if (typeof reason === 'number') {
41 statusCode = reason
42 } else {
43 statusCode = 500
44 }
45 const errorParameters = { ...this.emitParameters, reason }
46 try {
47 this.eventEmitter.emit('error', errorParameters)
48 } catch (e) {
49 // Unhandled error
50 logError(errorParameters)
51 }
52 if (this.failed) {
53 // Error during error: finalize the response (whatever it means)
54 this.response.end()
55 return redirected.call(this)
56 }
57 this.failed = true
58 return dispatch.call(this, statusCode)
59}
60
61function redirecting ({ mapping, match, handler, type, redirect, url, index = 0 }) {
62 this.eventEmitter.emit('redirecting', Object.assign(this.emitParameters, { type, redirect }))
63 try {
64 return handler.redirect({
65 configuration: this.configuration[$configurationInterface],
66 mapping,
67 match,
68 redirect,
69 request: this.request,
70 response: hookEnd(this.response)
71 })
72 .then(result => {
73 if (undefined !== result) {
74 const redirectCount = ++this.request[$requestRedirectCount]
75 if (redirectCount > this.configuration['max-redirect']) {
76 return error.call(this, 508)
77 }
78 return dispatch.call(this, result)
79 }
80 if (this.response[$responseEnded]) {
81 return redirected.call(this)
82 }
83 return dispatch.call(this, url, index + 1)
84 }, error.bind(this))
85 } catch (e) {
86 return error.call(this, e)
87 }
88}
89
90function tryMatch (mapping, method, url) {
91 if (mapping[$mappingMethod] && !mapping[$mappingMethod].includes(method)) {
92 return null
93 }
94 return mapping.match.exec(url)
95}
96
97function dispatch (url, index = 0) {
98 if (typeof url === 'number') {
99 return redirecting.call(this, {
100 type: 'status',
101 handler: this.configuration.handlers.status,
102 redirect: url
103 })
104 }
105 const length = this.configuration.mappings.length
106 while (index < length) {
107 const mapping = this.configuration.mappings[index]
108 const match = tryMatch(mapping, this.request.method, url)
109 if (match) {
110 const { handler, redirect, type } = this.configuration.handler(mapping)
111 return redirecting.call(this, { mapping, match, handler, type, redirect: interpolate(match, redirect), url, index })
112 }
113 ++index
114 }
115 return error.call(this, 501)
116}
117
118module.exports = function (configuration, request, response) {
119 const emitParameters = { method: request.method, url: request.url, start: new Date() }
120 let promiseResolver
121 const requestPromise = new Promise(resolve => { promiseResolver = resolve })
122 this.emit('incoming', emitParameters)
123 request[$requestPromise] = requestPromise
124 request[$requestRedirectCount] = 0
125 const configurationRequests = configuration[$configurationRequests]
126 return configurationRequests.hold
127 .then(() => {
128 configurationRequests.promises.push(requestPromise)
129 dispatch.call({ eventEmitter: this, emitParameters, configuration, request, response, resolve: promiseResolver }, request.url)
130 return requestPromise
131 })
132 .then(() => {
133 configurationRequests.promises = configurationRequests.promises
134 .filter(promise => promise !== requestPromise)
135 })
136}