webhoster 0.1.0 → 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.
- package/.eslintrc.json +74 -58
- package/.github/copilot-instructions.md +100 -0
- package/.github/workflows/test-matrix.yml +37 -0
- package/.test/benchmark.js +28 -0
- package/.test/constants.js +4 -0
- package/{test → .test}/http2server.js +1 -1
- package/{test → .test}/httpserver.js +1 -1
- package/{test → .test}/index.js +178 -192
- package/.test/multipromise.js +32 -0
- package/{test → .test}/tls.js +3 -3
- package/{test → .test}/urlencoded.js +3 -0
- package/.vscode/launch.json +24 -3
- package/README.md +116 -90
- package/data/CookieObject.js +14 -14
- package/errata/socketio.js +6 -11
- package/examples/starter.js +11 -0
- package/helpers/HeadersParser.js +7 -8
- package/helpers/HttpListener.js +387 -42
- package/helpers/RequestHeaders.js +43 -36
- package/helpers/RequestReader.js +27 -26
- package/helpers/ResponseHeaders.js +47 -36
- package/jsconfig.json +1 -1
- package/lib/HttpHandler.js +447 -277
- package/lib/HttpRequest.js +383 -39
- package/lib/HttpResponse.js +316 -52
- package/lib/HttpTransaction.js +146 -0
- package/middleware/AutoHeadersMiddleware.js +73 -0
- package/middleware/CORSMiddleware.js +45 -47
- package/middleware/CaseInsensitiveHeadersMiddleware.js +5 -11
- package/middleware/ContentDecoderMiddleware.js +81 -35
- package/middleware/ContentEncoderMiddleware.js +179 -132
- package/middleware/ContentLengthMiddleware.js +66 -43
- package/middleware/ContentWriterMiddleware.js +5 -11
- package/middleware/HashMiddleware.js +68 -40
- package/middleware/HeadMethodMiddleware.js +24 -22
- package/middleware/MethodMiddleware.js +29 -36
- package/middleware/PathMiddleware.js +49 -66
- package/middleware/ReadFormData.js +99 -0
- package/middleware/SendHeadersMiddleware.js +0 -2
- package/middleware/SendJsonMiddleware.js +131 -0
- package/middleware/SendStringMiddleware.js +87 -0
- package/package.json +38 -29
- package/polyfill/FormData.js +164 -0
- package/rollup.config.js +0 -1
- package/scripts/test-all-sync.sh +6 -0
- package/scripts/test-all.sh +7 -0
- package/templates/starter.js +53 -0
- package/test/fixtures/stream.js +68 -0
- package/test/helpers/HttpListener/construct.js +18 -0
- package/test/helpers/HttpListener/customOptions.js +22 -0
- package/test/helpers/HttpListener/doubleCreate.js +40 -0
- package/test/helpers/HttpListener/events.js +77 -0
- package/test/helpers/HttpListener/http.js +31 -0
- package/test/helpers/HttpListener/http2.js +41 -0
- package/test/helpers/HttpListener/https.js +38 -0
- package/test/helpers/HttpListener/startAll.js +31 -0
- package/test/helpers/HttpListener/stopNotStarted.js +23 -0
- package/test/lib/HttpHandler/class.js +8 -0
- package/test/lib/HttpHandler/handleRequest.js +11 -0
- package/test/lib/HttpHandler/middleware.js +941 -0
- package/test/lib/HttpHandler/parse.js +41 -0
- package/test/lib/HttpRequest/class.js +8 -0
- package/test/lib/HttpRequest/downstream.js +171 -0
- package/test/lib/HttpRequest/properties.js +101 -0
- package/test/lib/HttpRequest/read.js +518 -0
- package/test/lib/HttpResponse/class.js +8 -0
- package/test/lib/HttpResponse/properties.js +59 -0
- package/test/lib/HttpResponse/send.js +275 -0
- package/test/lib/HttpTransaction/class.js +8 -0
- package/test/lib/HttpTransaction/ping.js +50 -0
- package/test/lib/HttpTransaction/push.js +89 -0
- package/test/middleware/SendJsonMiddleware.js +222 -0
- package/test/sanity.js +10 -0
- package/test/templates/starter.js +93 -0
- package/tsconfig.json +12 -0
- package/types/index.js +61 -34
- package/types/typings.d.ts +8 -9
- package/utils/AsyncObject.js +6 -3
- package/utils/CaseInsensitiveObject.js +2 -3
- package/utils/function.js +1 -7
- package/utils/headers.js +42 -0
- package/utils/qualityValues.js +1 -1
- package/utils/stream.js +4 -20
- package/index.cjs +0 -3200
- package/test/constants.js +0 -4
- /package/{test → .test}/cookietester.js +0 -0
package/.eslintrc.json
CHANGED
|
@@ -1,79 +1,95 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": ["airbnb-base", "plugin:node/recommended"],
|
|
3
|
-
"plugins": ["jsdoc", "babel"],
|
|
4
2
|
"env": {
|
|
5
|
-
"
|
|
3
|
+
"es2021": true
|
|
6
4
|
},
|
|
7
|
-
"
|
|
5
|
+
"extends": [
|
|
6
|
+
"airbnb-base",
|
|
7
|
+
"plugin:@typescript-eslint/recommended",
|
|
8
|
+
"plugin:jsdoc/recommended-typescript-flavor-error",
|
|
9
|
+
"plugin:n/recommended",
|
|
10
|
+
"plugin:unicorn/recommended",
|
|
11
|
+
"plugin:import/recommended"
|
|
12
|
+
],
|
|
13
|
+
"overrides": [
|
|
14
|
+
{
|
|
15
|
+
"files": [
|
|
16
|
+
"*.cjs"
|
|
17
|
+
],
|
|
18
|
+
"rules": {
|
|
19
|
+
"@typescript-eslint/no-var-requires": 0,
|
|
20
|
+
"import/no-nodejs-modules": 0
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"parser": "@typescript-eslint/parser",
|
|
8
25
|
"parserOptions": {
|
|
9
|
-
"ecmaVersion": 2020,
|
|
10
|
-
"sourceType": "module",
|
|
11
26
|
"ecmaFeatures": {
|
|
12
27
|
"impliedStrict": true
|
|
13
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"ecmaVersion": 2019,
|
|
30
|
+
"project": "./jsconfig.json",
|
|
31
|
+
"sourceType": "module"
|
|
14
32
|
},
|
|
33
|
+
"plugins": [
|
|
34
|
+
"@typescript-eslint",
|
|
35
|
+
"canonical",
|
|
36
|
+
"jsdoc",
|
|
37
|
+
"n",
|
|
38
|
+
"unicorn",
|
|
39
|
+
"import"
|
|
40
|
+
],
|
|
41
|
+
"root": true,
|
|
15
42
|
"rules": {
|
|
16
|
-
"import/extensions": [
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}],
|
|
23
|
-
"import/prefer-default-export": "off",
|
|
24
|
-
"import/no-anonymous-default-export": ["error"],
|
|
25
|
-
"prefer-destructuring": ["error", {
|
|
26
|
-
"VariableDeclarator": {
|
|
27
|
-
"array": false,
|
|
28
|
-
"object": true
|
|
29
|
-
},
|
|
30
|
-
"AssignmentExpression": {
|
|
31
|
-
"array": false,
|
|
32
|
-
"object": true
|
|
43
|
+
"import/extensions": [
|
|
44
|
+
"error",
|
|
45
|
+
"always",
|
|
46
|
+
{
|
|
47
|
+
"js": "always",
|
|
48
|
+
"mjs": "never"
|
|
33
49
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
],
|
|
51
|
+
"import/no-extraneous-dependencies": "off",
|
|
52
|
+
"import/prefer-default-export": "off",
|
|
53
|
+
"jsdoc/no-defaults": "off",
|
|
54
|
+
"jsdoc/require-param-description": "off",
|
|
55
|
+
"jsdoc/require-property-description": "off",
|
|
56
|
+
"jsdoc/require-returns": "off",
|
|
57
|
+
"jsdoc/require-returns-description": "off",
|
|
58
|
+
"no-continue": "off",
|
|
59
|
+
"no-restricted-syntax": "off",
|
|
60
|
+
"no-return-await": "off",
|
|
61
|
+
"unicorn/explicit-length-check": "off",
|
|
62
|
+
"unicorn/filename-case": "off",
|
|
63
|
+
"unicorn/no-null": "off",
|
|
64
|
+
"unicorn/no-useless-switch-case": "off",
|
|
65
|
+
"unicorn/no-useless-undefined": "off",
|
|
66
|
+
"unicorn/prefer-ternary": [
|
|
67
|
+
"error",
|
|
68
|
+
"only-single-line"
|
|
69
|
+
],
|
|
70
|
+
"unicorn/prevent-abbreviations": [
|
|
71
|
+
"error",
|
|
72
|
+
{
|
|
73
|
+
"checkFilenames": false
|
|
53
74
|
}
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"jsdoc/no-undefined-types": 0,
|
|
58
|
-
"jsdoc/valid-types": 0,
|
|
59
|
-
"jsdoc/newline-after-description": ["warn", "never"],
|
|
60
|
-
"jsdoc/require-returns": ["warn", {
|
|
61
|
-
"forceReturnsWithAsync": true,
|
|
62
|
-
"forceRequireReturn": true
|
|
63
|
-
}]
|
|
75
|
+
],
|
|
76
|
+
"unicorn/switch-case-braces": "off",
|
|
77
|
+
"unicorn/text-encoding-identifier-case": "off"
|
|
64
78
|
},
|
|
65
79
|
"settings": {
|
|
66
80
|
"jsdoc": {
|
|
67
81
|
"preferredTypes": {
|
|
68
|
-
"
|
|
69
|
-
"object
|
|
70
|
-
"object": "Object"
|
|
82
|
+
"array": "Array",
|
|
83
|
+
"object": "Object",
|
|
84
|
+
"object.": "Object<>",
|
|
85
|
+
"object<>": "Object<>",
|
|
86
|
+
"symbol": "Symbol"
|
|
71
87
|
},
|
|
72
88
|
"tagNamePreference": {
|
|
73
89
|
"augment": "extends",
|
|
74
|
-
"
|
|
90
|
+
"constant": "const",
|
|
75
91
|
"property": "prop",
|
|
76
|
-
"
|
|
92
|
+
"returns": "return"
|
|
77
93
|
}
|
|
78
94
|
}
|
|
79
95
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!-- Auto-generated: guidance for AI coding assistants working on this repo -->
|
|
2
|
+
# Copilot / AI assistant instructions for webhoster
|
|
3
|
+
|
|
4
|
+
This file gives focused, actionable guidance to quickly become productive in this repository.
|
|
5
|
+
|
|
6
|
+
Overview
|
|
7
|
+
- **Purpose**: `webhoster` is a stream-first, middleware-driven HTTP/HTTP2 framework for Node.js. Core classes live in `lib/` (notably `lib/HttpHandler.js`, `lib/HttpRequest.js`, `lib/HttpResponse.js`). Middleware implementations live in `middleware/` and are composed into the `HttpHandler` middleware tree.
|
|
8
|
+
- **Module type**: The project is ESM (`"type": "module"` in `package.json`) and targets Node >v16.13.
|
|
9
|
+
|
|
10
|
+
Key patterns and conventions
|
|
11
|
+
- **Middleware tree**: Middleware can be a function, a filter, an Iterable (e.g. `Set`), or a `Map`. Branching is represented by nested arrays/iterables. See examples in `README.md` and `middleware/` files.
|
|
12
|
+
- **Return values**: Middleware return values drive control flow. Use the `HttpHandler` constants or their aliases:
|
|
13
|
+
- `HttpHandler.CONTINUE` (or `true`, `undefined`, `null`) — continue the branch
|
|
14
|
+
- `HttpHandler.BREAK` (or `false`) — break this branch, move to next
|
|
15
|
+
- `HttpHandler.END` (or `0`) — terminate the middleware tree
|
|
16
|
+
- `number` — treated as an HTTP status code and ends
|
|
17
|
+
- **I/O model**: Requests expose `.read()`, `.stream`, and `.body` helpers. Responses expose `.stream`, `.end()`, `.send()`, `.pipeFrom()` and may call `.pushPath()` for HTTP/2 pushes. See `lib/HttpRequest.js` and `lib/HttpResponse.js`.
|
|
18
|
+
- **Locals**: Per-request state belongs on `request.locals` (or `locals`) and is passed through middleware.
|
|
19
|
+
|
|
20
|
+
Developer workflows
|
|
21
|
+
- **Run tests**: `npm test` (uses `c8` + `ava` for coverage). Unit tests live under `test/` (pattern: `test/**/*.js`, excluding `test/fixtures/**`).
|
|
22
|
+
- **Run full/test-all scripts**: `npm run testall` (async) or `npm run testallsync` (sync). These call the scripts in `scripts/`.
|
|
23
|
+
- **Debug tests**: `npm run debug-test` which runs `ava --serial`.
|
|
24
|
+
- **Coverage**: Coverage created by `c8`; `posttestall`/`posttestallsync` call `c8 report`.
|
|
25
|
+
|
|
26
|
+
Code style & types
|
|
27
|
+
- Project uses `eslint` (Airbnb base + plugins) in devDependencies — follow existing style. There are TypeScript type hints (`types/typings.d.ts`) but the codebase is JavaScript ESM.
|
|
28
|
+
|
|
29
|
+
Where to change behavior
|
|
30
|
+
- Add or alter middleware in `middleware/`. Prefer small, single-responsibility modules: decoding, encoding, header transforms, routing filters (see `PathMiddleware.js`, `MethodMiddleware.js`).
|
|
31
|
+
- Core runtime behavior is in `lib/HttpHandler.js`. Changes here affect branching, error handling, and how middleware results are interpreted.
|
|
32
|
+
|
|
33
|
+
Testing & examples
|
|
34
|
+
- Example middleware composition is in `README.md` and `test/index.js` — use those as canonical examples when adding new middleware or behavior.
|
|
35
|
+
- Write tests under `test/` and avoid touching `test/fixtures/` unless updating fixture data. Use `ava` and follow existing test styles.
|
|
36
|
+
\
|
|
37
|
+
Example: minimal middleware
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
// middleware/ExampleMiddleware.js
|
|
41
|
+
export default class ExampleMiddleware {
|
|
42
|
+
execute(transaction) {
|
|
43
|
+
transaction.locals = transaction.locals || {};
|
|
44
|
+
transaction.locals.example = (transaction.locals.example || 0) + 1;
|
|
45
|
+
return true; // continue processing
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Test example (AVA):
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import test from 'ava';
|
|
54
|
+
import ExampleMiddleware from '../middleware/ExampleMiddleware.js';
|
|
55
|
+
|
|
56
|
+
test('ExampleMiddleware increments locals', (t) => {
|
|
57
|
+
const m = new ExampleMiddleware();
|
|
58
|
+
const transaction = { locals: {}, state: { treeIndex: [] }, request: {}, response: {} };
|
|
59
|
+
m.execute(transaction);
|
|
60
|
+
t.is(transaction.locals.example, 1);
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
I added `middleware/ExampleMiddleware.js` and `test/example-middleware.js` to the repo as a runnable template. Run `npm test` to verify the example.
|
|
65
|
+
|
|
66
|
+
Integration points & runtime
|
|
67
|
+
- Typical integration: a server registers handlers:
|
|
68
|
+
- `http1Server.addListener('request', HttpHandler.defaultInstance.handleHttp1Request)`
|
|
69
|
+
- `http2Server.addListener('stream', HttpHandler.defaultInstance.handleHttp2Stream)`
|
|
70
|
+
- Middleware may push HTTP/2 resources using `response.pushPath()` — guard with `if (response.canPushPath)`.
|
|
71
|
+
|
|
72
|
+
Practical tips for PRs and edits
|
|
73
|
+
- Keep changes small and focused. Prefer adding middleware modules over modifying `HttpHandler` unless implementing a framework-level feature.
|
|
74
|
+
- Preserve ESM syntax and `export`/`import` styles. Respect the project's `node` engine minimum.
|
|
75
|
+
- Add or update `test/*.js` for any behavioral change; run `npm test` locally to confirm.
|
|
76
|
+
|
|
77
|
+
Files to inspect when in doubt
|
|
78
|
+
- `lib/HttpHandler.js`, `lib/HttpRequest.js`, `lib/HttpResponse.js`
|
|
79
|
+
- `middleware/*` for existing middleware primitives
|
|
80
|
+
- `test/index.js` and `test/**` for test patterns and usage examples
|
|
81
|
+
- `scripts/test-all.sh` and `scripts/test-all-sync.sh` for CI/test orchestration
|
|
82
|
+
|
|
83
|
+
CI / Workflows
|
|
84
|
+
- `/.github/workflows/test-matrix.yml`: the recommended workflow. Runs `npm ci` + `npm run test` across a Node matrix (`16.13`, `16`, `18`, `20`, `22`) using `actions/setup-node`. Prefer editing this file when you want to change supported Node versions or add reporting steps (Codecov/Upload). Each matrix job uses the display name `Test on Node <version>`.
|
|
85
|
+
- `/.github/workflows/test-with-nvm.yml`: optional workflow that installs `nvm` on the runner and executes `scripts/test-all.sh` to mirror local developer behavior. Keep this for parity/debugging only; the matrix job is preferred for CI.
|
|
86
|
+
|
|
87
|
+
README badges
|
|
88
|
+
- `README.md` contains per-node badges that point to the `test-matrix.yml` workflow and the job names. If you rename the workflow or change the job display name, update the badges in `README.md` accordingly. Badges point to the `master` branch by default.
|
|
89
|
+
|
|
90
|
+
Local & debugging notes
|
|
91
|
+
- Run tests locally (fast):
|
|
92
|
+
- `npm ci`
|
|
93
|
+
- `npm test` (runs `c8 ava`)
|
|
94
|
+
- Run the full multi-version script locally (requires `nvm`):
|
|
95
|
+
- `chmod +x scripts/test-all.sh`
|
|
96
|
+
- `./scripts/test-all.sh`
|
|
97
|
+
- The `scripts/test-all.sh` script runs `nvm install`/`nvm use` for multiple Node versions and then executes `npx c8 --clean false -r none ava` for each version in parallel. A recent fix corrected a typo so the Node 22 step now correctly runs `nvm use 22`.
|
|
98
|
+
|
|
99
|
+
If you need more
|
|
100
|
+
- If anything here is unclear or you'd like more examples (specific middleware templates, test scaffolds, or a debugging recipe), ask and I'll add them.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Test matrix
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["master"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["master"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test on Node ${{ matrix.node-version }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
matrix:
|
|
15
|
+
node-version: ["16.13", "16", "18", "20", "22"]
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Setup Node
|
|
21
|
+
uses: actions/setup-node@v4
|
|
22
|
+
with:
|
|
23
|
+
node-version: ${{ matrix.node-version }}
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: npm run test
|
|
31
|
+
|
|
32
|
+
- name: Upload coverage artifact
|
|
33
|
+
if: always()
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: coverage-${{ matrix.node-version }}
|
|
37
|
+
path: coverage
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import HttpListener from '../helpers/HttpListener.js';
|
|
2
|
+
import HttpHandler from '../lib/HttpHandler.js';
|
|
3
|
+
import AutoHeadersMiddleware from '../middleware/AutoHeadersMiddleware.js';
|
|
4
|
+
import SendJsonMiddleware from '../middleware/SendJsonMiddleware.js';
|
|
5
|
+
import ContentLengthMiddleware from '../middleware/ContentLengthMiddleware.js';
|
|
6
|
+
|
|
7
|
+
const { middleware } = HttpHandler.defaultInstance;
|
|
8
|
+
middleware.push(
|
|
9
|
+
// new HeadMethodMiddleware(), // Discard body content
|
|
10
|
+
new SendJsonMiddleware(),
|
|
11
|
+
// new ContentLengthMiddleware({ delayCycle: false }), // Calculate length of anything after
|
|
12
|
+
// new AutoHeadersMiddleware(), // Send headers automatically
|
|
13
|
+
// new HashMiddleware(), // Hash anything after
|
|
14
|
+
// new ContentEncoderMiddleware(), // Compress anything after
|
|
15
|
+
// new ContentWriterMiddleware({ setCharset: true, setJSON: true }),
|
|
16
|
+
// new ContentDecoderMiddleware(),
|
|
17
|
+
// Automatically reads text, JSON, and form-url-encoded from requests
|
|
18
|
+
// new ContentReaderMiddleware({
|
|
19
|
+
// buildString: true,
|
|
20
|
+
// defaultMediaType: 'application/json',
|
|
21
|
+
// parseJSON: true,
|
|
22
|
+
// formURLEncodedFormat: 'object',
|
|
23
|
+
// }),
|
|
24
|
+
{ hello: "world" },
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const listener = new HttpListener({ insecurePort: 3000 });
|
|
28
|
+
listener.startAll();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const HTTP_PORT = Number.parseInt(process.env.HTTP_PORT || process.env.PORT || '8080', 10);
|
|
2
|
+
export const HTTPS_PORT = Number.parseInt(process.env.HTTPS_PORT || '8443', 10);
|
|
3
|
+
export const HTTP_HOST = process.env.HTTP_HOST || process.env.HOST || '0.0.0.0';
|
|
4
|
+
export const HTTPS_HOST = process.env.HTTPS_HOST || process.env.HOST || '0.0.0.0';
|