UNPKG

4.3 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 try {
35 this.eventEmitter.emit('redirected', redirectedInfos)
36 } catch (reason) {
37 logError({ ...this.emitParameters, reason }) // Too late to impact the request
38 }
39 this.resolve(redirectedInfos)
40}
41
42function error (reason) {
43 let statusCode
44 if (typeof reason === 'number') {
45 statusCode = reason
46 } else {
47 statusCode = 500
48 }
49 try {
50 this.eventEmitter.emit('error', { ...this.emitParameters, reason })
51 } catch (e) {
52 logError({ ...this.emitParameters, reason: e }) // Unhandled error
53 }
54 if (this.failed) {
55 // Error during error: finalize the response (whatever it means)
56 this.response.end()
57 return redirected.call(this)
58 }
59 this.failed = true
60 return dispatch.call(this, statusCode)
61}
62
63function redirecting ({ mapping, match, handler, type, redirect, url, index = 0 }) {
64 try {
65 this.eventEmitter.emit('redirecting', Object.assign(this.emitParameters, { type, redirect }))
66 return handler.redirect({
67 configuration: this.configuration[$configurationInterface],
68 mapping,
69 match,
70 redirect,
71 request: this.request,
72 response: hookEnd(this.response)
73 })
74 .then(result => {
75 if (undefined !== result) {
76 const redirectCount = ++this.request[$requestRedirectCount]
77 if (redirectCount > this.configuration['max-redirect']) {
78 return error.call(this, 508)
79 }
80 return dispatch.call(this, result)
81 }
82 if (this.response[$responseEnded]) {
83 return redirected.call(this)
84 }
85 return dispatch.call(this, url, index + 1)
86 }, error.bind(this))
87 } catch (e) {
88 return error.call(this, e)
89 }
90}
91
92function tryMatch (mapping, method, url) {
93 if (mapping[$mappingMethod] && !mapping[$mappingMethod].includes(method)) {
94 return null
95 }
96 return mapping.match.exec(url)
97}
98
99function dispatch (url, index = 0) {
100 if (typeof url === 'number') {
101 return redirecting.call(this, {
102 type: 'status',
103 handler: this.configuration.handlers.status,
104 redirect: url
105 })
106 }
107 const length = this.configuration.mappings.length
108 while (index < length) {
109 const mapping = this.configuration.mappings[index]
110 const match = tryMatch(mapping, this.request.method, url)
111 if (match) {
112 const { handler, redirect, type } = this.configuration.handler(mapping)
113 return redirecting.call(this, { mapping, match, handler, type, redirect: interpolate(match, redirect), url, index })
114 }
115 ++index
116 }
117 return error.call(this, 501)
118}
119
120module.exports = function (configuration, request, response) {
121 const emitParameters = { method: request.method, url: request.url, start: new Date() }
122 let promiseResolver
123 const requestPromise = new Promise(resolve => { promiseResolver = resolve })
124 const context = { eventEmitter: this, emitParameters, configuration, request, response, resolve: promiseResolver }
125 request[$requestPromise] = requestPromise
126 request[$requestRedirectCount] = 0
127 try {
128 this.emit('incoming', emitParameters)
129 } catch (reason) {
130 error.call(context, reason)
131 return requestPromise
132 }
133 const configurationRequests = configuration[$configurationRequests]
134 return configurationRequests.hold
135 .then(() => {
136 configurationRequests.promises.push(requestPromise)
137 dispatch.call(context, request.url)
138 return requestPromise
139 })
140 .then(() => {
141 configurationRequests.promises = configurationRequests.promises
142 .filter(promise => promise !== requestPromise)
143 })
144}