@superhero/http-server 4.0.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/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Erik Landvall
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,451 @@
1
+
2
+ # HTTP-server
3
+
4
+ An HTTP server module for Node.js that supports both HTTP/1.1 and HTTP/2 protocols, with built-in routing, HTTPS support, and stream support that defaults to server-sent events (SSE). Designed to be robust, flexible and extendible, while easy to work with.
5
+
6
+ ## Table of Contents
7
+
8
+ - [Installation](#installation)
9
+ - [Getting Started](#getting-started)
10
+ - [Usage](#usage)
11
+ - [Basic Example](#basic-example)
12
+ - [HTTPS Setup with Self-Signed Certificate](#https-setup-with-self-signed-certificate)
13
+ - [Altering Response Body, Headers, and Status](#altering-response-body-headers-and-status)
14
+ - [Handling Aborted Requests](#handling-aborted-requests)
15
+ - [Streaming Server-Sent Events (SSE)](#streaming-server-sent-events-sse)
16
+ - [Custom Logging](#custom-logging)
17
+ - [API](#api)
18
+ - [`HttpServer`](#httpserver)
19
+ - [`session.view`](#sessionview)
20
+ - [`session.abortion`](#sessionabortion)
21
+ - [Testing](#testing)
22
+ - [Coverage Report](#coverage-report)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ## Installation
27
+
28
+ Install the package using npm:
29
+
30
+ ```bash
31
+ npm install @superhero/http-server
32
+ ```
33
+
34
+ ## Getting Started
35
+
36
+ The `@superhero/http-server` module integrates with the `@superhero/locator` and `@superhero/router` modules to provide a flexible and modular HTTP server.
37
+
38
+ To get started, you'll need to set up a `Locator` instance, register your dispatchers, and then locate the `HttpServer` module using the `locator`.
39
+
40
+ ## Usage
41
+
42
+ ### Basic Example
43
+
44
+ ```javascript
45
+ import HttpServer from '@superhero/http-server';
46
+ import Locator from '@superhero/locator';
47
+ import Router from '@superhero/router';
48
+
49
+ // Instantiate the service locator
50
+ const locator = new Locator();
51
+
52
+ // Instantiate the router
53
+ const router = new Router(locator);
54
+
55
+ // Instantiate the server
56
+ const server = HttpServer(route);
57
+
58
+ // Register the route dispatcher service
59
+ locator.set('hello-dispatcher', {
60
+ dispatch: (request, session) => {
61
+ session.view.body.message = 'Hello, World!';
62
+ },
63
+ });
64
+
65
+ // Routes
66
+ const settings = {
67
+ router: {
68
+ routes: {
69
+ hello: {
70
+ criteria: '/hello',
71
+ dispatcher: 'hello-dispatcher',
72
+ },
73
+ },
74
+ },
75
+ };
76
+
77
+ // Bootstrap and start the server
78
+ await server.bootstrap(settings);
79
+ await server.listen(3000);
80
+ ```
81
+
82
+ **Explanation:**
83
+
84
+ - **Import Statements**: We instantiate the required components `locator`, `router` and `server`.
85
+ - **Dispatcher Registration**: Register a dispatcher called `'hello-dispatcher'` in the locator.
86
+ - **Server Settings**: Define the routes, and possible other server configurations, in the `settings` object.
87
+ - **Bootstrap and Listen**: Bootstrap the server with the settings and start listening on port `3000`.
88
+ - **Ready to serve requests**: Request to `http://localhost:3000/hello` will reply `{ "message": "Hello, World!" }`.
89
+
90
+ ### HTTPS Setup with Self-Signed Certificate
91
+
92
+ ```javascript
93
+ import fs from 'node:fs';
94
+ import Locator from '@superhero/locator';
95
+
96
+ // Instantiate the service locator
97
+ const locator = new Locator();
98
+
99
+ // Locate the server
100
+ const server = await locator.lazyload('@superhero/http-server');
101
+
102
+ // Register necessary services
103
+ locator.set('secure-dispatcher', {
104
+ dispatch: (request, session) => {
105
+ session.view.body = { message: 'Secure Hello, World!' };
106
+ },
107
+ });
108
+
109
+ // Server settings and routes
110
+ const serverSettings = {
111
+ server: {
112
+ key: fs.readFileSync('path/to/private.key'),
113
+ cert: fs.readFileSync('path/to/server.cert'),
114
+ },
115
+ router: {
116
+ routes: {
117
+ secure: {
118
+ criteria: '/secure',
119
+ dispatcher: 'secure-dispatcher',
120
+ },
121
+ },
122
+ },
123
+ };
124
+
125
+ await server.bootstrap(serverSettings);
126
+ await server.listen(443);
127
+ ```
128
+
129
+ > [!NOTE]
130
+ > Replace `'path/to/private.key'` and `'path/to/server.cert'` with the actual paths to your SSL key and certificate files.
131
+
132
+ ### Altering Response Body, Headers, and Status
133
+
134
+ ```javascript
135
+ locator.set('custom-dispatcher', {
136
+ dispatch: (request, session) => {
137
+ session.view.body = { data: 'Custom Data' };
138
+ session.view.headers['Custom-Header'] = 'CustomValue';
139
+ session.view.status = 201; // HTTP 201 Created
140
+ },
141
+ });
142
+
143
+ // Update the routes in the settings
144
+ const settings = {
145
+ router: {
146
+ routes: {
147
+ custom: {
148
+ criteria: '/custom',
149
+ dispatcher: 'custom-dispatcher',
150
+ },
151
+ },
152
+ },
153
+ };
154
+
155
+ // Bootstrap and start the server
156
+ await server.bootstrap(settings);
157
+ await server.listen(3000);
158
+ ```
159
+
160
+ ### Handling Aborted Requests
161
+
162
+ ```javascript
163
+ locator.set('abort-dispatcher', {
164
+ dispatch: (request, session) => {
165
+ // Abort the request with a custom error
166
+ const error = new Error('Request Aborted');
167
+ error.code = 'E_REQUEST_ABORTED';
168
+ session.abortion.abort(error);
169
+ },
170
+ });
171
+
172
+ // Update the routes in the settings
173
+ const settings = {
174
+ router: {
175
+ routes: {
176
+ abort: {
177
+ criteria: '/abort',
178
+ dispatcher: 'abort-dispatcher',
179
+ },
180
+ },
181
+ },
182
+ };
183
+
184
+ // Bootstrap and start the server
185
+ await server.bootstrap(settings);
186
+ await server.listen(3000);
187
+ ```
188
+
189
+ > [!NOTE]
190
+ > Will result in a `status 500` response `{ "error": "Request Aborted", "code": "E_REQUEST_ABORTED" }`
191
+
192
+ ### Streaming Server-Sent Events (SSE)
193
+
194
+ ```javascript
195
+ locator.set('sse-dispatcher', {
196
+ dispatch: (request, session) => {
197
+ // Write events to the stream
198
+ session.view.stream.write({ data: 'First message' });
199
+ session.view.stream.write({ data: 'Second message' });
200
+
201
+ // End the stream
202
+ session.view.stream.end();
203
+ },
204
+ });
205
+
206
+ // Update the routes in the settings
207
+ const settings = {
208
+ router: {
209
+ routes: {
210
+ sse: {
211
+ criteria: '/sse',
212
+ dispatcher: 'sse-dispatcher',
213
+ },
214
+ },
215
+ },
216
+ };
217
+
218
+ // Bootstrap and start the server
219
+ await server.bootstrap(settings);
220
+ await server.listen(3000);
221
+ ```
222
+
223
+ > [!NOTE]
224
+ > By default responds with a `text/event-stream` content type:
225
+ > ```
226
+ > data: { "data": "First message" }
227
+ >
228
+ > data: { "data": "Second message" }
229
+ > ```
230
+
231
+ ### Custom Logging
232
+
233
+ You can override the default logging methods to integrate with your logging system.
234
+
235
+ #### Turn Off Info Logs
236
+
237
+ ```javascript
238
+ server.log.info = () => null;
239
+ ```
240
+
241
+ #### Custom Error Logging
242
+
243
+ ```javascript
244
+ server.log.error = (error) => {
245
+ // TODO: custom error logging logic...
246
+ };
247
+ ```
248
+
249
+ #### Turn Off Log Colors
250
+
251
+ By default, the logger renders a colored output.
252
+
253
+ ```javascript
254
+ server.log.format = server.log.simple;
255
+ ```
256
+
257
+ ## API
258
+
259
+ ### `HttpServer`
260
+
261
+ The main class responsible for handling HTTP requests.
262
+
263
+ - **Constructor**: The server can be instantiated or located via the `Locator`.
264
+ - Use `locator.locate('@superhero/http-server')` to get an instance.
265
+
266
+ - **Methods**:
267
+ - `async bootstrap(settings)`: Bootstraps the server with the provided settings.
268
+ - `settings`: An object containing server and router configurations.
269
+ - `async listen(port)`: Starts the server on the specified port.
270
+ - `port`: The port number to listen on.
271
+ - `async close()`: Closes the server and all active sessions.
272
+
273
+ ### `request`
274
+
275
+ An object used to read
276
+
277
+ - **Properties**:
278
+ - `body`: The request body (Promise).
279
+ - `method`: The request HTTP method.
280
+ - `headers`: The request HTTP headers.
281
+ - `url`: The requested URL.
282
+
283
+ ### `session.view`
284
+
285
+ An object used within dispatchers to manipulate the response.
286
+
287
+ - **Properties**:
288
+ - `body`: The response body to be sent to the client.
289
+ - `headers`: An object containing response headers.
290
+ - `status`: HTTP status code of the response.
291
+ - `stream`: A writable stream for sending SSE data, or to be configured to stream some other type of response to the client.
292
+
293
+ ### `session.abortion`
294
+
295
+ An `AbortController` used to trigger and manage dispatch abortion.
296
+
297
+ - **Methods**:
298
+ - `abort(error)`: Aborts the request with the provided error.
299
+
300
+ ## Testing
301
+
302
+ The test suite uses Node.js's built-in testing module.
303
+
304
+ ### Running Tests
305
+
306
+ To run the tests, execute:
307
+
308
+ ```bash
309
+ npm test
310
+ ```
311
+
312
+ ### Test Coverage
313
+
314
+ ```
315
+ ▶ @superhero/http-server
316
+ ▶ Lifecycle
317
+ ✔ Can instantiate HttpServer (8.419758ms)
318
+ ✔ Can bootstrap server with non-secure settings (2.635146ms)
319
+ ✔ Listens and closes the server as expected (3.349393ms)
320
+ ✔ Rejects if server is not available to listen error (2.108227ms)
321
+ ✔ Rejects if server is not available to close error (1.792574ms)
322
+ ✔ Lifecycle (19.74534ms)
323
+
324
+ ▶ Routing and Requests
325
+ ▶ HTTP/1
326
+ ✔ Can dispatch a request aligned to the route map (39.125646ms)
327
+ ✔ Can alter the output body (5.52864ms)
328
+ ✔ Can stream HTML5 standard Server-Sent Events (SSE) (7.515151ms)
329
+ ✔ Can alter the output headers (6.829782ms)
330
+ ✔ Can alter the output status (5.509801ms)
331
+ ✔ Can abort the dispatcher (6.059604ms)
332
+ ✔ Can describe an abortion in detail (6.212658ms)
333
+ ✔ Can manage thrown errors in the dispatcher (6.902106ms)
334
+ ✔ Can not mistakenly access the wrong view property (4.354173ms)
335
+ ✔ Can not mistakenly assign a value to the wrong view property (7.322497ms)
336
+ ✔ Support connection keep-alive header (6.339005ms)
337
+ ✔ HTTP/1 (103.550623ms)
338
+
339
+ ▶ HTTP/2
340
+ ✔ Can dispatch a request aligned to the route map (67.303149ms)
341
+ ✔ Can alter the output body (6.051175ms)
342
+ ✔ Can stream HTML5 standard Server-Sent Events (SSE) (5.646976ms)
343
+ ✔ Can alter the output headers (4.816813ms)
344
+ ✔ Can alter the output status (8.330617ms)
345
+ ✔ Can abort the dispatcher (6.803324ms)
346
+ ✔ Can describe an abortion in detail (4.771987ms)
347
+ ✔ Can manage thrown errors in the dispatcher (6.57489ms)
348
+ ✔ Can not mistakenly access the wrong view property (4.451118ms)
349
+ ✔ Can not mistakenly assign a value to the wrong view property (4.587527ms)
350
+ ✔ HTTP/2 (120.216844ms)
351
+ ✔ Routing and Requests (224.037442ms)
352
+
353
+ ▶ HTTPS server with self-signed certificate
354
+ ▶ TLSv1.2
355
+ ▶ RSA:2048
356
+ ✔ HTTP1 (9.766994ms)
357
+ ✔ HTTP2 (10.784857ms)
358
+ ✔ RSA:2048 (157.12133ms)
359
+
360
+ ▶ RSA:4096
361
+ ✔ HTTP1 (11.566225ms)
362
+ ✔ HTTP2 (18.136774ms)
363
+ ✔ RSA:4096 (581.128109ms)
364
+
365
+ ▶ ECDSA:P-256
366
+ ✔ HTTP1 (5.324231ms)
367
+ ✔ HTTP2 (8.312658ms)
368
+ ✔ ECDSA:P-256 (50.979123ms)
369
+
370
+ ▶ ECDSA:P-384
371
+ ✔ HTTP1 (6.277003ms)
372
+ ✔ HTTP2 (9.918662ms)
373
+ ✔ ECDSA:P-384 (52.076847ms)
374
+
375
+ ▶ ECDSA:P-521
376
+ ✔ HTTP1 (10.988173ms)
377
+ ✔ HTTP2 (13.745049ms)
378
+ ✔ ECDSA:P-521 (63.762337ms)
379
+
380
+ ▶ EdDSA:Ed25519
381
+ ✔ HTTP1 (4.940083ms)
382
+ ✔ HTTP2 (8.791915ms)
383
+ ✔ EdDSA:Ed25519 (48.717009ms)
384
+
385
+ ▶ EdDSA:Ed448
386
+ ✔ HTTP1 (6.589414ms)
387
+ ✔ HTTP2 (8.132894ms)
388
+ ✔ EdDSA:Ed448 (50.727502ms)
389
+ ✔ TLSv1.2 (1005.148618ms)
390
+
391
+ ▶ TLSv1.3
392
+ ▶ RSA:2048
393
+ ✔ HTTP1 (6.038652ms)
394
+ ✔ HTTP2 (8.748363ms)
395
+ ✔ RSA:2048 (119.601474ms)
396
+
397
+ ▶ RSA:4096
398
+ ✔ HTTP1 (12.785668ms)
399
+ ✔ HTTP2 (14.531181ms)
400
+ ✔ RSA:4096 (622.520543ms)
401
+
402
+ ▶ ECDSA:P-256
403
+ ✔ HTTP1 (6.356325ms)
404
+ ✔ HTTP2 (10.260146ms)
405
+ ✔ ECDSA:P-256 (59.91212ms)
406
+
407
+ ▶ ECDSA:P-384
408
+ ✔ HTTP1 (8.192784ms)
409
+ ✔ HTTP2 (16.138147ms)
410
+ ✔ ECDSA:P-384 (66.214344ms)
411
+
412
+ ▶ ECDSA:P-521
413
+ ✔ HTTP1 (9.829523ms)
414
+ ✔ HTTP2 (14.905145ms)
415
+ ✔ ECDSA:P-521 (71.622241ms)
416
+
417
+ ▶ EdDSA:Ed25519
418
+ ✔ HTTP1 (6.453652ms)
419
+ ✔ HTTP2 (6.992268ms)
420
+ ✔ EdDSA:Ed25519 (50.780468ms)
421
+
422
+ ▶ EdDSA:Ed448
423
+ ✔ HTTP1 (5.421677ms)
424
+ ✔ HTTP2 (7.588945ms)
425
+ ✔ EdDSA:Ed448 (49.520972ms)
426
+ ✔ TLSv1.3 (1040.826701ms)
427
+ ✔ HTTPS server with self-signed certificate (2046.10023ms)
428
+ ✔ @superhero/http-server (2290.563163ms)
429
+
430
+ tests 68
431
+ suites 8
432
+ pass 68
433
+
434
+ --------------------------------------------------------------------------------------------------------------
435
+ file | line % | branch % | funcs % | uncovered lines
436
+ --------------------------------------------------------------------------------------------------------------
437
+ index.js | 91.75 | 91.18 | 74.07 | 92-94 128-129 135-137 266-269 369-373 389-394 397-402 405-410
438
+ index.test.js | 100.00 | 100.00 | 100.00 |
439
+ view.js | 92.98 | 88.89 | 84.21 | 133-138 196-200 238-239 247-253
440
+ --------------------------------------------------------------------------------------------------------------
441
+ all files | 95.31 | 93.68 | 86.61 |
442
+ --------------------------------------------------------------------------------------------------------------
443
+ ```
444
+
445
+ ## License
446
+
447
+ This project is licensed under the MIT License.
448
+
449
+ ## Contributing
450
+
451
+ Feel free to submit issues or pull requests for improvements or additional features.
package/config.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "bootstrap":
3
+ {
4
+ "@superhero/http-server": true
5
+ },
6
+ "locator":
7
+ {
8
+ "@superhero/http-server": true
9
+ },
10
+ "http-server":
11
+ {
12
+ "server": {},
13
+ "router":
14
+ {
15
+ "routes": {}
16
+ }
17
+ }
18
+ }