@swarmmachina/swm-core 1.1.2 → 1.1.4

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": "@swarmmachina/swm-core",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Zero-dependency high-performance HTTP/WebSocket server built on uWebSockets.js",
5
5
  "keywords": [
6
6
  "http",
@@ -6,8 +6,15 @@ export default class HttpContext {
6
6
  #ip = ''
7
7
  #method = ''
8
8
  #url = ''
9
+ #headersCached = false
10
+ #headers = {}
11
+ #fullQuery = null
12
+ #fullQueryParsed = false
13
+ #query = {}
14
+ #params = {}
9
15
  #statusOverride = null
10
16
  #contentLength = undefined
17
+ #pendingHeaders = new Map()
11
18
  #bodyParser = new BodyParser()
12
19
  #resStreamer = new ResStreamer()
13
20
 
@@ -108,10 +115,17 @@ export default class HttpContext {
108
115
 
109
116
  this.#statusOverride = null
110
117
  this.#contentLength = undefined
118
+ this.#pendingHeaders.clear()
111
119
 
112
120
  this.#ip = ''
113
121
  this.#url = ''
114
122
  this.#method = ''
123
+ this.#headersCached = false
124
+ this.#headers = {}
125
+ this.#fullQuery = null
126
+ this.#fullQueryParsed = false
127
+ this.#query = {}
128
+ this.#params = {}
115
129
 
116
130
  this.#bodyParser.reset(this, maxSize)
117
131
  this.#resStreamer.reset(this, res)
@@ -135,9 +149,16 @@ export default class HttpContext {
135
149
 
136
150
  this.#statusOverride = null
137
151
  this.#contentLength = undefined
152
+ this.#pendingHeaders.clear()
138
153
  this.#ip = ''
139
154
  this.#url = ''
140
155
  this.#method = ''
156
+ this.#headersCached = false
157
+ this.#headers = {}
158
+ this.#fullQuery = null
159
+ this.#fullQueryParsed = false
160
+ this.#query = {}
161
+ this.#params = {}
141
162
 
142
163
  this.#bodyParser.clear()
143
164
  this.#resStreamer.clear()
@@ -166,6 +187,68 @@ export default class HttpContext {
166
187
  }
167
188
  }
168
189
 
190
+ cacheHeaders() {
191
+ if (this.#headersCached || !this.req) {
192
+ return
193
+ }
194
+
195
+ this.req.forEach((key, value) => {
196
+ this.#headers[key] = value
197
+ })
198
+
199
+ this.#headersCached = true
200
+ }
201
+
202
+ cacheQuery() {
203
+ if (this.#fullQuery !== null || !this.req) {
204
+ return
205
+ }
206
+
207
+ const fullQuery = this.req.getQuery()
208
+
209
+ this.#fullQuery = typeof fullQuery === 'string' ? fullQuery : ''
210
+ this.#fullQueryParsed = false
211
+ }
212
+
213
+ #parseFullQuery() {
214
+ if (this.#fullQueryParsed) {
215
+ return
216
+ }
217
+
218
+ const fullQuery = this.#fullQuery
219
+
220
+ if (!fullQuery) {
221
+ this.#fullQueryParsed = true
222
+ return
223
+ }
224
+
225
+ let start = 0
226
+
227
+ while (start <= fullQuery.length) {
228
+ let end = fullQuery.indexOf('&', start)
229
+
230
+ if (end === -1) {
231
+ end = fullQuery.length
232
+ }
233
+
234
+ const eq = fullQuery.indexOf('=', start)
235
+ const hasEq = eq !== -1 && eq < end
236
+ const key = hasEq ? fullQuery.slice(start, eq) : fullQuery.slice(start, end)
237
+
238
+ if (!(key in this.#query)) {
239
+ this.#query[key] = hasEq ? fullQuery.slice(eq + 1, end) : ''
240
+ }
241
+
242
+ if (end === fullQuery.length) {
243
+ break
244
+ }
245
+
246
+ start = end + 1
247
+ }
248
+
249
+ this.#fullQueryParsed = true
250
+ }
251
+
169
252
  ip() {
170
253
  if (!this.res) {
171
254
  return ''
@@ -213,7 +296,25 @@ export default class HttpContext {
213
296
  * @returns {string|undefined}
214
297
  */
215
298
  query(name) {
216
- return this.req.getQuery(name)
299
+ if (name in this.#query) {
300
+ return this.#query[name]
301
+ }
302
+
303
+ if (this.#fullQuery !== null) {
304
+ this.#parseFullQuery()
305
+
306
+ if (name in this.#query) {
307
+ return this.#query[name]
308
+ }
309
+
310
+ this.#query[name] = undefined
311
+ return undefined
312
+ }
313
+
314
+ const value = this.req.getQuery(name)
315
+
316
+ this.#query[name] = value
317
+ return value
217
318
  }
218
319
 
219
320
  /**
@@ -221,7 +322,14 @@ export default class HttpContext {
221
322
  * @returns {string|undefined}
222
323
  */
223
324
  param(i) {
224
- return this.req.getParameter(i)
325
+ if (i in this.#params) {
326
+ return this.#params[i]
327
+ }
328
+
329
+ const value = this.req.getParameter(i)
330
+
331
+ this.#params[i] = value
332
+ return value
225
333
  }
226
334
 
227
335
  /**
@@ -229,7 +337,18 @@ export default class HttpContext {
229
337
  * @returns {string}
230
338
  */
231
339
  header(name) {
232
- return this.req.getHeader(name)
340
+ if (name in this.#headers) {
341
+ return this.#headers[name]
342
+ }
343
+
344
+ if (this.#headersCached) {
345
+ return ''
346
+ }
347
+
348
+ const value = this.req.getHeader(name)
349
+
350
+ this.#headers[name] = value
351
+ return value
233
352
  }
234
353
 
235
354
  contentLength() {
@@ -280,7 +399,11 @@ export default class HttpContext {
280
399
  * @returns {HttpContext}
281
400
  */
282
401
  setHeader(key, value) {
283
- this.res.writeHeader(key, value)
402
+ if (this.replied || this.aborted) {
403
+ return this
404
+ }
405
+
406
+ this.#stageHeader(key, value)
284
407
  return this
285
408
  }
286
409
 
@@ -288,34 +411,85 @@ export default class HttpContext {
288
411
  * @param {Record<string, string> | null | undefined} headers
289
412
  */
290
413
  setHeaders(headers) {
414
+ if (this.replied || this.aborted) {
415
+ return
416
+ }
417
+
418
+ this.#stageHeaders(headers)
419
+ }
420
+
421
+ /**
422
+ * @param {Record<string, string> | null | undefined} headers
423
+ */
424
+ flushHeaders(headers = null) {
425
+ this.#flushPendingHeaders(headers)
426
+ }
427
+
428
+ /**
429
+ * @param {string} key
430
+ * @param {string} value
431
+ */
432
+ #stageHeader(key, value) {
433
+ if (value === undefined || value === null) {
434
+ return
435
+ }
436
+
437
+ const headerName = String(key)
438
+ const headerValue = String(value)
439
+
440
+ this.#pendingHeaders.set(headerName.toLowerCase(), [headerName, headerValue])
441
+ }
442
+
443
+ /**
444
+ * @param {Record<string, string> | null | undefined} headers
445
+ */
446
+ #stageHeaders(headers) {
291
447
  if (!headers) {
292
448
  return
293
449
  }
294
450
 
295
451
  if (headers === TEXT_PLAIN_HEADER) {
296
- this.res.writeHeader('content-type', 'text/plain; charset=utf-8')
452
+ this.#stageHeader('content-type', 'text/plain; charset=utf-8')
297
453
  return
298
454
  }
299
455
 
300
456
  if (headers === JSON_HEADER) {
301
- this.res.writeHeader('content-type', 'application/json; charset=utf-8')
457
+ this.#stageHeader('content-type', 'application/json; charset=utf-8')
302
458
  return
303
459
  }
304
460
 
305
461
  if (headers === OCTET_STREAM_HEADER) {
306
- this.res.writeHeader('content-type', 'application/octet-stream')
462
+ this.#stageHeader('content-type', 'application/octet-stream')
307
463
  return
308
464
  }
309
465
 
310
466
  for (const key in headers) {
311
467
  const value = headers[key]
312
468
 
313
- if (value !== undefined && value !== null) {
314
- this.res.writeHeader(key, value)
315
- }
469
+ this.#stageHeader(key, value)
316
470
  }
317
471
  }
318
472
 
473
+ /**
474
+ * @param {Record<string, string> | null | undefined} headers
475
+ */
476
+ #flushPendingHeaders(headers = null) {
477
+ if (!this.res) {
478
+ this.#pendingHeaders.clear()
479
+ return
480
+ }
481
+
482
+ if (headers) {
483
+ this.#stageHeaders(headers)
484
+ }
485
+
486
+ for (const [, [key, value]] of this.#pendingHeaders) {
487
+ this.res.writeHeader(key, value)
488
+ }
489
+
490
+ this.#pendingHeaders.clear()
491
+ }
492
+
319
493
  /**
320
494
  * @param {any} result
321
495
  * @returns {void}
@@ -399,7 +573,7 @@ export default class HttpContext {
399
573
  }
400
574
 
401
575
  this.res.writeStatus(this.getStatus(status))
402
- this.setHeaders(headers)
576
+ this.#flushPendingHeaders(headers)
403
577
 
404
578
  if (body != null) {
405
579
  this.res.end(body)
package/src/index.js CHANGED
@@ -325,6 +325,11 @@ export default class Server {
325
325
  }
326
326
 
327
327
  if (isPromise(result)) {
328
+ ctx.method()
329
+ ctx.url()
330
+ ctx.cacheQuery()
331
+ ctx.cacheHeaders()
332
+
328
333
  // eslint-disable-next-line promise/catch-or-return
329
334
  result.then(ctx.onResolve, ctx.onReject)
330
335
  return
@@ -107,7 +107,7 @@ export default class ResStreamer {
107
107
 
108
108
  res.cork(() => {
109
109
  res.writeStatus(typeof status === 'string' ? status : this.#ctx.getStatus(status))
110
- this.#ctx.setHeaders(headers)
110
+ this.#ctx.flushHeaders(headers)
111
111
  })
112
112
 
113
113
  this.#started = true