1 | 'use strict'
|
2 |
|
3 | const logError = require('./logError')
|
4 | const interpolate = require('./interpolate')
|
5 | const {
|
6 | $configurationInterface,
|
7 | $configurationRequests,
|
8 | $dispatcherEnd,
|
9 | $mappingMethod,
|
10 | $requestPromise,
|
11 | $requestRedirectCount,
|
12 | $responseEnded
|
13 | } = require('./symbols')
|
14 |
|
15 | function 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 |
|
27 | function 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 })
|
38 | }
|
39 | this.resolve(redirectedInfos)
|
40 | }
|
41 |
|
42 | function 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 })
|
53 | }
|
54 | if (this.failed) {
|
55 |
|
56 | this.response.end()
|
57 | return redirected.call(this)
|
58 | }
|
59 | this.failed = true
|
60 | return dispatch.call(this, statusCode)
|
61 | }
|
62 |
|
63 | function 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 |
|
92 | function tryMatch (mapping, method, url) {
|
93 | if (mapping[$mappingMethod] && !mapping[$mappingMethod].includes(method)) {
|
94 | return null
|
95 | }
|
96 | return mapping.match.exec(url)
|
97 | }
|
98 |
|
99 | function 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 |
|
120 | module.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 | }
|