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 | this.eventEmitter.emit('redirected', redirectedInfos)
|
35 | this.resolve(redirectedInfos)
|
36 | }
|
37 |
|
38 | function 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 |
|
50 | logError(errorParameters)
|
51 | }
|
52 | if (this.failed) {
|
53 |
|
54 | this.response.end()
|
55 | return redirected.call(this)
|
56 | }
|
57 | this.failed = true
|
58 | return dispatch.call(this, statusCode)
|
59 | }
|
60 |
|
61 | function 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 |
|
90 | function tryMatch (mapping, method, url) {
|
91 | if (mapping[$mappingMethod] && !mapping[$mappingMethod].includes(method)) {
|
92 | return null
|
93 | }
|
94 | return mapping.match.exec(url)
|
95 | }
|
96 |
|
97 | function 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 |
|
118 | module.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 | }
|