@srfnstack/spliffy 1.1.4 → 1.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srfnstack/spliffy",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "author": "snowbldr",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/narcolepticsnowman/spliffy",
@@ -33,7 +33,7 @@
33
33
  "cookie": "^0.4.1",
34
34
  "etag": "^1.8.1",
35
35
  "uuid": "^8.3.2",
36
- "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.34.0"
36
+ "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.40.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "helmet": "^4.6.0",
package/src/decorator.mjs CHANGED
@@ -45,20 +45,15 @@ export function decorateRequest (uwsReq, pathParameters, res, {
45
45
  const query = uwsReq.getQuery()
46
46
  req.path = uwsReq.getUrl()
47
47
  req.url = `${req.path}${query ? '?' + query : ''}`
48
+ const paramToIndex = pathParameters.reduce((acc, cur, i) => {
49
+ acc[cur] = i
50
+ return acc
51
+ }, {})
48
52
  req.spliffyUrl = {
49
53
  path: req.path,
50
54
  query: (query && parseQuery(query, decodeQueryParameters)) || {},
51
- pathParameters: {}
55
+ param: name => uwsReq.getParameter(paramToIndex[name])
52
56
  }
53
- if (pathParameters && pathParameters.length > 0) {
54
- for (const i in pathParameters) {
55
- req.spliffyUrl.pathParameters[pathParameters[i]] =
56
- decodePathParameters
57
- ? decodeURIComponent(uwsReq.getParameter(i))
58
- : uwsReq.getParameter(i)
59
- }
60
- }
61
- req.params = req.spliffyUrl.pathParameters
62
57
  req.query = req.spliffyUrl.query
63
58
  req.headers = {}
64
59
  uwsReq.forEach((header, value) => { req.headers[header] = value })
@@ -97,19 +92,21 @@ export function decorateResponse (res, req, finalizeResponse, errorTransformer,
97
92
  if (!res.statusCode) res.statusCode = httpStatusCodes.OK
98
93
  if (!res.statusMessage) res.statusMessage = defaultStatusMessages[res.statusCode]
99
94
  res.headersSent = true
100
- res.writeStatus(`${res.statusCode} ${res.statusMessage}`)
101
- if (typeof res.onFlushHeaders === 'function') {
102
- res.onFlushHeaders(res)
103
- }
104
- for (const header of Object.keys(res.headers)) {
105
- if (Array.isArray(res.headers[header])) {
106
- for (const multiple of res.headers[header]) {
107
- res.writeHeader(header, multiple.toString())
95
+ res.cork(() => {
96
+ res.writeStatus(`${res.statusCode} ${res.statusMessage}`)
97
+ if (typeof res.onFlushHeaders === 'function') {
98
+ res.onFlushHeaders(res)
99
+ }
100
+ for (const header of Object.keys(res.headers)) {
101
+ if (Array.isArray(res.headers[header])) {
102
+ for (const multiple of res.headers[header]) {
103
+ res.writeHeader(header, multiple.toString())
104
+ }
105
+ } else {
106
+ res.writeHeader(header, res.headers[header].toString())
108
107
  }
109
- } else {
110
- res.writeHeader(header, res.headers[header].toString())
111
108
  }
112
- }
109
+ })
113
110
  }
114
111
  res.writeHead = (status, headers) => {
115
112
  res.statusCode = status
@@ -128,22 +125,26 @@ export function decorateResponse (res, req, finalizeResponse, errorTransformer,
128
125
  return this
129
126
  }
130
127
 
131
- res.writeArrayBuffer = res.write
128
+ res.uwsWrite = res.write
132
129
  res.write = (chunk, encoding, cb) => {
133
130
  try {
134
- res.streaming = true
135
- res.flushHeaders()
136
131
  let result
137
- if (chunk instanceof Buffer) {
138
- result = res.writeArrayBuffer(toArrayBuffer(chunk))
139
- } else if (typeof chunk === 'string') {
140
- result = res.writeArrayBuffer(toArrayBuffer(Buffer.from(chunk, encoding || 'utf8')))
141
- } else {
142
- result = res.writeArrayBuffer(toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8')))
143
- }
144
- if (typeof cb === 'function') {
145
- cb()
146
- }
132
+ res.cork(() => {
133
+ res.streaming = true
134
+ res.flushHeaders()
135
+ let data
136
+ if (chunk instanceof Buffer) {
137
+ data = toArrayBuffer(chunk)
138
+ } else if (typeof chunk === 'string') {
139
+ data = toArrayBuffer(Buffer.from(chunk, encoding || 'utf8'))
140
+ } else {
141
+ data = toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8'))
142
+ }
143
+ result = res.uwsWrite(data)
144
+ if (typeof cb === 'function') {
145
+ cb()
146
+ }
147
+ })
147
148
  return result
148
149
  } catch (e) {
149
150
  if (typeof cb === 'function') {
@@ -181,10 +182,12 @@ export function decorateResponse (res, req, finalizeResponse, errorTransformer,
181
182
  }
182
183
  // provide writableEnded like node does, with slightly different behavior
183
184
  if (!res.writableEnded) {
184
- res.flushHeaders()
185
- uwsEnd.call(res, body)
186
- res.writableEnded = true
187
- res.ended = true
185
+ res.cork(() => {
186
+ res.flushHeaders()
187
+ uwsEnd.call(res, body)
188
+ res.writableEnded = true
189
+ res.ended = true
190
+ })
188
191
  }
189
192
  if (typeof res.onEnd === 'function') {
190
193
  res.onEnd()
package/src/handler.mjs CHANGED
@@ -47,7 +47,11 @@ const endError = (res, e, refId, errorTransformer) => {
47
47
  e = errorTransformer(e, refId)
48
48
  }
49
49
  res.headers['x-ref-id'] = refId
50
- end(res, e.statusCode || 500, null, e.body || '')
50
+ const status = e.statusCode || 500
51
+ if (status === 500) {
52
+ log.error(e)
53
+ }
54
+ end(res, status, null, e.body || '')
51
55
  }
52
56
 
53
57
  const end = (res, defaultStatusCode, statusCodeOverride, body) => {
@@ -56,7 +60,6 @@ const end = (res, defaultStatusCode, statusCodeOverride, body) => {
56
60
  if (body instanceof Readable || res.streaming) {
57
61
  res.streaming = true
58
62
  if (body instanceof Readable) {
59
- res.flushHeaders()
60
63
  pipeResponse(res, body)
61
64
  }
62
65
  // handler is responsible for ending the response if they are streaming
@@ -65,10 +68,14 @@ const end = (res, defaultStatusCode, statusCodeOverride, body) => {
65
68
  }
66
69
  }
67
70
 
71
+ const ipv6CompressRegex = /\b:?(?:0+:?){2,}/g
72
+
73
+ const compressIpv6 = ip => ip && ip.includes(':') ? ip.replaceAll(ipv6CompressRegex, '::') : ip
74
+
68
75
  const writeAccess = function (req, res) {
69
76
  const start = new Date().getTime()
70
77
  return () => {
71
- log.access(req.remoteAddress, res.proxiedRemoteAddress || '', res.statusCode, req.method, req.url, new Date().getTime() - start + 'ms')
78
+ log.access(compressIpv6(req.remoteAddress), compressIpv6(res.proxiedRemoteAddress) || '', res.statusCode, req.method, req.url, new Date().getTime() - start + 'ms')
72
79
  }
73
80
  }
74
81
 
@@ -233,7 +240,7 @@ export const createNotFoundHandler = config => {
233
240
  res.statusCode = 500
234
241
  })
235
242
  } else {
236
- res.statusCode = 405
243
+ res.statusCode = 404
237
244
  res.end()
238
245
  }
239
246
  } else {
@@ -50,6 +50,7 @@ export const validateMiddleware = (middleware) => {
50
50
  }
51
51
 
52
52
  export const validateMiddlewareArray = (arr) => {
53
+ if (!arr) return
53
54
  if (!Array.isArray(arr)) {
54
55
  throw new Error('middleware must be an array of functions')
55
56
  }
package/src/server.mjs CHANGED
@@ -39,7 +39,7 @@ const startHttpRedirect = (host, port) => {
39
39
  uws.App().any('/*',
40
40
  (req, res) => {
41
41
  try {
42
- res.writeHead(301, { Location: `https://${req.headers.host}:${port}${req.url}` })
42
+ res.writeHead(301, { Location: `https://${req.headers.get('host')}:${port}${req.url}` })
43
43
  res.end()
44
44
  } catch (e) {
45
45
  log.error(`Failed to handle http request on port ${port}`, req.url, e)
@@ -91,18 +91,27 @@ export async function startServer (config) {
91
91
  if (config.defaultRoute && config.defaultRoute.match(routePattern)) {
92
92
  config.defaultRouteHandler = route
93
93
  }
94
+ let hadSlash = false
95
+ if (route.urlPath.endsWith('/')) {
96
+ hadSlash = true
97
+ route.urlPath = route.urlPath.substring(0, route.urlPath.length - 1)
98
+ }
94
99
  for (const method in route.handlers) {
95
100
  const theHandler = createHandler(route.handlers[method], route.middleware, route.pathParameters, config)
96
101
  app[appMethods[method]](route.urlPath, theHandler)
97
- if (route.urlPath.endsWith('/') && route.urlPath.length > 1) {
98
- app[appMethods[method]](route.urlPath.substr(0, route.urlPath.length - 1), theHandler)
102
+ if (hadSlash && config.serveRoutesWithSlash) {
103
+ app[appMethods[method]](route.urlPath + '/', theHandler)
99
104
  }
100
105
  if (route.urlPath.endsWith('/*')) {
101
106
  app[appMethods[method]](route.urlPath.substr(0, route.urlPath.length - 2), theHandler)
102
107
  }
103
108
  }
104
109
  if (config.autoOptions && !route.handlers.OPTIONS) {
105
- app.options(route.urlPath, optionsHandler(config, route.middleware, Object.keys(route.handlers).join(', ')))
110
+ const theHandler = optionsHandler(config, route.middleware, Object.keys(route.handlers).join(', '))
111
+ app.options(route.urlPath, theHandler)
112
+ if (hadSlash && config.serveRoutesWithSlash) {
113
+ app.options(route.urlPath + '/', theHandler)
114
+ }
106
115
  }
107
116
  }
108
117
 
@@ -71,4 +71,4 @@ export function createStaticHandler (fullPath, contentType, cacheStatic, staticC
71
71
  }
72
72
  }
73
73
  }
74
- }
74
+ }