webhoster 0.1.1 → 0.3.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.
Files changed (85) hide show
  1. package/.eslintrc.json +74 -58
  2. package/.github/copilot-instructions.md +100 -0
  3. package/.github/workflows/test-matrix.yml +37 -0
  4. package/.test/benchmark.js +28 -0
  5. package/.test/constants.js +4 -0
  6. package/{test → .test}/http2server.js +1 -1
  7. package/{test → .test}/httpserver.js +1 -1
  8. package/{test → .test}/index.js +178 -192
  9. package/.test/multipromise.js +32 -0
  10. package/{test → .test}/tls.js +3 -3
  11. package/{test → .test}/urlencoded.js +3 -0
  12. package/.vscode/launch.json +24 -3
  13. package/README.md +116 -90
  14. package/data/CookieObject.js +14 -14
  15. package/errata/socketio.js +6 -11
  16. package/examples/starter.js +11 -0
  17. package/helpers/HeadersParser.js +7 -8
  18. package/helpers/HttpListener.js +387 -42
  19. package/helpers/RequestHeaders.js +43 -36
  20. package/helpers/RequestReader.js +27 -26
  21. package/helpers/ResponseHeaders.js +47 -36
  22. package/jsconfig.json +1 -1
  23. package/lib/HttpHandler.js +447 -277
  24. package/lib/HttpRequest.js +383 -39
  25. package/lib/HttpResponse.js +316 -52
  26. package/lib/HttpTransaction.js +146 -0
  27. package/middleware/AutoHeadersMiddleware.js +73 -0
  28. package/middleware/CORSMiddleware.js +45 -47
  29. package/middleware/CaseInsensitiveHeadersMiddleware.js +5 -11
  30. package/middleware/ContentDecoderMiddleware.js +81 -35
  31. package/middleware/ContentEncoderMiddleware.js +179 -132
  32. package/middleware/ContentLengthMiddleware.js +66 -41
  33. package/middleware/ContentWriterMiddleware.js +5 -5
  34. package/middleware/HashMiddleware.js +68 -40
  35. package/middleware/HeadMethodMiddleware.js +24 -21
  36. package/middleware/MethodMiddleware.js +29 -36
  37. package/middleware/PathMiddleware.js +49 -66
  38. package/middleware/ReadFormData.js +99 -0
  39. package/middleware/SendJsonMiddleware.js +131 -0
  40. package/middleware/SendStringMiddleware.js +87 -0
  41. package/package.json +38 -29
  42. package/polyfill/FormData.js +164 -0
  43. package/rollup.config.js +0 -1
  44. package/scripts/test-all-sync.sh +6 -0
  45. package/scripts/test-all.sh +7 -0
  46. package/templates/starter.js +53 -0
  47. package/test/fixtures/stream.js +68 -0
  48. package/test/helpers/HttpListener/construct.js +18 -0
  49. package/test/helpers/HttpListener/customOptions.js +22 -0
  50. package/test/helpers/HttpListener/doubleCreate.js +40 -0
  51. package/test/helpers/HttpListener/events.js +77 -0
  52. package/test/helpers/HttpListener/http.js +31 -0
  53. package/test/helpers/HttpListener/http2.js +41 -0
  54. package/test/helpers/HttpListener/https.js +38 -0
  55. package/test/helpers/HttpListener/startAll.js +31 -0
  56. package/test/helpers/HttpListener/stopNotStarted.js +23 -0
  57. package/test/lib/HttpHandler/class.js +8 -0
  58. package/test/lib/HttpHandler/handleRequest.js +11 -0
  59. package/test/lib/HttpHandler/middleware.js +941 -0
  60. package/test/lib/HttpHandler/parse.js +41 -0
  61. package/test/lib/HttpRequest/class.js +8 -0
  62. package/test/lib/HttpRequest/downstream.js +171 -0
  63. package/test/lib/HttpRequest/properties.js +101 -0
  64. package/test/lib/HttpRequest/read.js +518 -0
  65. package/test/lib/HttpResponse/class.js +8 -0
  66. package/test/lib/HttpResponse/properties.js +59 -0
  67. package/test/lib/HttpResponse/send.js +275 -0
  68. package/test/lib/HttpTransaction/class.js +8 -0
  69. package/test/lib/HttpTransaction/ping.js +50 -0
  70. package/test/lib/HttpTransaction/push.js +89 -0
  71. package/test/middleware/SendJsonMiddleware.js +222 -0
  72. package/test/sanity.js +10 -0
  73. package/test/templates/starter.js +93 -0
  74. package/tsconfig.json +12 -0
  75. package/types/index.js +61 -34
  76. package/types/typings.d.ts +8 -9
  77. package/utils/AsyncObject.js +6 -3
  78. package/utils/CaseInsensitiveObject.js +2 -3
  79. package/utils/function.js +1 -7
  80. package/utils/headers.js +42 -0
  81. package/utils/qualityValues.js +1 -1
  82. package/utils/stream.js +4 -20
  83. package/index.cjs +0 -3190
  84. package/test/constants.js +0 -4
  85. /package/{test → .test}/cookietester.js +0 -0
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @param buffer
3
+ */
1
4
  function decode(buffer) {
2
5
  const sequences = [];
3
6
  let startIndex = 0;
@@ -4,11 +4,32 @@
4
4
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
5
  "version": "0.2.0",
6
6
  "configurations": [
7
+ {
8
+ "name": "Debug AVA",
9
+ "request": "launch",
10
+ "runtimeArgs": [
11
+ "run-script",
12
+ "debug-test"
13
+ ],
14
+ "runtimeExecutable": "npm",
15
+ "skipFiles": [
16
+ "<node_internals>/**"
17
+ ],
18
+ "type": "pwa-node"
19
+ },
7
20
  {
8
21
  "type": "node",
9
22
  "request": "launch",
10
- "name": "Test",
11
- "program": "${workspaceFolder}/test/index.js"
12
- }
23
+ "name": "Debug AVA test file",
24
+ "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ava",
25
+ "runtimeArgs": [
26
+ "--serial",
27
+ "${file}"
28
+ ],
29
+ "outputCapture": "std",
30
+ "skipFiles": [
31
+ "<node_internals>/**/*.js"
32
+ ]
33
+ },
13
34
  ]
