@srfnstack/spliffy 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/decorator.mjs CHANGED
@@ -1,209 +1,209 @@
1
- import cookie from 'cookie'
2
- import http from 'http'
3
- import { parseQuery } from './url.mjs'
4
- import log from './log.mjs'
5
- import { v4 as uuid } from 'uuid'
6
- import stream from 'stream'
7
- import httpStatusCodes, { defaultStatusMessages } from './httpStatusCodes.mjs'
8
-
9
- const { Writable } = stream
10
-
11
- const addressArrayBufferToString = addrBuf => String.fromCharCode.apply(null, new Int8Array(addrBuf))
12
- const excludedMessageProps = {
13
- setTimeout: true,
14
- _read: true,
15
- destroy: true,
16
- _addHeaderLines: true,
17
- _addHeaderLine: true,
18
- _dump: true,
19
- __proto__: true
20
- }
21
-
22
- const normalizeHeader = header => header.toLowerCase()
23
-
24
- const reqProtoProps = () => Object.keys(http.IncomingMessage.prototype).filter(p => !excludedMessageProps[p])
25
-
26
- export const setCookie = (res) => function () {
27
- return res.setHeader('Set-Cookie', [...(res.getHeader('Set-Cookie') || []), cookie.serialize(...arguments)])
28
- }
29
-
30
- export function decorateRequest (uwsReq, pathParameters, res, {
31
- decodeQueryParameters,
32
- decodePathParameters,
33
- parseCookie,
34
- extendIncomingMessage
35
- } = {}) {
36
- // uwsReq can't be used in async functions because it gets de-allocated when the handler function returns
37
- const req = {}
38
- if (extendIncomingMessage) {
39
- // frameworks like passport like to modify the message prototype
40
- // Setting the prototype of req is not desirable because the entire api of IncomingMessage is not supported
41
- for (const p of reqProtoProps()) {
42
- if (!req[p]) req[p] = http.IncomingMessage.prototype[p]
43
- }
44
- }
45
- const query = uwsReq.getQuery()
46
- req.path = uwsReq.getUrl()
47
- req.url = `${req.path}${query ? '?' + query : ''}`
48
- const paramToIndex = pathParameters.reduce((acc, cur, i) => {
49
- acc[cur] = i
50
- return acc
51
- }, {})
52
- req.spliffyUrl = {
53
- path: req.path,
54
- query: (query && parseQuery(query, decodeQueryParameters)) || {},
55
- param: name => uwsReq.getParameter(paramToIndex[name])
56
- }
57
- req.query = req.spliffyUrl.query
58
- req.headers = {}
59
- uwsReq.forEach((header, value) => { req.headers[header] = value })
60
- req.method = uwsReq.getMethod().toUpperCase()
61
- req.remoteAddress = addressArrayBufferToString(res.getRemoteAddressAsText())
62
- req.proxiedRemoteAddress = addressArrayBufferToString(res.getProxiedRemoteAddressAsText())
63
- req.get = header => req.headers[header]
64
- if (parseCookie) {
65
- req.cookies = (req.headers.cookie && cookie.parse(req.headers.cookie)) || {}
66
- }
67
- return req
68
- }
69
-
70
- function toArrayBuffer (buffer) {
71
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
72
- }
73
-
74
- export function decorateResponse (res, req, finalizeResponse, errorTransformer, endError, { acceptsDefault }) {
75
- res.onAborted(() => {
76
- res.ended = true
77
- res.writableEnded = true
78
- res.finalized = true
79
- log.error(`Request to ${req.url} was aborted`)
80
- })
81
- res.acceptsDefault = acceptsDefault
82
- res.headers = {}
83
- res.headersSent = false
84
- res.setHeader = (header, value) => {
85
- res.headers[normalizeHeader(header)] = value
86
- }
87
- res.removeHeader = header => {
88
- delete res.headers[normalizeHeader(header)]
89
- }
90
- res.flushHeaders = () => {
91
- if (res.headersSent) return
92
- if (!res.statusCode) res.statusCode = httpStatusCodes.OK
93
- if (!res.statusMessage) res.statusMessage = defaultStatusMessages[res.statusCode]
94
- res.headersSent = true
95
- res.writeStatus(`${res.statusCode} ${res.statusMessage}`)
96
- if (typeof res.onFlushHeaders === 'function') {
97
- res.onFlushHeaders(res)
98
- }
99
- for (const header of Object.keys(res.headers)) {
100
- if (Array.isArray(res.headers[header])) {
101
- for (const multiple of res.headers[header]) {
102
- res.writeHeader(header, multiple.toString())
103
- }
104
- } else {
105
- res.writeHeader(header, res.headers[header].toString())
106
- }
107
- }
108
- }
109
- res.writeHead = (status, headers) => {
110
- res.statusCode = status
111
- res.assignHeaders(headers)
112
- }
113
- res.assignHeaders = headers => {
114
- for (const header of Object.keys(headers)) {
115
- res.headers[normalizeHeader(header)] = headers[header]
116
- }
117
- }
118
- res.getHeader = header => {
119
- return res.headers[normalizeHeader(header)]
120
- }
121
- res.status = (code) => {
122
- res.statusCode = code
123
- return this
124
- }
125
-
126
- res.uwsWrite = res.write
127
- res.write = (chunk, encoding, cb) => {
128
- try {
129
- res.streaming = true
130
- res.flushHeaders()
131
- let data
132
- if (chunk instanceof Buffer) {
133
- data = toArrayBuffer(chunk)
134
- } else if (typeof chunk === 'string') {
135
- data = toArrayBuffer(Buffer.from(chunk, encoding || 'utf8'))
136
- } else {
137
- data = toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8'))
138
- }
139
- const result = res.uwsWrite(data)
140
- if (typeof cb === 'function') {
141
- cb()
142
- }
143
- return result
144
- } catch (e) {
145
- if (typeof cb === 'function') {
146
- cb(e)
147
- } else {
148
- throw e
149
- }
150
- }
151
- }
152
- let outStream
153
- res.getWritable = () => {
154
- if (!outStream) {
155
- res.streaming = true
156
- outStream = new Writable({
157
- write: res.write
158
- })
159
- .on('finish', res.end)
160
- .on('end', res.end)
161
- .on('error', e => {
162
- try {
163
- outStream.destroy()
164
- } finally {
165
- endError(res, e, uuid(), errorTransformer)
166
- }
167
- })
168
- }
169
- return outStream
170
- }
171
-
172
- const uwsEnd = res.end
173
- res.ended = false
174
- res.end = body => {
175
- if (res.ended) {
176
- return
177
- }
178
- // provide writableEnded like node does, with slightly different behavior
179
- if (!res.writableEnded) {
180
- res.flushHeaders()
181
- uwsEnd.call(res, body)
182
- res.writableEnded = true
183
- res.ended = true
184
- }
185
- if (typeof res.onEnd === 'function') {
186
- res.onEnd()
187
- }
188
- }
189
-
190
- res.redirect = function (code, location) {
191
- if (arguments.length === 1) {
192
- location = code
193
- code = httpStatusCodes.MOVED_PERMANENTLY
194
- }
195
- return finalizeResponse(req, res, {
196
- statusCode: code,
197
- headers: {
198
- location: location
199
- }
200
- })
201
- }
202
- res.send = (body) => {
203
- finalizeResponse(req, res, body)
204
- }
205
- res.json = res.send
206
- res.setCookie = setCookie(res)
207
- res.cookie = res.setCookie
208
- return res
209
- }
1
+ import cookie from 'cookie'
2
+ import http from 'http'
3
+ import { parseQuery } from './url.mjs'
4
+ import log from './log.mjs'
5
+ import { v4 as uuid } from 'uuid'
6
+ import stream from 'stream'
7
+ import httpStatusCodes, { defaultStatusMessages } from './httpStatusCodes.mjs'
8
+
9
+ const { Writable } = stream
10
+
11
+ const addressArrayBufferToString = addrBuf => String.fromCharCode.apply(null, new Int8Array(addrBuf))
12
+ const excludedMessageProps = {
13
+ setTimeout: true,
14
+ _read: true,
15
+ destroy: true,
16
+ _addHeaderLines: true,
17
+ _addHeaderLine: true,
18
+ _dump: true,
19
+ __proto__: true
20
+ }
21
+
22
+ const normalizeHeader = header => header.toLowerCase()
23
+
24
+ const reqProtoProps = () => Object.keys(http.IncomingMessage.prototype).filter(p => !excludedMessageProps[p])
25
+
26
+ export const setCookie = (res) => function () {
27
+ return res.setHeader('Set-Cookie', [...(res.getHeader('Set-Cookie') || []), cookie.serialize(...arguments)])
28
+ }
29
+
30
+ export function decorateRequest (uwsReq, pathParameters, res, {
31
+ decodeQueryParameters,
32
+ decodePathParameters,
33
+ parseCookie,
34
+ extendIncomingMessage
35
+ } = {}) {
36
+ // uwsReq can't be used in async functions because it gets de-allocated when the handler function returns
37
+ const req = {}
38
+ if (extendIncomingMessage) {
39
+ // frameworks like passport like to modify the message prototype
40
+ // Setting the prototype of req is not desirable because the entire api of IncomingMessage is not supported
41
+ for (const p of reqProtoProps()) {
42
+ if (!req[p]) req[p] = http.IncomingMessage.prototype[p]
43
+ }
44
+ }
45
+ const query = uwsReq.getQuery()
46
+ req.path = uwsReq.getUrl()
47
+ req.url = `${req.path}${query ? '?' + query : ''}`
48
+ const paramToIndex = pathParameters.reduce((acc, cur, i) => {
49
+ acc[cur] = i
50
+ return acc
51
+ }, {})
52
+ req.spliffyUrl = {
53
+ path: req.path,
54
+ query: (query && parseQuery(query, decodeQueryParameters)) || {},
55
+ param: name => uwsReq.getParameter(paramToIndex[name])
56
+ }
57
+ req.query = req.spliffyUrl.query
58
+ req.headers = {}
59
+ uwsReq.forEach((header, value) => { req.headers[header] = value })
60
+ req.method = uwsReq.getMethod().toUpperCase()
61
+ req.remoteAddress = addressArrayBufferToString(res.getRemoteAddressAsText())
62
+ req.proxiedRemoteAddress = addressArrayBufferToString(res.getProxiedRemoteAddressAsText())
63
+ req.get = header => req.headers[header]
64
+ if (parseCookie) {
65
+ req.cookies = (req.headers.cookie && cookie.parse(req.headers.cookie)) || {}
66
+ }
67
+ return req
68
+ }
69
+
70
+ function toArrayBuffer (buffer) {
71
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
72
+ }
73
+
74
+ export function decorateResponse (res, req, finalizeResponse, errorTransformer, endError, { acceptsDefault }) {
75
+ res.onAborted(() => {
76
+ res.ended = true
77
+ res.writableEnded = true
78
+ res.finalized = true
79
+ log.error(`Request to ${req.url} was aborted`)
80
+ })
81
+ res.acceptsDefault = acceptsDefault
82
+ res.headers = {}
83
+ res.headersSent = false
84
+ res.setHeader = (header, value) => {
85
+ res.headers[normalizeHeader(header)] = value
86
+ }
87
+ res.removeHeader = header => {
88
+ delete res.headers[normalizeHeader(header)]
89
+ }
90
+ res.flushHeaders = () => {
91
+ if (res.headersSent) return
92
+ if (!res.statusCode) res.statusCode = httpStatusCodes.OK
93
+ if (!res.statusMessage) res.statusMessage = defaultStatusMessages[res.statusCode]
94
+ res.headersSent = true
95
+ res.writeStatus(`${res.statusCode} ${res.statusMessage}`)
96
+ if (typeof res.onFlushHeaders === 'function') {
97
+ res.onFlushHeaders(res)
98
+ }
99
+ for (const header of Object.keys(res.headers)) {
100
+ if (Array.isArray(res.headers[header])) {
101
+ for (const multiple of res.headers[header]) {
102
+ res.writeHeader(header, multiple.toString())
103
+ }
104
+ } else {
105
+ res.writeHeader(header, res.headers[header].toString())
106
+ }
107
+ }
108
+ }
109
+ res.writeHead = (status, headers) => {
110
+ res.statusCode = status
111
+ res.assignHeaders(headers)
112
+ }
113
+ res.assignHeaders = headers => {
114
+ for (const header of Object.keys(headers)) {
115
+ res.headers[normalizeHeader(header)] = headers[header]
116
+ }
117
+ }
118
+ res.getHeader = header => {
119
+ return res.headers[normalizeHeader(header)]
120
+ }
121
+ res.status = (code) => {
122
+ res.statusCode = code
123
+ return this
124
+ }
125
+
126
+ res.uwsWrite = res.write
127
+ res.write = (chunk, encoding, cb) => {
128
+ try {
129
+ res.streaming = true
130
+ res.flushHeaders()
131
+ let data
132
+ if (chunk instanceof Buffer) {
133
+ data = toArrayBuffer(chunk)
134
+ } else if (typeof chunk === 'string') {
135
+ data = toArrayBuffer(Buffer.from(chunk, encoding || 'utf8'))
136
+ } else {
137
+ data = toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8'))
138
+ }
139
+ const result = res.uwsWrite(data)
140
+ if (typeof cb === 'function') {
141
+ cb()
142
+ }
143
+ return result
144
+ } catch (e) {
145
+ if (typeof cb === 'function') {
146
+ cb(e)
147
+ } else {
148
+ throw e
149
+ }
150
+ }
151
+ }
152
+ let outStream
153
+ res.getWritable = () => {
154
+ if (!outStream) {
155
+ res.streaming = true
156
+ outStream = new Writable({
157
+ write: res.write
158
+ })
159
+ .on('finish', res.end)
160
+ .on('end', res.end)
161
+ .on('error', e => {
162
+ try {
163
+ outStream.destroy()
164
+ } finally {
165
+ endError(res, e, uuid(), errorTransformer)
166
+ }
167
+ })
168
+ }
169
+ return outStream
170
+ }
171
+
172
+ const uwsEnd = res.end
173
+ res.ended = false
174
+ res.end = body => {
175
+ if (res.ended) {
176
+ return
177
+ }
178
+ // provide writableEnded like node does, with slightly different behavior
179
+ if (!res.writableEnded) {
180
+ res.flushHeaders()
181
+ uwsEnd.call(res, body)
182
+ res.writableEnded = true
183
+ res.ended = true
184
+ }
185
+ if (typeof res.onEnd === 'function') {
186
+ res.onEnd()
187
+ }
188
+ }
189
+
190
+ res.redirect = function (code, location) {
191
+ if (arguments.length === 1) {
192
+ location = code
193
+ code = httpStatusCodes.MOVED_PERMANENTLY
194
+ }
195
+ return finalizeResponse(req, res, {
196
+ statusCode: code,
197
+ headers: {
198
+ location: location
199
+ }
200
+ })
201
+ }
202
+ res.send = (body) => {
203
+ finalizeResponse(req, res, body)
204
+ }
205
+ res.json = res.send
206
+ res.setCookie = setCookie(res)
207
+ res.cookie = res.setCookie
208
+ return res
209
+ }