@thi.ng/server 0.7.1 → 0.8.1
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/CHANGELOG.md +10 -1
- package/README.md +5 -2
- package/api.d.ts +9 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interceptors/reject-useragent.d.ts +21 -0
- package/interceptors/reject-useragent.js +21 -0
- package/package.json +23 -20
- package/server.d.ts +72 -0
- package/server.js +89 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-03-
|
|
3
|
+
- **Last updated**: 2025-03-17T13:40:35Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,15 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [0.8.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.8.0) (2025-03-13)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- add method adapter, update `RequestCtx` ([360abda](https://github.com/thi-ng/umbrella/commit/360abda))
|
|
19
|
+
- add `ServerOpts.method` to allow for method conversion
|
|
20
|
+
- add `rejectUserAgents()` interceptor ([de0c373](https://github.com/thi-ng/umbrella/commit/de0c373))
|
|
21
|
+
- add UA presets for AI bots & scrapers
|
|
22
|
+
|
|
14
23
|
## [0.7.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/server@0.7.0) (2025-03-09)
|
|
15
24
|
|
|
16
25
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!-- This file is generated - DO NOT EDIT! -->
|
|
2
2
|
<!-- Please see: https://github.com/thi-ng/umbrella/blob/develop/CONTRIBUTING.md#changes-to-readme-files -->
|
|
3
|
-
# 
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@thi.ng/server)
|
|
6
6
|

|
|
@@ -80,6 +80,7 @@ for more details.
|
|
|
80
80
|
- [`logResponse()`](https://docs.thi.ng/umbrella/server/functions/logResponse.html): Response logging
|
|
81
81
|
- [`rateLimiter()`](https://docs.thi.ng/umbrella/server/functions/rateLimiter-1.html): Configurable rate limiting
|
|
82
82
|
- [`referrerPolicy()`](https://docs.thi.ng/umbrella/server/functions/referrerPolicy-1.html): Policy header injection
|
|
83
|
+
- [`rejectUserAgents()`](https://docs.thi.ng/umbrella/server/functions/rejectUserAgents.html): Configurable UA blocking
|
|
83
84
|
- [`sessionInterceptor()`](https://docs.thi.ng/umbrella/server/functions/sessionInterceptor-1.html): User defined in-memory sessions with TTL
|
|
84
85
|
- [`strictTransportSecurity()`](https://docs.thi.ng/umbrella/server/functions/strictTransportSecurity.html): Policy header injection
|
|
85
86
|
|
|
@@ -149,7 +150,7 @@ For Node.js REPL:
|
|
|
149
150
|
const ser = await import("@thi.ng/server");
|
|
150
151
|
```
|
|
151
152
|
|
|
152
|
-
Package sizes (brotli'd, pre-treeshake): ESM:
|
|
153
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 6.15 KB
|
|
153
154
|
|
|
154
155
|
## Dependencies
|
|
155
156
|
|
|
@@ -203,6 +204,8 @@ const app = srv.server<AppCtx>({
|
|
|
203
204
|
intercept: [
|
|
204
205
|
// log all requests (using server's configured logger)
|
|
205
206
|
srv.logRequest(),
|
|
207
|
+
// block known AI bots
|
|
208
|
+
srv.rejectUserAgents(srv.USER_AGENT_AI_BOTS),
|
|
206
209
|
// lookup/create sessions (using above interceptor)
|
|
207
210
|
session,
|
|
208
211
|
// ensure routes with `auth` flag have a logged-in user
|
package/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Fn, Maybe, MaybePromise } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, Fn2, Maybe, MaybePromise } from "@thi.ng/api";
|
|
2
2
|
import type { ILogger } from "@thi.ng/logger";
|
|
3
3
|
import type { Route, RouteMatch } from "@thi.ng/router";
|
|
4
4
|
import type { IncomingMessage } from "node:http";
|
|
@@ -67,6 +67,12 @@ export interface ServerOpts<CTX extends RequestCtx = RequestCtx> {
|
|
|
67
67
|
* Reference: https://nodejs.org/api/net.html#class-netblocklist
|
|
68
68
|
*/
|
|
69
69
|
blockList: BlockList;
|
|
70
|
+
/**
|
|
71
|
+
* HTTP method adapter/converter. The default implementation only converts
|
|
72
|
+
* {@link Method} `head` to `get` if the route does not provide a `head`
|
|
73
|
+
* handler.
|
|
74
|
+
*/
|
|
75
|
+
method: Fn2<Method, Pick<RequestCtx, "req" | "route" | "match" | "cookies" | "query">, Method>;
|
|
70
76
|
}
|
|
71
77
|
export interface ServerRoute<CTX extends RequestCtx = RequestCtx> extends Route {
|
|
72
78
|
handlers: Partial<Record<Method, RequestHandler<CTX>>>;
|
|
@@ -85,6 +91,8 @@ export interface RequestCtx {
|
|
|
85
91
|
res: ServerResponse;
|
|
86
92
|
route: CompiledServerRoute;
|
|
87
93
|
match: RouteMatch;
|
|
94
|
+
method: Method;
|
|
95
|
+
origMethod: Method;
|
|
88
96
|
path: string;
|
|
89
97
|
query: Record<string, any>;
|
|
90
98
|
cookies?: Record<string, string>;
|
package/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./interceptors/logging.js";
|
|
|
8
8
|
export * from "./interceptors/measure.js";
|
|
9
9
|
export * from "./interceptors/rate-limit.js";
|
|
10
10
|
export * from "./interceptors/referrer-policy.js";
|
|
11
|
+
export * from "./interceptors/reject-useragent.js";
|
|
11
12
|
export * from "./interceptors/strict-transport.js";
|
|
12
13
|
export * from "./interceptors/x-origin-opener.js";
|
|
13
14
|
export * from "./interceptors/x-origin-resource.js";
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./interceptors/logging.js";
|
|
|
8
8
|
export * from "./interceptors/measure.js";
|
|
9
9
|
export * from "./interceptors/rate-limit.js";
|
|
10
10
|
export * from "./interceptors/referrer-policy.js";
|
|
11
|
+
export * from "./interceptors/reject-useragent.js";
|
|
11
12
|
export * from "./interceptors/strict-transport.js";
|
|
12
13
|
export * from "./interceptors/x-origin-opener.js";
|
|
13
14
|
export * from "./interceptors/x-origin-resource.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Interceptor } from "../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pre-interceptor to check `User-Agent` header against given regexp. If the
|
|
4
|
+
* regexp matches, triggers a HTTP 403 response and terminates the request.
|
|
5
|
+
*
|
|
6
|
+
* @param patterns
|
|
7
|
+
*/
|
|
8
|
+
export declare const rejectUserAgents: (patterns: string | RegExp) => Interceptor;
|
|
9
|
+
/**
|
|
10
|
+
* String defining partial regexp of known AI bot names in `User-Agent` header.
|
|
11
|
+
*
|
|
12
|
+
* Source: https://github.com/qwebltd/Useful-scripts/blob/main/Bash%20scripts%20for%20Linux/nginx-badbot-forbids.conf
|
|
13
|
+
*/
|
|
14
|
+
export declare const USER_AGENT_AI_BOTS = "AI2Bot|Anthropic|BrightBot|ByteDance|ByteSpider|CCBot|ChatGPT|ClaudeBot|Claude-Web|Cohere-AI|Cohere-Training-Data-Crawler|DiffBot|DuckAssistBot|FriendlyCrawler|Friendly_Crawler|Google-CloudVertexBot|GPTBot|ICC-Crawler|Img2Dataset|Kangaroo Bot|MLBot|OAI-SearchBot|PanguBot|Sentibot|VelenPublicWebCrawler|Webzio-Extended";
|
|
15
|
+
/**
|
|
16
|
+
* String defining partial regexp of known crawlers & scraper names in `User-Agent` header.
|
|
17
|
+
*
|
|
18
|
+
* Source: https://github.com/qwebltd/Useful-scripts/blob/main/Bash%20scripts%20for%20Linux/nginx-rate-limiting.conf
|
|
19
|
+
*/
|
|
20
|
+
export declare const USER_AGENT_SCRAPERS = "008|AddSugarSpiderBot|AdsBot|AhrefsBot|AmazonBot|Arachmo|Barkrowler|BimBot|BlexBot|Boitho.com|BTBot|ConveraCrawler|DiamondBot|DotBot|Earthcom.info|EmeraldShield.com|EsperanzaBot|FacebookBot|Fast Enterprise|FindLinks|FurlBot|FyberSpider|GaisBot|GigaBot|GirafaBot|GoogleOther|Go-HTTP-Client|HL_Ftien_Spider|Holmes|HTDig|ICCrawler|Ichiro|IgdeSpyder|ImageSiftBot|IonCrawl|IRLbot|ISSCyberRiskCrawler|IssueCrawler|Jaxified Bot|JyxoBot|KoepaBot|Kototoi.org|Larbin|LDSpider|LinkWalker|LMSpider|Lwp-Trivial|L.Webis|Mabontland|Magpie-Crawler|Mail.RU_Bot|Masscan-NG|Meltwater|Meta-ExternalAgent|Mogimogi|MoreoverBot|Morning Paper|MSRBot|MVAClient|MXBot|NetResearchServer|NetSeer Crawler|NewsGator|NiceBot|NUSearch Spider|Nutch|Nymesis|OmniExplorer_Bot|OrbBot|OozBot|PageBitesHyperBot|Peer39_Crawler|PolyBot|PSBot|PycUrl|Qseero|Radian6|RampyBot|RufusBot|SandCrawler|SBIder|Scrapy|SeekBot|SemanticDiscovery|Semrush|Sensis Web Crawler|SEOChat|Shim-Crawler|SiteBot|Snappy|SurveyBot|Sqworm|SuggyBot|SynooBot|TerrawizBot|TheSuBot|Thumbnail.cz|TimpiBot|TinEye|TruwoGPS|TurnItInBot|TweetedTimes Bot|UrlFileBot|Vagabondo|Vortex|Voyager|VYU2|WebCollage|Wf84|WomlpeFactory|Xaldon_WebSpider|Yacy|YasakliBot";
|
|
21
|
+
//# sourceMappingURL=reject-useragent.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isString } from "@thi.ng/checks";
|
|
2
|
+
const rejectUserAgents = (patterns) => {
|
|
3
|
+
const regexp = isString(patterns) ? new RegExp(patterns) : patterns;
|
|
4
|
+
return {
|
|
5
|
+
pre: (ctx) => {
|
|
6
|
+
const ua = ctx.req.headers["user-agent"];
|
|
7
|
+
if (ua && regexp.test(ua)) {
|
|
8
|
+
ctx.res.forbidden();
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const USER_AGENT_AI_BOTS = "AI2Bot|Anthropic|BrightBot|ByteDance|ByteSpider|CCBot|ChatGPT|ClaudeBot|Claude-Web|Cohere-AI|Cohere-Training-Data-Crawler|DiffBot|DuckAssistBot|FriendlyCrawler|Friendly_Crawler|Google-CloudVertexBot|GPTBot|ICC-Crawler|Img2Dataset|Kangaroo Bot|MLBot|OAI-SearchBot|PanguBot|Sentibot|VelenPublicWebCrawler|Webzio-Extended";
|
|
16
|
+
const USER_AGENT_SCRAPERS = "008|AddSugarSpiderBot|AdsBot|AhrefsBot|AmazonBot|Arachmo|Barkrowler|BimBot|BlexBot|Boitho.com|BTBot|ConveraCrawler|DiamondBot|DotBot|Earthcom.info|EmeraldShield.com|EsperanzaBot|FacebookBot|Fast Enterprise|FindLinks|FurlBot|FyberSpider|GaisBot|GigaBot|GirafaBot|GoogleOther|Go-HTTP-Client|HL_Ftien_Spider|Holmes|HTDig|ICCrawler|Ichiro|IgdeSpyder|ImageSiftBot|IonCrawl|IRLbot|ISSCyberRiskCrawler|IssueCrawler|Jaxified Bot|JyxoBot|KoepaBot|Kototoi.org|Larbin|LDSpider|LinkWalker|LMSpider|Lwp-Trivial|L.Webis|Mabontland|Magpie-Crawler|Mail.RU_Bot|Masscan-NG|Meltwater|Meta-ExternalAgent|Mogimogi|MoreoverBot|Morning Paper|MSRBot|MVAClient|MXBot|NetResearchServer|NetSeer Crawler|NewsGator|NiceBot|NUSearch Spider|Nutch|Nymesis|OmniExplorer_Bot|OrbBot|OozBot|PageBitesHyperBot|Peer39_Crawler|PolyBot|PSBot|PycUrl|Qseero|Radian6|RampyBot|RufusBot|SandCrawler|SBIder|Scrapy|SeekBot|SemanticDiscovery|Semrush|Sensis Web Crawler|SEOChat|Shim-Crawler|SiteBot|Snappy|SurveyBot|Sqworm|SuggyBot|SynooBot|TerrawizBot|TheSuBot|Thumbnail.cz|TimpiBot|TinEye|TruwoGPS|TurnItInBot|TweetedTimes Bot|UrlFileBot|Vagabondo|Vortex|Voyager|VYU2|WebCollage|Wf84|WomlpeFactory|Xaldon_WebSpider|Yacy|YasakliBot";
|
|
17
|
+
export {
|
|
18
|
+
USER_AGENT_AI_BOTS,
|
|
19
|
+
USER_AGENT_SCRAPERS,
|
|
20
|
+
rejectUserAgents
|
|
21
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Minimal HTTP server with declarative routing, static file serving and freely extensible via pre/post interceptors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -39,26 +39,26 @@
|
|
|
39
39
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@thi.ng/api": "^8.11.
|
|
43
|
-
"@thi.ng/arrays": "^2.10.
|
|
44
|
-
"@thi.ng/cache": "^2.3.
|
|
45
|
-
"@thi.ng/checks": "^3.7.
|
|
46
|
-
"@thi.ng/errors": "^2.5.
|
|
47
|
-
"@thi.ng/file-io": "^2.1.
|
|
48
|
-
"@thi.ng/leaky-bucket": "^0.2.
|
|
49
|
-
"@thi.ng/logger": "^3.1.
|
|
50
|
-
"@thi.ng/mime": "^2.7.
|
|
51
|
-
"@thi.ng/paths": "^5.2.
|
|
52
|
-
"@thi.ng/router": "^4.1.
|
|
53
|
-
"@thi.ng/strings": "^3.9.
|
|
54
|
-
"@thi.ng/timestamp": "^1.1.
|
|
55
|
-
"@thi.ng/uuid": "^1.1.
|
|
42
|
+
"@thi.ng/api": "^8.11.23",
|
|
43
|
+
"@thi.ng/arrays": "^2.10.20",
|
|
44
|
+
"@thi.ng/cache": "^2.3.28",
|
|
45
|
+
"@thi.ng/checks": "^3.7.3",
|
|
46
|
+
"@thi.ng/errors": "^2.5.29",
|
|
47
|
+
"@thi.ng/file-io": "^2.1.32",
|
|
48
|
+
"@thi.ng/leaky-bucket": "^0.2.1",
|
|
49
|
+
"@thi.ng/logger": "^3.1.4",
|
|
50
|
+
"@thi.ng/mime": "^2.7.5",
|
|
51
|
+
"@thi.ng/paths": "^5.2.6",
|
|
52
|
+
"@thi.ng/router": "^4.1.23",
|
|
53
|
+
"@thi.ng/strings": "^3.9.8",
|
|
54
|
+
"@thi.ng/timestamp": "^1.1.8",
|
|
55
|
+
"@thi.ng/uuid": "^1.1.20"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@types/node": "^22.13.
|
|
59
|
-
"esbuild": "^0.25.
|
|
60
|
-
"typedoc": "^0.
|
|
61
|
-
"typescript": "^5.
|
|
58
|
+
"@types/node": "^22.13.10",
|
|
59
|
+
"esbuild": "^0.25.1",
|
|
60
|
+
"typedoc": "^0.28.0",
|
|
61
|
+
"typescript": "^5.8.2"
|
|
62
62
|
},
|
|
63
63
|
"keywords": [
|
|
64
64
|
"cookie",
|
|
@@ -123,6 +123,9 @@
|
|
|
123
123
|
"./interceptors/referrer-policy": {
|
|
124
124
|
"default": "./interceptors/referrer-policy.js"
|
|
125
125
|
},
|
|
126
|
+
"./interceptors/reject-useragent": {
|
|
127
|
+
"default": "./interceptors/reject-useragent.js"
|
|
128
|
+
},
|
|
126
129
|
"./interceptors/strict-transport": {
|
|
127
130
|
"default": "./interceptors/strict-transport.js"
|
|
128
131
|
},
|
|
@@ -164,5 +167,5 @@
|
|
|
164
167
|
"status": "alpha",
|
|
165
168
|
"year": 2024
|
|
166
169
|
},
|
|
167
|
-
"gitHead": "
|
|
170
|
+
"gitHead": "dfecb91b5b6a05db32d96f261e0c0ac6319240b9\n"
|
|
168
171
|
}
|
package/server.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class Server<CTX extends RequestCtx = RequestCtx> {
|
|
|
10
10
|
server: http.Server<typeof http.IncomingMessage, typeof ServerResponse>;
|
|
11
11
|
host: string;
|
|
12
12
|
protected augmentCtx: Fn<RequestCtx, CTX>;
|
|
13
|
+
protected methodAdapter: ServerOpts["method"];
|
|
13
14
|
constructor(opts?: Partial<ServerOpts<CTX>>);
|
|
14
15
|
start(): Promise<boolean>;
|
|
15
16
|
stop(): Promise<boolean>;
|
|
@@ -26,15 +27,86 @@ export declare const server: <CTX extends RequestCtx>(opts?: Partial<ServerOpts<
|
|
|
26
27
|
* for various commonly used HTTP statuses/errors.
|
|
27
28
|
*/
|
|
28
29
|
export declare class ServerResponse extends http.ServerResponse<http.IncomingMessage> {
|
|
30
|
+
/**
|
|
31
|
+
* Writes a HTTP 204 header (plus given `headers`) and ends the response.
|
|
32
|
+
*
|
|
33
|
+
* @param headers
|
|
34
|
+
*/
|
|
29
35
|
noContent(headers?: http.OutgoingHttpHeaders): void;
|
|
36
|
+
/**
|
|
37
|
+
* Writes a HTTP 302 header to redirect to given URL, add given additional
|
|
38
|
+
* `headers` and ends the response.
|
|
39
|
+
*
|
|
40
|
+
* @remarks
|
|
41
|
+
* Also see {@link ServerResponse.seeOther}.
|
|
42
|
+
*
|
|
43
|
+
* @param headers
|
|
44
|
+
*/
|
|
30
45
|
redirectTo(location: string, headers?: http.OutgoingHttpHeaders): void;
|
|
46
|
+
/**
|
|
47
|
+
* Writes a HTTP 303 header to redirect to given URL, add given additional
|
|
48
|
+
* `headers` and ends the response.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* Also see {@link ServerResponse.redirectTo}.
|
|
52
|
+
*
|
|
53
|
+
* @param headers
|
|
54
|
+
*/
|
|
31
55
|
seeOther(location: string, headers?: http.OutgoingHttpHeaders): void;
|
|
56
|
+
/**
|
|
57
|
+
* Writes a HTTP 304 header (plus given `headers`) and ends the response.
|
|
58
|
+
*
|
|
59
|
+
* @param headers
|
|
60
|
+
*/
|
|
32
61
|
unmodified(headers?: http.OutgoingHttpHeaders): void;
|
|
62
|
+
/**
|
|
63
|
+
* Writes a HTTP 400 header (plus given `headers`) and ends the response
|
|
64
|
+
* (with optional `body`).
|
|
65
|
+
*
|
|
66
|
+
* @param headers
|
|
67
|
+
*/
|
|
68
|
+
badRequest(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
69
|
+
/**
|
|
70
|
+
* Writes a HTTP 401 header (plus given `headers`) and ends the response
|
|
71
|
+
* (with optional `body`).
|
|
72
|
+
*
|
|
73
|
+
* @param headers
|
|
74
|
+
*/
|
|
33
75
|
unauthorized(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
76
|
+
/**
|
|
77
|
+
* Writes a HTTP 403 header (plus given `headers`) and ends the response
|
|
78
|
+
* (with optional `body`).
|
|
79
|
+
*
|
|
80
|
+
* @param headers
|
|
81
|
+
*/
|
|
34
82
|
forbidden(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
83
|
+
/**
|
|
84
|
+
* Writes a HTTP 404 header (plus given `headers`) and ends the response
|
|
85
|
+
* (with optional `body`).
|
|
86
|
+
*
|
|
87
|
+
* @param headers
|
|
88
|
+
*/
|
|
35
89
|
missing(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
90
|
+
/**
|
|
91
|
+
* Writes a HTTP 405 header (plus given `headers`) and ends the response
|
|
92
|
+
* (with optional `body`).
|
|
93
|
+
*
|
|
94
|
+
* @param headers
|
|
95
|
+
*/
|
|
36
96
|
notAllowed(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
97
|
+
/**
|
|
98
|
+
* Writes a HTTP 406 header (plus given `headers`) and ends the response
|
|
99
|
+
* (with optional `body`).
|
|
100
|
+
*
|
|
101
|
+
* @param headers
|
|
102
|
+
*/
|
|
37
103
|
notAcceptable(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
104
|
+
/**
|
|
105
|
+
* Writes a HTTP 429 header (plus given `headers`) and ends the response
|
|
106
|
+
* (with optional `body`).
|
|
107
|
+
*
|
|
108
|
+
* @param headers
|
|
109
|
+
*/
|
|
38
110
|
rateLimit(headers?: http.OutgoingHttpHeaders, body?: any): void;
|
|
39
111
|
/**
|
|
40
112
|
* HTTP 444. Indicates the server has returned no information to the client and closed
|
package/server.js
CHANGED
|
@@ -20,6 +20,7 @@ class Server {
|
|
|
20
20
|
this.opts = opts;
|
|
21
21
|
this.logger = opts.logger ?? new ConsoleLogger("server");
|
|
22
22
|
this.host = opts.host ?? "localhost";
|
|
23
|
+
this.methodAdapter = opts.method ?? ((method, { route }) => method === "head" && !route.handlers.head && route.handlers.get ? (console.log("adapted head"), "get") : method);
|
|
23
24
|
if (isIPv6(this.host)) this.host = normalizeIPv6Address(this.host);
|
|
24
25
|
this.augmentCtx = opts.context ?? identity;
|
|
25
26
|
const routes = [
|
|
@@ -44,6 +45,7 @@ class Server {
|
|
|
44
45
|
server;
|
|
45
46
|
host;
|
|
46
47
|
augmentCtx;
|
|
48
|
+
methodAdapter;
|
|
47
49
|
async start() {
|
|
48
50
|
const {
|
|
49
51
|
ssl,
|
|
@@ -101,15 +103,22 @@ class Server {
|
|
|
101
103
|
const match = this.router.route(path);
|
|
102
104
|
if (match.id === MISSING) return res.missing();
|
|
103
105
|
const route = this.router.routeForID(match.id).spec;
|
|
104
|
-
|
|
106
|
+
const rawCookies = req.headers["cookie"] || req.headers["set-cookie"]?.join(";");
|
|
107
|
+
const cookies = rawCookies ? parseCoookies(rawCookies) : {};
|
|
108
|
+
const query = parseSearchParams(url.searchParams);
|
|
109
|
+
const origMethod = req.method.toLowerCase();
|
|
110
|
+
const method = this.methodAdapter(origMethod, {
|
|
111
|
+
req,
|
|
112
|
+
route,
|
|
113
|
+
match,
|
|
114
|
+
query,
|
|
115
|
+
cookies
|
|
116
|
+
});
|
|
105
117
|
if (method === "options" && !route.handlers.options) {
|
|
106
118
|
return res.noContent({
|
|
107
119
|
allow: Object.keys(route.handlers).map(upper).join(", ")
|
|
108
120
|
});
|
|
109
121
|
}
|
|
110
|
-
const rawCookies = req.headers["cookie"] || req.headers["set-cookie"]?.join(";");
|
|
111
|
-
const cookies = rawCookies ? parseCoookies(rawCookies) : {};
|
|
112
|
-
const query = parseSearchParams(url.searchParams);
|
|
113
122
|
const ctx = this.augmentCtx({
|
|
114
123
|
// @ts-ignore
|
|
115
124
|
server: this,
|
|
@@ -120,11 +129,10 @@ class Server {
|
|
|
120
129
|
query,
|
|
121
130
|
cookies,
|
|
122
131
|
route,
|
|
123
|
-
match
|
|
132
|
+
match,
|
|
133
|
+
method,
|
|
134
|
+
origMethod
|
|
124
135
|
});
|
|
125
|
-
if (method === "head" && !route.handlers.head && route.handlers.get) {
|
|
126
|
-
method = "get";
|
|
127
|
-
}
|
|
128
136
|
const handler = route.handlers[method];
|
|
129
137
|
if (handler) {
|
|
130
138
|
this.runHandler(handler, ctx);
|
|
@@ -232,33 +240,106 @@ class Server {
|
|
|
232
240
|
}
|
|
233
241
|
const server = (opts) => new Server(opts);
|
|
234
242
|
class ServerResponse extends http.ServerResponse {
|
|
243
|
+
/**
|
|
244
|
+
* Writes a HTTP 204 header (plus given `headers`) and ends the response.
|
|
245
|
+
*
|
|
246
|
+
* @param headers
|
|
247
|
+
*/
|
|
235
248
|
noContent(headers) {
|
|
236
249
|
this.writeHead(204, headers).end();
|
|
237
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Writes a HTTP 302 header to redirect to given URL, add given additional
|
|
253
|
+
* `headers` and ends the response.
|
|
254
|
+
*
|
|
255
|
+
* @remarks
|
|
256
|
+
* Also see {@link ServerResponse.seeOther}.
|
|
257
|
+
*
|
|
258
|
+
* @param headers
|
|
259
|
+
*/
|
|
238
260
|
redirectTo(location, headers) {
|
|
239
261
|
this.writeHead(302, { ...headers, location }).end();
|
|
240
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Writes a HTTP 303 header to redirect to given URL, add given additional
|
|
265
|
+
* `headers` and ends the response.
|
|
266
|
+
*
|
|
267
|
+
* @remarks
|
|
268
|
+
* Also see {@link ServerResponse.redirectTo}.
|
|
269
|
+
*
|
|
270
|
+
* @param headers
|
|
271
|
+
*/
|
|
241
272
|
seeOther(location, headers) {
|
|
242
273
|
this.writeHead(303, { ...headers, location }).end();
|
|
243
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Writes a HTTP 304 header (plus given `headers`) and ends the response.
|
|
277
|
+
*
|
|
278
|
+
* @param headers
|
|
279
|
+
*/
|
|
244
280
|
unmodified(headers) {
|
|
245
281
|
this.writeHead(304, headers).end();
|
|
246
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Writes a HTTP 400 header (plus given `headers`) and ends the response
|
|
285
|
+
* (with optional `body`).
|
|
286
|
+
*
|
|
287
|
+
* @param headers
|
|
288
|
+
*/
|
|
289
|
+
badRequest(headers, body) {
|
|
290
|
+
this.writeHead(400, headers).end(body);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Writes a HTTP 401 header (plus given `headers`) and ends the response
|
|
294
|
+
* (with optional `body`).
|
|
295
|
+
*
|
|
296
|
+
* @param headers
|
|
297
|
+
*/
|
|
247
298
|
unauthorized(headers, body) {
|
|
248
299
|
this.writeHead(401, headers).end(body);
|
|
249
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Writes a HTTP 403 header (plus given `headers`) and ends the response
|
|
303
|
+
* (with optional `body`).
|
|
304
|
+
*
|
|
305
|
+
* @param headers
|
|
306
|
+
*/
|
|
250
307
|
forbidden(headers, body) {
|
|
251
308
|
this.writeHead(403, headers).end(body);
|
|
252
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Writes a HTTP 404 header (plus given `headers`) and ends the response
|
|
312
|
+
* (with optional `body`).
|
|
313
|
+
*
|
|
314
|
+
* @param headers
|
|
315
|
+
*/
|
|
253
316
|
missing(headers, body) {
|
|
254
317
|
this.writeHead(404, headers).end(body);
|
|
255
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Writes a HTTP 405 header (plus given `headers`) and ends the response
|
|
321
|
+
* (with optional `body`).
|
|
322
|
+
*
|
|
323
|
+
* @param headers
|
|
324
|
+
*/
|
|
256
325
|
notAllowed(headers, body) {
|
|
257
326
|
this.writeHead(405, headers).end(body);
|
|
258
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Writes a HTTP 406 header (plus given `headers`) and ends the response
|
|
330
|
+
* (with optional `body`).
|
|
331
|
+
*
|
|
332
|
+
* @param headers
|
|
333
|
+
*/
|
|
259
334
|
notAcceptable(headers, body) {
|
|
260
335
|
this.writeHead(406, headers).end(body);
|
|
261
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Writes a HTTP 429 header (plus given `headers`) and ends the response
|
|
339
|
+
* (with optional `body`).
|
|
340
|
+
*
|
|
341
|
+
* @param headers
|
|
342
|
+
*/
|
|
262
343
|
rateLimit(headers, body) {
|
|
263
344
|
this.writeHead(429, headers).end(body);
|
|
264
345
|
}
|