14
35
  }
package/README.md CHANGED
@@ -1,8 +1,17 @@
1
1
  ![npm](https://img.shields.io/npm/v/webhoster) ![David](https://img.shields.io/david/clshortfuse/webhoster) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/clshortfuse/webhoster) ![node-current](https://img.shields.io/node/v/webhoster) ![npm](https://img.shields.io/npm/dw/webhoster) ![GitHub top language](https://img.shields.io/github/languages/top/clshortfuse/webhoster)
2
2
 
3
+ <!-- Per-node test status badges (matrix job names must match `test-matrix.yml` job names) -->
4
+ ![Node 16.13](https://img.shields.io/github/actions/workflow/status/clshortfuse/webhoster/test-matrix.yml?branch=master&label=Node%2016.13&job=Test%20on%20Node%2016.13)
5
+ ![Node 16](https://img.shields.io/github/actions/workflow/status/clshortfuse/webhoster/test-matrix.yml?branch=master&label=Node%2016&job=Test%20on%20Node%2016)
6
+ ![Node 18](https://img.shields.io/github/actions/workflow/status/clshortfuse/webhoster/test-matrix.yml?branch=master&label=Node%2018&job=Test%20on%20Node%2018)
7
+ ![Node 20](https://img.shields.io/github/actions/workflow/status/clshortfuse/webhoster/test-matrix.yml?branch=master&label=Node%2020&job=Test%20on%20Node%2020)
8
+ ![Node 22](https://img.shields.io/github/actions/workflow/status/clshortfuse/webhoster/test-matrix.yml?branch=master&label=Node%2022&job=Test%20on%20Node%2022)
9
+
10
+ <!-- Note: these badges reflect the job named "Test on Node <version>" in `.github/workflows/test-matrix.yml`. -->
11
+
3
12
  # webhoster
4
13
 
5
- An opt-in, stream-based approach to Web Hosting with NodeJS.
14
+ An opt-in, stream-based, tree-processing approach to Web Hosting with NodeJS.
6
15
 
7
16
  * Supports HTTP
8
17
  * Supports HTTPS
@@ -10,7 +19,7 @@ An opt-in, stream-based approach to Web Hosting with NodeJS.
10
19
 
11
20
  #### Nothing is true; everything is permitted
12
21
 
13
- By default, the framework does nothing. It parses no headers. It writes no headers. It never writes to or reads from any stream. That is, unless you add middleware. All middleware is built to provide maximum throughput and do as little as possible.
22
+ By default, the framework does nothing. It parses no headers. It writes no headers. It never writes to or reads from any stream. All processing is handled by middleware. All middleware is built to provide maximum throughput and do as little as possible.
14
23
 
15
24
  ## install
16
25
 
@@ -28,10 +37,9 @@ For now, take a look at [/test/index.js](/test/index.js)
28
37
  *Class that handles the logic for handling requests and responses*
29
38
 
30
39
  * `.defaultInstance` - (`HttpHandler`) - Returns a instance of `HttpHandler` that can be accessed staticly.
31
- * `.preprocessors` - (`Middleware[]`) - An array of middleware to use when handling requests before running the main middleware collection.
32
- * `.middleware` - (`Set<Middleware>`) - A collection of middleware chains to iterate though when handling a request. It is recommended to create isolated chains (eg: `/images/`; `/api/`; `/views/`; etc.). The use of `Set` type is to avoid mistakenly inserting the same middleware chain twice.
40
+ * `.middleware` - (`Middleware[]`) - An array of middleware operations to iterate through when handling a request. It is recommended to create isolated branches (eg: `/images/`; `/api/`; `/views/`; etc.).
33
41
  * `.errorHandlers` - (`MiddlewareErrorHandler[]`) - An array of `MiddlewareErrorHandler` that will handle errors and respond appropriately (eg: `res.status = 500`)
34
- * `.handleRequest` - (`function(MiddlewareFunctionParams):Promise<HttpResponse>`) - handles logic for calling preprocessors, middleware, and error handlers. Unlikely to be used directly.
42
+ * `.handleRequest` - (`function(MiddlewareFunctionParams):Promise<HttpResponse>`) - handles logic for calling middleware and error handlers. Unlikely to be used directly.
35
43
  * `.handleHttp1Request` - (`function(IncomingMessage, ServerResponse):Promise<HttpResponse>`) - constructs a new `HttpRequest` and `HttpResponse` based on the HTTP1 parameters and passes it to `handleRequest`
36
44
  * `.handleHttp2Stream` - (`function(ServerHttp2Stream, IncomingHttpHeaders, HttpResponseOptions):Promise<HttpResponse>`) - constructs a new `HttpRequest` and `HttpResponse` based on the HTTP2 parameters and passes it to `handleRequest`
37
45
 
@@ -39,21 +47,24 @@ For now, take a look at [/test/index.js](/test/index.js)
39
47
 
40
48
  ````js
41
49
  const handler = HttpHandler.defaultInstance;
42
- handler.preprocessors.push([
43
- new SendHeadersMiddleware(),
44
- new ContentLengthMiddleware(),
45
- new HashMiddleware(),
46
- new ContentEncoderMiddleware(),
47
- new ContentDecoderMiddleware(),
48
- new ContentWriterMiddleware({ setCharset: true, setJSON: true }),
49
- new ContentReaderMiddleware({ buildString: true, parseJSON: true }),
50
- ]);
51
- handler.middleware.add(imagesMiddleware);
52
- handler.middleware.add(return404Middleware);
53
- handler.errorHandlers.push([
50
+ handler.middleware.push(
51
+ new ContentDecoderMiddleware(), // Automatically decodes content
52
+ new SendStringMiddleware(), // Auto convert strings to Buffer
53
+ new SendJsonMiddleware(), // Auto converts objects to JSON
54
+ new ContentEncoderMiddleware(), // Compress anything after
55
+ new HashMiddleware(), // Hash anything after
56
+ new ContentLengthMiddleware(), // Calculate length of anything after
57
+ new AutoHeadersMiddleware(), // Send headers automatically
58
+ new HeadMethodMiddleware(), // Discard body content
59
+ );
60
+ handler.middleware.push(
61
+ imagesMiddleware,
62
+ return404Middleware,
63
+ );
64
+ handler.errorHandlers.push(
54
65
  errorLoggerMiddleware,
55
- return500Middleware
56
- ]);
66
+ return500Middleware,
67
+ );
57
68
  http1Server.addListener('request', handler.handleHttp1Request);
58
69
  http2Server.addListener('stream', handler.handleHttp2Stream);
59
70
  ````
@@ -61,58 +72,66 @@ For now, take a look at [/test/index.js](/test/index.js)
61
72
  ### [HttpRequest.js](/lib/HttpRequest.js)
62
73
  *Class that provides the bare-minimum to bridge different protocols for client requests*
63
74
 
64
- * `.stream` - (`Readable`) - This is generally how you will read content inside client requests. With no middleware, it emits a `Buffer`. But if are using an Object Mode middleware like `contentReader.js`, events may emit an `Object` or `string`.
75
+ * `.read()` - `Promise<any>` - Returns content as handled by request's content handlers. Returns `.raw()` if no compatible handler found.
76
+ * `.stream` - (`Readable`) - Allows for direct interaction with tail-end of the request stream pipeline. With no middleware, it emits `Buffer` chunks.
77
+ * `.body` - (`ReadableStream`) - Returns request's body as `ReadableStream` (if supported).
78
+ * `.bodyUsed` - (`boolean`) - Returns whether request's body has been read from.
79
+ * `.arrayBuffer()` - (`Promise<ArrayBuffer>`) - Returns a promise fulfilled with request's body as `ArrayBuffer`.
80
+ * `.blob()` - (`Promise<Blob>`) - Returns a promise fulfilled with request's body as `Blob`.
81
+ * `.formData()` - (`Promise<FormData>`) - Returns a promise fulfilled with request's body as `FormData`. Not implemented by default.
82
+ * `.json()` - (`Promise<any>`) - Returns a promise fulfilled with request's body parsed as `JSON`.
83
+ * `.text()` - (`Promise<string>`) - Returns a promise fulfilled with request's body as `string`.
65
84
  * `.headers` - (`IncomingHttpHeaders`) - The response headers exactly as presented to the NodeJS Server with no modifications.
66
- * `.locals` - (`Object<string,any>`) - Object that gets passed in every step of the middleware chain. Application-level variables *should* be presented here.
67
- * `.replaceStream()` - (`function(stream:Readable):Readable`) - replaces the current stream with a new stream. Used by high-level middleware for the purpose of transforming data (eg: JSON-parsing).
85
+ * `.locals` - (`Object<string,any>`) - Object that gets passed in every step of the middleware tree. Application-level variables *should* be presented here.
86
+ * `.addDownstream()` - (`function(stream:Readable):Readable`) - adds a downstream to the current pipeline. Used by preprocessor middleware for the purpose of transforming data (eg: JSON-parsing) before reaching logic middleware.
68
87
 
69
88
  ### Example
70
89
 
71
90
  ````js
72
- async function onPostComment({req, res}) {
73
- const content = (await req.stream[Symbol.asyncIterator]().next()).value;
91
+ async function onPostComment({ request }) {
92
+ const content = await request.read();
74
93
  let comment;
75
94
  try {
76
95
  comment = new UserComment(content);
77
96
  } catch {
78
- res.status = 400;
79
- return 'end';
97
+ return 400;
80
98
  }
81
99
  try {
82
100
  await insertComment(comment);
83
101
  } catch {
84
- res.status = 500;
85
- return 'end';
102
+ return 500;
86
103
  }
87
- res.status = 200;
88
- res.stream.end({status: 'OK'});
89
- return 'end';
104
+ return { status: 'OK' };
90
105
  }
91
106
  ````
92
107
 
93
108
  ### [HttpResponse.js](/lib/HttpResponse.js)
94
109
  *Class that provides the bare-minimum to bridge different protocols for client responses*
95
110
 
96
- * `.stream` - (`Writable`) - This is generally how you will return payloads in your custom middleware. It's recommended to use `.end(payload)` unless you are sending things in chunks with `.write()`. `.pipe()` is also supported. With no middleware, it accepts a `Buffer` or `string`. But if are using an Object Mode middleware like `contentWriter.js`, then you can pass an `Object` that can transform the object to JSON and set the appropriate headers automatically.
97
- * `.headers` - (`OutgoingHttpHeaders`) - The response headers exactly as presented to the NodeJS Server with no modifications.
98
- * `.status` - (`number`) - The response status
99
- * `.locals` - (`Object<string,any>`) - Object that gets passed in every step of the middleware chain. Application-level variables *should* be presented here.
100
- * `.replaceStream()` - (`function(stream:Writable):Writable`) - replaces the current stream with a new stream. Used by high-level middleware for the purpose of transforming data, or analyzing data to modify response headers. (eg: zlib compression, hashing, content-length).
111
+ * `end(content:any) => HttpHandler.END` - Content to be sent to the client based on the Content Handler configured. The function will call `.stream.end` and return `HttpHandler.END`.
112
+ * `headers` - (`OutgoingHttpHeaders`) - The response headers exactly as presented to the NodeJS Server with no modifications.
113
+ * `content` - (`any`) - Content that will be sent to client.
114
+ * `status` - (`number`) - The response status code
115
+ * `setStatus(statusCode:number) => this` - Sets `.status` while returning `this` (`HttpResponse`). Will throw an `Error` if headers have already been sent.
116
+ * `code(statusCode:number) => this` - Sets `.status` while returning `this` (`HttpResponse).
117
+ * `async send(content:any)` - Similar to `end`, but asynchronous. Return when the client stream ends, confirming the data was sent, or throws an error on failure. Useful for ensuring client received the data.
118
+ * `stream` - (`Writable`) - Used for interacting with the stream. With no custom middleware, it accepts a `Buffer` or `string`.
119
+ * `pipeFrom(source:any)` - Creates a pipeline starting with source. May insert `.pipeProcessors` into pipeline. Returns `HttpHandler.END`;
120
+ * `pipelineFrom(source:any) => Promise<HttpHandler.END>` - Similar to `pipeFrom`, but asynchronous. Return when the client stream ends, confirming the data was sent, or throws an error on failure. Useful for ensuring client received the data.
121
+ * `.finalizers` - Array of body processors that may read and transform `.body`, or simply analyze on `.body`.
101
122
  * `.canPushPath` - (`boolean`) - `true` on HTTP/2 streams that support push
102
123
  * `.pushPath` - (`function(path:string):Promise`) - Push a new HTTP/2 stream simulating a request for `path`
103
124
 
104
125
  ### Example
105
126
 
106
127
  ````js
107
- async function onGetIndexPage({res}) {
108
- const indexHTML = await getIndexPage();
109
- res.status = 200;
110
- if (res.canPushPath) {
111
- res.pushPath('/script.js');
112
- res.pushPath('/styles.css');
128
+ async function onGetIndexPage(transaction) {
129
+ if (transaction.canPushPath) {
130
+ transaction.pushPath('/script.js').catch(() => { /* Ignore push failure */ });
131
+ transaction.pushPath('/styles.css').catch(() => { /* Ignore push failure */ });;
113
132
  }
114
- res.stream.end(indexHTML);
115
- return 'end';
133
+ transaction.response.header['content-type'] = 'text/html';
134
+ return await getIndexPage();
116
135
  }
117
136
  ````
118
137
 
@@ -120,34 +139,38 @@ async function onGetIndexPage({res}) {
120
139
 
121
140
  #### MiddlewareFunction
122
141
 
123
- Middleware logic flows in a tree structure, allowing for `break`, `end`, or `continue`.
142
+ Middleware logic flows in a tree structure, allowing for `continue`, `break`, or `end`.
124
143
 
125
- A `MiddlewareFunction` is a function that accepts a `MiddlewareFunctionParams` object structured as `{ res: HttpRequest, res: HttpResponse }`. The function must return a instruction as to proceed with the current step in tree-based logic. It maybe return any of these instructions with any of the values as a literal or a `Promise`:
144
+ A `MiddlewareFunction` is a function that accepts a `MiddlewareFunctionParams` object structured as `{ res: HttpRequest, res: HttpResponse }`. The function can return a instruction with the step in the tree-based logic, status code, or content body to be handled. It maybe return any of these instructions with any of the values as a literal, a `Promise`, or `PromiseLike`:
126
145
 
127
- * End: Terminates the entire middleware chain. `values: 'end'`
128
- * Continue: Continues on the current chain to the next middleware, or moves to the next chain if there are no siblings left. `values: 'continue'|void|null|undefined`
129
- * Break: Breaks from the current middleware chain, and continues to the next chain. `values: 'break'`
146
+ * `HttpHandler.CONTINUE`: Continues on the current branch to the next middleware, or moves to the next branch if there are no siblings left. `alias: true|void|null|undefined`
147
+ * `HttpHandler.BREAK`: Breaks from the current middleware branch, and continues to the next branch. `alias: false`
148
+ * `HttpHandler.END`: Terminates the entire middleware tree. `alias: 0`
149
+ * `number`: Sets status code and then ends stream. `alias: res.code(statusCode).end()`
150
+ * `Set|Map`: Add an inline middleware branch.
151
+ * `Array`: Explicitly passed to `HttpResponse.end()`. This is to support sending an `Array` object instead having it becoming an inline middleware branch.
152
+ * `any`: Any other value returned would automatically be passed to `HttpResponse.end()` which, in turn, uses it's own content handlers (eg: `JSON`; `Readable`), and finally terminates the middleware tree.
130
153
 
131
- A `MiddlewareFilter` is a function that accepts a `MiddlewareFunctionParams` and returns a `MiddlewareContinueBoolean` or `Promise<MiddlewareContinueBoolean>` signaling whether to continue in the chain. `true` means to `continue`. `false` means to `break`. There is no support for `end` logic in a MiddlewareFilter.
154
+ A `MiddlewareFilter` is a function that accepts a `MiddlewareFunctionParams` and returns a `boolean` or `Promise<boolean>` signaling whether to continue in the branch. `true` translates to `HttpHandler.CONTINUE`. `false` translates to `HttpHandler.BREAK`. There is no support for `HttpHandler.END` logic in a MiddlewareFilter by design.
132
155
 
133
- A `MiddlewareErrorHandler` is an `Object` with a `onError` property. `onError` is like a MiddlewareFunction, but includes an `err` item in its parameter object. When the handler is in an error state, it will bubble upwards while search for the next `MiddlewareErrorHandler`.
156
+ A `MiddlewareErrorHandler` is an `Object` with a `onError` property. `onError` is like a MiddlewareFunction, but includes an `err` item in its parameter object. When the handler is in an error state, it will bubble upwards while searching for the next `MiddlewareErrorHandler`.
134
157
 
135
- `Middleware` can be a `MiddlewareFunction` or `MiddlewareFilter`. It can also a compatible response value of either: `'continue'|true|null|void|undefined`, `'break'|false`, `'end'`. The response can be the value or a `Promise`.
158
+ `Middleware` can be a `MiddlewareFunction` or `MiddlewareFilter`. It can also a compatible response value of either: `HttpHandler.CONTINUE|true|null|void|undefined`, `HttpHandler.BREAK|false`, `HttpHandler.END|0`. The response can be the value or a `Promise`.
136
159
 
137
- To support branching, `Middleware` can also be a `Iterable<Middleware>` (include `Array` or `Set`), `Map<any, Middleware>` or `Object<string, Middleware>`. The `HttpHandler` will iterate through each and flow based on the `break`, `continue`, or `end` instruction return by each entry.
160
+ To support branching, `Middleware` can also be a `Iterable<Middleware>` (eg: `Set`) or `Map<any, Middleware>`. The `HttpHandler` will iterate through each and flow based on the `break`, `continue`, or `end` instruction returned by each entry.
138
161
 
139
162
  ## Included Middleware
140
163
 
141
164
  ### Response Middleware
142
- * [SendHeaders](/middleware/SendHeadersMiddleware.js) - Automatically sends response headers before writing or ending a response stream
143
- * [ContentLength](/middleware/contentLengthMiddleware.js) - Sets `Content-Length` based on response stream content writes
144
- * [Hash](/middleware/HashMiddleware.js) - Sets `ETag`, `Digest`, and `Content-MD5` response headers automatically
145
- * [ContentEncoder](/middleware/ContentEncoderMiddleware.js) - Applies `Content-Encoding` to response based on `Accept-Encoding` request header
146
- * [ContentWriter](/middleware/ContentWriterMiddleware.js) - Adds `Object Mode` write support to response stream, including `string` and `JSON` support
165
+ * [AuthHeaders](./middleware/AutoHeadersMiddleware.js) - Automatically sends response headers before writing or ending a response stream
166
+ * [ContentLength](./middleware/contentLengthMiddleware.js) - Sets `Content-Length` based on response stream content writes
167
+ * [Hash](./middleware/HashMiddleware.js) - Sets `ETag`, `Digest`, and `Content-MD5` response headers automatically
168
+ * [ContentEncoder](./middleware/ContentEncoderMiddleware.js) - Applies `Content-Encoding` to response based on `Accept-Encoding` request header
169
+ * [SendJson](./middleware/SendJsonMiddleware.js) - Adds response content processor that encodes objects and arrays to JSON string. Sets `application/json;charset=utf-8`, if content-type not set.
170
+ * [SendString](./middleware/SendStringMiddleware.js) - Adds response content processor that encodes a string. Uses `charset` or uses `utf-8`, if not set.
147
171
 
148
172
  ### Request Middleware
149
173
  * [ContentDecoder](/middleware/ContentDecoderMiddleware.js) - Decodes `Content-Encoding` from request streams
150
- * [ContentReader](/middleware/ContentReaderMiddleware.js) - Adds `Object Mode` read support to request stream, including `string`, `JSON`, and `urlencoded` support. Can cache transformation into `req.local` for convenience.
151
174
 
152
175
  ### Logic Middleware
153
176
  * [Path](/middleware/PathMiddleware.js) - Creates logic filter based on URL pathname
@@ -160,30 +183,29 @@ To support branching, `Middleware` can also be a `Iterable<Middleware>` (include
160
183
  ### Examples:
161
184
 
162
185
  ````js
163
- HttpHandler.defaultInstance.preprocessors.push({
164
- // This is an object with names for each entry
165
- sendHeaders: new SendHeadersMiddleware(),
166
- contentLength: new ContentLengthMiddleware(),
167
- hash: (USE_HASH ? new HashMiddleware() : 'continue'),
168
- });
169
- HttpHandler.defaultInstance.middleware.add([
170
- PathMiddleware.SUBPATH('/api'),
171
- new CORSMiddleware(),
172
- [MethodMiddleware.GET, myAPIGetFunctions],
173
- [MethodMiddleware.POST, myAPIPostFunctions],
174
- ]);
175
- HttpHandler.defaultInstance.middleware.add([
176
- new PathMiddleware(/^\/(index.html?)?$/),
177
- indexPageMiddleware,
178
- 'end',
179
- ]);
180
- HttpHandler.defaultInstance.middleware.add(arrayToBePopulatedLater);
181
- HttpHandler.defaultInstance.middleware.add(({ res }) => { res.status = 404; return 'end'; });
186
+ HttpHandler.defaultInstance.middleware.push(
187
+ new AutoHeadersMiddleware(),
188
+ new ContentLengthMiddleware(),
189
+ hash: (USE_HASH ? new HashMiddleware() : HttpHandler.CONTINUE),
190
+ );
191
+ HttpHandler.defaultInstance.middleware.push(
192
+ [
193
+ PathMiddleware.SUBPATH('/api'),
194
+ new CORSMiddleware(),
195
+ [MethodMiddleware.GET, myAPIGetFunctions],
196
+ [MethodMiddleware.POST, myAPIPostFunctions],
197
+ ],
198
+ [
199
+ new PathMiddleware(/^\/(index\.html?)?$/),
200
+ indexPageMiddleware
201
+ ],
202
+ arrayToBePopulatedLater,
203
+ 404 // Equivalient of ({response}) => response.code(404).end()
204
+ );
182
205
  HttpHandler.defaultInstance.errorHandlers.push({
183
- onError({ res, err }) {
184
- console.error(err);
185
- res.status = 500;
186
- return 'end';
206
+ onError({error}) {
207
+ console.error(error);
208
+ return 500;
187
209
  },
188
210
  });
189
211
  ````
@@ -192,17 +214,21 @@ HttpHandler.defaultInstance.errorHandlers.push({
192
214
 
193
215
 
194
216
  ````js
195
- async function checkToken({req, res}) {
196
- const content = (await req.stream[Symbol.asyncIterator]().next()).value;
197
- req.locals.content = content;
217
+ async function checkToken({request, response, locals}) {
218
+ const content = await req.read();
198
219
  try {
199
220
  const decoded = await decodeJWT(content.token);
200
- req.locals.jwt = decoded;
201
- delete req.locals.content.token;
221
+ locals.jwt = decoded;
202
222
  } catch {
203
- res.status = 401;
204
- return 'end';
223
+ return 401;
205
224
  }
206
- return 'continue'
225
+ /**
226
+ * Since we want the logic to continue to the next step,
227
+ * We can either allow the function to implicitly return `undefined`
228
+ * or explicitly use any of the following:
229
+ * * return undefined;
230
+ * * return true;
231
+ * * return HttpHandler.CONTINUE;
232
+ */
207
233
  }
208
234
  ````
@@ -25,7 +25,7 @@ export default class CookieObject {
25
25
  static parse(cookieString) {
26
26
  /** @type {Partial<CookieDetails>} */
27
27
  const options = {};
28
- cookieString.split(';').forEach((pair, index) => {
28
+ for (const [index, pair] of cookieString.split(';').entries()) {
29
29
  const indexOfEquals = pair.indexOf('=');
30
30
  let key;
31
31
  let value;
@@ -33,28 +33,28 @@ export default class CookieObject {
33
33
  key = '';
34
34
  value = pair.trim();
35
35
  } else {
36
- key = pair.substr(0, indexOfEquals).trim();
37
- value = pair.substr(indexOfEquals + 1).trim();
36
+ key = pair.slice(0, indexOfEquals).trim();
37
+ value = pair.slice(indexOfEquals + 1).trim();
38
38
  }
39
39
  const firstQuote = value.indexOf('"');
40
40
  const lastQuote = value.lastIndexOf('"');
41
41
  if (firstQuote !== -1 && lastQuote !== -1) {
42
- value = value.substring(firstQuote + 1, lastQuote);
42
+ value = value.slice(firstQuote + 1, lastQuote);
43
43
  }
44
44
  if (index === 0) {
45
45
  options.name = key;
46
46
  if (value != null) {
47
47
  options.value = value;
48
48
  }
49
- return;
49
+ continue;
50
50
  }
51
51
  switch (key.toLowerCase()) {
52
52
  case 'expires':
53
53
  options.expires = new Date(value);
54
- return;
54
+ continue;
55
55
  case 'max-age':
56
- options.maxAge = parseInt(value, 10);
57
- return;
56
+ options.maxAge = Number.parseInt(value, 10);
57
+ continue;
58
58
  case 'domain':
59
59
  options.domain = value;
60
60
  break;
@@ -68,22 +68,22 @@ export default class CookieObject {
68
68
  options.httpOnly = true;
69
69
  break;
70
70
  case 'samesite':
71
- // @ts-ignore No cast
71
+ // @ts-expect-error No cast
72
72
  options.sameSite = value;
73
73
  break;
74
74
  default:
75
75
  }
76
- });
76
+ }
77
77
  return new CookieObject(options);
78
78
  }
79
79
 
80
80
  toString() {
81
81
  // eslint-disable-next-line prefer-template
82
82
  return (`${this.name ?? ''}=${this.value ?? ''}`)
83
- + (this.expires != null ? `; Expires=${this.expires.toUTCString()}` : '')
84
- + (this.maxAge != null ? `; Max-Age=${this.maxAge}` : '')
85
- + (this.domain != null ? `; Domain=${this.domain}` : '')
86
- + (this.path != null ? `; Path=${this.path}` : '')
83
+ + (this.expires == null ? '' : `; Expires=${this.expires.toUTCString()}`)
84
+ + (this.maxAge == null ? '' : `; Max-Age=${this.maxAge}`)
85
+ + (this.domain == null ? '' : `; Domain=${this.domain}`)
86
+ + (this.path == null ? '' : `; Path=${this.path}`)
87
87
  + (this.secure ? '; Secure' : '')
88
88
  + (this.httpOnly ? '; HttpOnly' : '')
89
89
  + (this.sameSite ? `; SameSite=${this.sameSite}` : '');
@@ -1,5 +1,3 @@
1
- /* eslint-disable import/prefer-default-export */
2
-
3
1
  /** @typedef {import('../lib/HttpHandler').default} HttpHandler */
4
2
  /** @typedef {import('http2').Http2SecureServer} Http2SecureServer */
5
3
 
@@ -8,17 +6,14 @@
8
6
  * @param {RegExp} [socketioPath] /^\/socket.io\//i
9
7
  * @return {void}
10
8
  */
11
- export function addHttp2Support(httpHandler, socketioPath = /^\/socket.io\//i) {
12
- const fn = httpHandler.handleHttp2Stream;
13
- /** @type {fn} */
14
- const newFunction = (...args) => {
15
- const headers = args[1];
9
+ export function addHttp2Support(httpHandler, socketioPath = /^\/socket\.io\//i) {
10
+ const previous = httpHandler.handleHttp2Stream;
11
+ // eslint-disable-next-line no-param-reassign, unicorn/prevent-abbreviations
12
+ httpHandler.handleHttp2Stream = (...args) => {
13
+ const [, headers] = args;
16
14
  if (headers?.[':path']?.match(socketioPath)) {
17
15
  return Promise.resolve(null);
18
16
  }
19
- return fn.call(httpHandler, ...args);
17
+ return previous.call(httpHandler, ...args);
20
18
  };
21
- // @ts-ignore
22
- // eslint-disable-next-line no-param-reassign
23
- httpHandler.handleHttp2Stream = newFunction;
24
19
  }
@@ -0,0 +1,11 @@
1
+ import * as starter from '../templates/starter.js';
2
+
3
+ const middleware = [
4
+ () => 'Hello world!',
5
+ ];
6
+
7
+ // Start the singleton HTTP server using the starter convenience API.
8
+ await starter.start({ middleware });
9
+
10
+ // Export nothing — this example file is intended as a tiny runnable demo.
11
+ export {};
@@ -23,10 +23,10 @@ export default class HeadersParser {
23
23
  * The `charset` direct of `Content-Type`.
24
24
  * The character encoding standard.
25
25
  * (Always lowercase)
26
- * @return {string} */
26
+ @return {string} */
27
27
  get charset() {
28
28
  let value = null;
29
- // eslint-disable-next-line no-unused-expressions
29
+
30
30
  this.contentType?.split(';').some((directive) => {
31
31
  const parameters = directive.split('=');
32
32
  if (parameters[0].trim().toLowerCase() !== 'charset') {
@@ -36,7 +36,7 @@ export default class HeadersParser {
36
36
  const firstQuote = value.indexOf('"');
37
37
  const lastQuote = value.lastIndexOf('"');
38
38
  if (firstQuote !== -1 && lastQuote !== -1) {
39
- value = value.substring(firstQuote + 1, lastQuote);
39
+ value = value.slice(firstQuote + 1, lastQuote);
40
40
  }
41
41
  return true;
42
42
  });
@@ -46,7 +46,7 @@ export default class HeadersParser {
46
46
  /** @return {string} */
47
47
  get boundary() {
48
48
  let value = null;
49
- // eslint-disable-next-line no-unused-expressions
49
+
50
50
  this.contentType?.split(';').some((directive) => {
51
51
  const parameters = directive.split('=');
52
52
  if (parameters[0].trim().toLowerCase() !== 'boundary') {
@@ -56,16 +56,15 @@ export default class HeadersParser {
56
56
  const firstQuote = value.indexOf('"');
57
57
  const lastQuote = value.lastIndexOf('"');
58
58
  if (firstQuote !== -1 && lastQuote !== -1) {
59
- value = value.substring(firstQuote + 1, lastQuote);
59
+ value = value.slice(firstQuote + 1, lastQuote);
60
60
  }
61
61
  return true;
62
62
  });
63
63
  return value;
64
64
  }
65
65
 
66
-
67
- /** @return {number} */
66
+ /** @return {?number} */
68
67
  get contentLength() {
69
- return parseInt(this.headers['content-length'], 10) || null;
68
+ return Number.parseInt(this.headers['content-length'], 10) || null;
70
69
  }
71
70
  }