@zudojs/http 1.2.0 → 1.4.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/README.md +221 -0
- package/dist/httpAdapter/http.adapters.d.ts +24 -3
- package/dist/httpAdapter/http.adapters.js +20 -11
- package/dist/httpAdapter/node/httpNode.adapter.d.ts +2 -1
- package/dist/httpAdapter/node/httpNode.adapter.js +17 -2
- package/dist/httpAdapter/node/httpNode.request.js +7 -1
- package/dist/httpAdapter/node/httpNode.type.d.ts +14 -0
- package/dist/httpAgent/http.agent.d.ts +22 -3
- package/dist/httpAgent/http.agent.js +52 -14
- package/dist/httpCacheControl/httpCacheControl.freshness.d.ts +7 -1
- package/dist/httpCacheControl/httpCacheControl.freshness.js +30 -3
- package/dist/httpClient/httpClient.retry.d.ts +17 -12
- package/dist/httpClient/httpClient.retry.js +35 -10
- package/dist/httpClient/httpClient.type.d.ts +14 -0
- package/dist/httpErrors/httpError.base.js +2 -2
- package/dist/httpErrors/httpError.util.d.ts +8 -0
- package/dist/httpErrors/httpError.util.js +12 -0
- package/dist/httpFetchMount/httpFetchMount.core.d.ts +25 -0
- package/dist/httpFetchMount/httpFetchMount.core.js +84 -0
- package/dist/httpFetchMount/httpFetchMount.request.d.ts +21 -0
- package/dist/httpFetchMount/httpFetchMount.request.js +100 -0
- package/dist/httpFetchMount/httpFetchMount.type.d.ts +56 -0
- package/dist/httpFetchMount/httpFetchMount.type.js +5 -0
- package/dist/httpFetchMount/index.d.ts +11 -0
- package/dist/httpFetchMount/index.js +10 -0
- package/dist/httpKeepAlive/httpKeepAlive.core.js +14 -2
- package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.d.ts +18 -0
- package/dist/httpMiddleware/builtin/logging/httpMiddleware.logging.js +17 -1
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.d.ts +6 -3
- package/dist/httpMiddleware/builtin/rateLimit/httpMiddleware.rateLimit.js +34 -6
- package/dist/httpMiddleware/builtin/security/httpMiddleware.security.d.ts +11 -0
- package/dist/httpMiddleware/builtin/security/httpMiddleware.security.js +25 -12
- package/dist/httpMiddleware/httpMiddleware.type.d.ts +9 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.execution.js +22 -45
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.d.ts +36 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.guardResponse.js +57 -0
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.d.ts +2 -1
- package/dist/httpMiddleware/pipeline/httpPipeline.helper.js +10 -0
- package/dist/httpMiddleware/pipeline/index.d.ts +1 -0
- package/dist/httpMiddleware/pipeline/index.js +1 -0
- package/dist/httpNegotiation/httpNegotiation.core.d.ts +13 -0
- package/dist/httpNegotiation/httpNegotiation.core.js +60 -8
- package/dist/httpOpenApi/httpOpenApi.document.d.ts +44 -0
- package/dist/httpOpenApi/httpOpenApi.document.js +61 -0
- package/dist/httpOpenApi/httpOpenApi.mount.d.ts +31 -0
- package/dist/httpOpenApi/httpOpenApi.mount.js +58 -0
- package/dist/httpOpenApi/httpOpenApi.type.d.ts +54 -0
- package/dist/httpOpenApi/httpOpenApi.type.js +5 -0
- package/dist/httpOpenApi/index.d.ts +13 -0
- package/dist/httpOpenApi/index.js +12 -0
- package/dist/httpOpenApi/routeTable/index.d.ts +11 -0
- package/dist/httpOpenApi/routeTable/index.js +11 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.d.ts +19 -0
- package/dist/httpOpenApi/routeTable/routeTable.collect.js +89 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.d.ts +15 -0
- package/dist/httpOpenApi/routeTable/routeTable.merge.js +37 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.d.ts +30 -0
- package/dist/httpOpenApi/routeTable/routeTable.template.js +67 -0
- package/dist/httpProxy/http.proxy.d.ts +9 -0
- package/dist/httpProxy/http.proxy.js +35 -3
- package/dist/httpQuery/index.d.ts +11 -2
- package/dist/httpQuery/index.js +11 -2
- package/dist/httpQuery/queryParse/index.d.ts +10 -0
- package/dist/httpQuery/queryParse/index.js +10 -0
- package/dist/httpQuery/queryParse/queryParse.flat.d.ts +13 -0
- package/dist/httpQuery/queryParse/queryParse.flat.js +44 -0
- package/dist/httpQuery/queryParse/queryParse.nested.d.ts +25 -0
- package/dist/httpQuery/queryParse/queryParse.nested.js +112 -0
- package/dist/httpQuery/queryParse/queryParse.tokenizer.d.ts +37 -0
- package/dist/httpQuery/queryParse/queryParse.tokenizer.js +95 -0
- package/dist/httpQuery/queryRequest/index.d.ts +9 -0
- package/dist/httpQuery/queryRequest/index.js +9 -0
- package/dist/httpQuery/queryRequest/query.request.d.ts +43 -0
- package/dist/httpQuery/queryRequest/query.request.js +96 -0
- package/dist/httpQuery/querySerialize/index.d.ts +10 -0
- package/dist/httpQuery/querySerialize/index.js +10 -0
- package/dist/httpQuery/querySerialize/query.util.d.ts +21 -0
- package/dist/httpQuery/querySerialize/query.util.js +67 -0
- package/dist/httpQuery/querySerialize/querySerialize.core.d.ts +12 -0
- package/dist/httpQuery/querySerialize/querySerialize.core.js +97 -0
- package/dist/httpQuery/queryTypes/index.d.ts +11 -0
- package/dist/httpQuery/queryTypes/index.js +9 -0
- package/dist/httpQuery/queryTypes/query.container.d.ts +16 -0
- package/dist/httpQuery/queryTypes/query.container.js +51 -0
- package/dist/httpQuery/queryTypes/query.limit.d.ts +25 -0
- package/dist/httpQuery/queryTypes/query.limit.js +32 -0
- package/dist/httpQuery/queryTypes/query.type.d.ts +62 -0
- package/dist/httpQuery/queryTypes/query.type.js +2 -0
- package/dist/httpRedirect/http.redirect.d.ts +6 -0
- package/dist/httpRedirect/http.redirect.js +53 -2
- package/dist/httpRequest/http.request.d.ts +61 -2
- package/dist/httpRequest/http.request.js +86 -35
- package/dist/httpRequest/httpRequest.context.d.ts +8 -0
- package/dist/httpRequest/httpRequest.context.js +23 -18
- package/dist/httpRequest/index.d.ts +1 -0
- package/dist/httpRequest/index.js +1 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.d.ts +25 -0
- package/dist/httpRequest/requestId/httpRequest.requestId.js +34 -0
- package/dist/httpRequest/requestId/index.d.ts +7 -0
- package/dist/httpRequest/requestId/index.js +7 -0
- package/dist/httpRequest/target/httpRequest.target.d.ts +2 -2
- package/dist/httpRequest/target/httpRequest.target.js +23 -5
- package/dist/httpResponse/httpResponse.writer.js +15 -0
- package/dist/httpRouter/core/factory/httpRoute.factory.base.d.ts +24 -4
- package/dist/httpRouter/core/factory/httpRoute.factory.base.js +73 -17
- package/dist/httpRouter/core/factory/httpRoute.factory.js +3 -3
- package/dist/httpRouter/core/group/httpRouterGroup.core.js +13 -0
- package/dist/httpRouter/core/register/httpRouter.register.js +19 -21
- package/dist/httpRouter/core/types/httpRouter.type.d.ts +37 -1
- package/dist/httpRouter/core/util/httpRoute.util.d.ts +55 -0
- package/dist/httpRouter/core/util/httpRoute.util.js +101 -4
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.d.ts +11 -0
- package/dist/httpRouter/dispatch/httpRoute.dispatcher.js +25 -6
- package/dist/httpRouter/matching/httpRoute.matcher.core.js +51 -6
- package/dist/httpRouter/matching/httpRoute.matcher.d.ts +0 -1
- package/dist/httpRouter/matching/httpRoute.matcher.js +64 -26
- package/dist/httpRouter/pattern/httpRoute.pattern.parse.d.ts +16 -0
- package/dist/httpRouter/pattern/httpRoute.pattern.parse.js +40 -11
- package/dist/httpRouter/pattern/index.d.ts +1 -1
- package/dist/httpRouter/pattern/index.js +1 -1
- package/dist/httpSecurity/httpSecurity.config.js +4 -1
- package/dist/httpSecurity/httpSecurity.validator.js +16 -7
- package/dist/httpServer/factory/httpServer.factory.d.ts +10 -9
- package/dist/httpServer/factory/httpServer.factory.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +11 -8
|
@@ -12,24 +12,37 @@ import { parseCacheControl } from "./core/httpCacheControl.parse.js";
|
|
|
12
12
|
* the origin marked must-revalidate-before-reuse must never be reported
|
|
13
13
|
* fresh.
|
|
14
14
|
*
|
|
15
|
+
* The response's current age follows RFC 9111 section 4.2.3: the `Age`
|
|
16
|
+
* header plus the time elapsed since the response's `Date`. Reading `Age`
|
|
17
|
+
* alone — as this used to — left every response without that header aged
|
|
18
|
+
* `0` forever, so `isFresh()` answered `true` for a response of any age.
|
|
19
|
+
*
|
|
15
20
|
* @param responseHeaders - The cached response's headers.
|
|
16
21
|
* @param responseDate - The response's `Date`, if already parsed.
|
|
22
|
+
* @param now - The current time, defaulting to `Date.now()`.
|
|
17
23
|
* @returns The freshness calculation.
|
|
18
24
|
*/
|
|
19
|
-
export function calculateFreshness(responseHeaders, responseDate) {
|
|
25
|
+
export function calculateFreshness(responseHeaders, responseDate, now = new Date()) {
|
|
20
26
|
const cacheControl = responseHeaders["cache-control"];
|
|
21
27
|
const directives = parseCacheControl(cacheControl);
|
|
22
|
-
const date = responseDate ??
|
|
28
|
+
const date = responseDate ?? parseDateHeader(responseHeaders["date"], now);
|
|
23
29
|
/*
|
|
24
30
|
* RFC 9111 section 5.1: Age is a non-negative delta-seconds. A negative or
|
|
25
31
|
* suffixed value would otherwise inflate freshness without bound, which
|
|
26
32
|
* pins a poisoned response in cache far past the origin's TTL.
|
|
27
33
|
*/
|
|
28
34
|
const rawAge = (responseHeaders["age"] ?? "").trim();
|
|
29
|
-
const
|
|
35
|
+
const ageHeader = /^\d+$/.test(rawAge) ? Number(rawAge) : 0;
|
|
36
|
+
const residentSeconds = Math.max(0, Math.floor((now.getTime() - date.getTime()) / 1_000));
|
|
37
|
+
const age = ageHeader + residentSeconds;
|
|
30
38
|
const expiresHeader = responseHeaders["expires"];
|
|
31
39
|
const expires = expiresHeader ? parseExpires(expiresHeader) : undefined;
|
|
32
40
|
const effectiveMaxAge = resolveMaxAge(directives, expires, date);
|
|
41
|
+
/*
|
|
42
|
+
* With an `Expires`-derived lifetime this reduces to `expires - now`,
|
|
43
|
+
* because the lifetime is measured from `Date` and the age is measured to
|
|
44
|
+
* `now`.
|
|
45
|
+
*/
|
|
33
46
|
const remaining = Math.max(0, effectiveMaxAge - age);
|
|
34
47
|
const stale = remaining <= 0 ||
|
|
35
48
|
directives.noCache === true ||
|
|
@@ -43,6 +56,20 @@ export function calculateFreshness(responseHeaders, responseDate) {
|
|
|
43
56
|
remaining,
|
|
44
57
|
};
|
|
45
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses a `Date` header, falling back to the current time.
|
|
61
|
+
*
|
|
62
|
+
* @param value - The raw `Date` value.
|
|
63
|
+
* @param now - The fallback instant.
|
|
64
|
+
* @returns The parsed date.
|
|
65
|
+
*/
|
|
66
|
+
function parseDateHeader(value, now) {
|
|
67
|
+
if (value === undefined) {
|
|
68
|
+
return now;
|
|
69
|
+
}
|
|
70
|
+
const parsed = new Date(value);
|
|
71
|
+
return Number.isNaN(parsed.getTime()) ? now : parsed;
|
|
72
|
+
}
|
|
46
73
|
/**
|
|
47
74
|
* Parses an `Expires` header, treating an unparseable value as expired.
|
|
48
75
|
*
|
|
@@ -4,17 +4,8 @@
|
|
|
4
4
|
* Handles retry configuration, status-based retry decisions,
|
|
5
5
|
* exponential backoff, and delay utilities.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
export interface HttpRetryOptions {
|
|
10
|
-
readonly retries?: number;
|
|
11
|
-
readonly retryDelay?: number;
|
|
12
|
-
readonly maxRetryDelay?: number;
|
|
13
|
-
readonly retryStatusCodes?: readonly number[];
|
|
14
|
-
readonly retryMethods?: readonly HttpClientMethod[];
|
|
15
|
-
readonly retryOnNetworkError?: boolean;
|
|
16
|
-
readonly backoff?: "fixed" | "exponential";
|
|
17
|
-
}
|
|
7
|
+
import type { HttpRetryOptions } from "./httpClient.type.js";
|
|
8
|
+
export type { HttpRetryOptions } from "./httpClient.type.js";
|
|
18
9
|
/**
|
|
19
10
|
* Normalize retry options with defaults.
|
|
20
11
|
*/
|
|
@@ -25,10 +16,24 @@ export declare function normalizeRetryOptions(options?: HttpRetryOptions): HttpR
|
|
|
25
16
|
export declare function shouldRetryStatus(status: number, method: string, retry?: HttpRetryOptions): boolean;
|
|
26
17
|
/**
|
|
27
18
|
* Check if an error should trigger a retry.
|
|
19
|
+
*
|
|
20
|
+
* Transport failures are retried when `retryOnNetworkError` is on, and
|
|
21
|
+
* timeouts when `retryOnTimeout` is on (it defaults to
|
|
22
|
+
* `retryOnNetworkError`). Both apply only to `retryMethods`, which default
|
|
23
|
+
* to the idempotent `GET`, `HEAD` and `OPTIONS`. A caller's own abort is
|
|
24
|
+
* never retried.
|
|
28
25
|
*/
|
|
29
26
|
export declare function shouldRetryError(error: unknown, method: string, retry?: HttpRetryOptions): boolean;
|
|
30
27
|
/**
|
|
31
|
-
* Calculate retry
|
|
28
|
+
* Calculate the wait before retry number `attempt + 1`.
|
|
29
|
+
*
|
|
30
|
+
* The delay is `retryDelay` (fixed backoff) or `retryDelay * 2^attempt`
|
|
31
|
+
* (exponential), capped at `maxRetryDelay`. With `jitter` (the default) the
|
|
32
|
+
* wait is drawn uniformly from 0 up to that delay ("full jitter"), so a
|
|
33
|
+
* burst of clients that failed together does not retry in lockstep;
|
|
34
|
+
* `jitter: false` waits exactly the delay. Jitter used to add up to a
|
|
35
|
+
* fixed 1000 ms whatever `retryDelay` was, so `retryDelay: 50` could wait
|
|
36
|
+
* a second.
|
|
32
37
|
*/
|
|
33
38
|
export declare function calculateRetryDelay(attempt: number, retry: HttpRetryOptions): number;
|
|
34
39
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Handles retry configuration, status-based retry decisions,
|
|
5
5
|
* exponential backoff, and delay utilities.
|
|
6
6
|
*/
|
|
7
|
+
import { HttpClientTimeoutError } from "./httpClient.error.js";
|
|
7
8
|
/**
|
|
8
9
|
* Normalize retry options with defaults.
|
|
9
10
|
*/
|
|
@@ -17,7 +18,9 @@ export function normalizeRetryOptions(options) {
|
|
|
17
18
|
retryStatusCodes: options.retryStatusCodes ?? [429, 502, 503, 504],
|
|
18
19
|
retryMethods: options.retryMethods ?? ["GET", "HEAD", "OPTIONS"],
|
|
19
20
|
retryOnNetworkError: options.retryOnNetworkError ?? true,
|
|
21
|
+
retryOnTimeout: options.retryOnTimeout ?? options.retryOnNetworkError ?? true,
|
|
20
22
|
backoff: options.backoff ?? "exponential",
|
|
23
|
+
jitter: options.jitter ?? true,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
@@ -32,20 +35,35 @@ export function shouldRetryStatus(status, method, retry) {
|
|
|
32
35
|
}
|
|
33
36
|
/**
|
|
34
37
|
* Check if an error should trigger a retry.
|
|
38
|
+
*
|
|
39
|
+
* Transport failures are retried when `retryOnNetworkError` is on, and
|
|
40
|
+
* timeouts when `retryOnTimeout` is on (it defaults to
|
|
41
|
+
* `retryOnNetworkError`). Both apply only to `retryMethods`, which default
|
|
42
|
+
* to the idempotent `GET`, `HEAD` and `OPTIONS`. A caller's own abort is
|
|
43
|
+
* never retried.
|
|
35
44
|
*/
|
|
36
45
|
export function shouldRetryError(error, method, retry) {
|
|
37
46
|
if (!retry?.retries)
|
|
38
47
|
return false;
|
|
39
|
-
if (!retry.retryOnNetworkError)
|
|
40
|
-
return false;
|
|
41
48
|
/*
|
|
42
|
-
* A connection that drops
|
|
43
|
-
* indistinguishable from one that never arrived, so
|
|
44
|
-
* non-idempotent method duplicates its side effects.
|
|
45
|
-
* for exactly this and was honoured only on the
|
|
49
|
+
* A connection that drops (or a request that times out) after the server
|
|
50
|
+
* processed it is indistinguishable from one that never arrived, so
|
|
51
|
+
* replaying a non-idempotent method duplicates its side effects.
|
|
52
|
+
* `retryMethods` exists for exactly this and was honoured only on the
|
|
53
|
+
* status path.
|
|
46
54
|
*/
|
|
47
55
|
if (!retry.retryMethods?.includes(method))
|
|
48
56
|
return false;
|
|
57
|
+
/*
|
|
58
|
+
* Timeouts were never retried: `isRetryableNetworkError` does not match
|
|
59
|
+
* `HttpClientTimeoutError`, so `retryOnNetworkError` had no effect on the
|
|
60
|
+
* most common transient failure.
|
|
61
|
+
*/
|
|
62
|
+
if (error instanceof HttpClientTimeoutError) {
|
|
63
|
+
return retry.retryOnTimeout ?? retry.retryOnNetworkError ?? true;
|
|
64
|
+
}
|
|
65
|
+
if (!retry.retryOnNetworkError)
|
|
66
|
+
return false;
|
|
49
67
|
return isRetryableNetworkError(error);
|
|
50
68
|
}
|
|
51
69
|
/**
|
|
@@ -71,15 +89,22 @@ function isRetryableNetworkError(error) {
|
|
|
71
89
|
return !/body|disturbed|already (been )?(used|read)/i.test(error.message);
|
|
72
90
|
}
|
|
73
91
|
/**
|
|
74
|
-
* Calculate retry
|
|
92
|
+
* Calculate the wait before retry number `attempt + 1`.
|
|
93
|
+
*
|
|
94
|
+
* The delay is `retryDelay` (fixed backoff) or `retryDelay * 2^attempt`
|
|
95
|
+
* (exponential), capped at `maxRetryDelay`. With `jitter` (the default) the
|
|
96
|
+
* wait is drawn uniformly from 0 up to that delay ("full jitter"), so a
|
|
97
|
+
* burst of clients that failed together does not retry in lockstep;
|
|
98
|
+
* `jitter: false` waits exactly the delay. Jitter used to add up to a
|
|
99
|
+
* fixed 1000 ms whatever `retryDelay` was, so `retryDelay: 50` could wait
|
|
100
|
+
* a second.
|
|
75
101
|
*/
|
|
76
102
|
export function calculateRetryDelay(attempt, retry) {
|
|
77
103
|
const base = retry.retryDelay ?? 1000;
|
|
78
104
|
const max = retry.maxRetryDelay ?? 30_000;
|
|
79
105
|
const ms = retry.backoff === "fixed" ? base : base * Math.pow(2, attempt);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return Math.min(ms + Math.random() * 1000, max);
|
|
106
|
+
const capped = Math.max(0, Math.min(ms, max));
|
|
107
|
+
return retry.jitter === false ? capped : Math.random() * capped;
|
|
83
108
|
}
|
|
84
109
|
/**
|
|
85
110
|
* Delay for a given number of milliseconds.
|
|
@@ -8,14 +8,28 @@ export type HttpClientBody = BodyInit | Record<string, unknown> | readonly unkno
|
|
|
8
8
|
export type HttpClientQueryValue = string | number | boolean | bigint | null | undefined | readonly (string | number | boolean | bigint)[];
|
|
9
9
|
export type HttpClientQuery = Readonly<Record<string, HttpClientQueryValue>> | URLSearchParams;
|
|
10
10
|
export type HttpResponseType = "auto" | "json" | "text" | "arrayBuffer" | "blob" | "response";
|
|
11
|
+
/**
|
|
12
|
+
* Retry policy for the HTTP client. See the README's "Retries and backoff".
|
|
13
|
+
*/
|
|
11
14
|
export interface HttpRetryOptions {
|
|
15
|
+
/** Retries after the first attempt (default 0: no retries). */
|
|
12
16
|
readonly retries?: number;
|
|
17
|
+
/** Base delay in ms (default 1000). */
|
|
13
18
|
readonly retryDelay?: number;
|
|
19
|
+
/** Upper bound on any single wait, in ms (default 30000). */
|
|
14
20
|
readonly maxRetryDelay?: number;
|
|
21
|
+
/** Statuses that are retried (default 429, 502, 503, 504). */
|
|
15
22
|
readonly retryStatusCodes?: readonly number[];
|
|
23
|
+
/** Methods that may be retried at all (default GET, HEAD, OPTIONS). */
|
|
16
24
|
readonly retryMethods?: readonly HttpClientMethod[];
|
|
25
|
+
/** Retry transport failures such as a refused connection (default true). */
|
|
17
26
|
readonly retryOnNetworkError?: boolean;
|
|
27
|
+
/** Retry a request that hit `timeout` (default: `retryOnNetworkError`). */
|
|
28
|
+
readonly retryOnTimeout?: boolean;
|
|
29
|
+
/** `"exponential"` doubles the delay per attempt (default); `"fixed"` does not. */
|
|
18
30
|
readonly backoff?: "fixed" | "exponential";
|
|
31
|
+
/** Wait a random 0..delay instead of exactly the delay (default true). */
|
|
32
|
+
readonly jitter?: boolean;
|
|
19
33
|
}
|
|
20
34
|
export interface HttpClientResponse<T = unknown> {
|
|
21
35
|
readonly data: T;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module httpErrors/base
|
|
8
8
|
*/
|
|
9
9
|
import { HttpError as BaseHttpError } from "@zudojs/errors";
|
|
10
|
-
import { normalizeHeaders, getStatusText } from "./httpError.util.js";
|
|
10
|
+
import { defaultErrorCode, normalizeHeaders, getStatusText, } from "./httpError.util.js";
|
|
11
11
|
/**
|
|
12
12
|
* HTTP error with response-specific properties.
|
|
13
13
|
*
|
|
@@ -31,7 +31,7 @@ export class HttpError extends BaseHttpError {
|
|
|
31
31
|
const statusText = getStatusText(status);
|
|
32
32
|
super(message ?? statusText, {
|
|
33
33
|
statusCode: status,
|
|
34
|
-
code: options.code,
|
|
34
|
+
code: options.code ?? defaultErrorCode(status),
|
|
35
35
|
expose: options.expose ?? status < 500,
|
|
36
36
|
metadata: options.metadata,
|
|
37
37
|
cause: options.cause,
|
|
@@ -19,4 +19,12 @@ export declare function normalizeHeaders(headers: Record<string, string> | undef
|
|
|
19
19
|
* `statusText` and `message` as "Unknown Status".
|
|
20
20
|
*/
|
|
21
21
|
export declare function getStatusText(status: number): string;
|
|
22
|
+
/**
|
|
23
|
+
* The default error code for a status: its symbolic name, as the factories
|
|
24
|
+
* use (`415` gives `"UNSUPPORTED_MEDIA_TYPE"`, `404` `"NOT_FOUND"`).
|
|
25
|
+
* `undefined` for a status with no name, which keeps the shared default.
|
|
26
|
+
* `new HttpError(415, msg)` without a code used to report
|
|
27
|
+
* `ERR_OPERATION_FAILED`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function defaultErrorCode(status: number): string | undefined;
|
|
22
30
|
//# sourceMappingURL=httpError.util.d.ts.map
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module httpErrors/util
|
|
9
9
|
*/
|
|
10
10
|
import { getStatusText as lookupStatusText } from "../httpStatus/httpStatus.lookup.js";
|
|
11
|
+
import { statusName } from "../httpStatus/httpStatus.name.js";
|
|
11
12
|
/**
|
|
12
13
|
* Normalizes header keys to lowercase.
|
|
13
14
|
*/
|
|
@@ -33,4 +34,15 @@ export function normalizeHeaders(headers) {
|
|
|
33
34
|
export function getStatusText(status) {
|
|
34
35
|
return lookupStatusText(status);
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* The default error code for a status: its symbolic name, as the factories
|
|
39
|
+
* use (`415` gives `"UNSUPPORTED_MEDIA_TYPE"`, `404` `"NOT_FOUND"`).
|
|
40
|
+
* `undefined` for a status with no name, which keeps the shared default.
|
|
41
|
+
* `new HttpError(415, msg)` without a code used to report
|
|
42
|
+
* `ERR_OPERATION_FAILED`.
|
|
43
|
+
*/
|
|
44
|
+
export function defaultErrorCode(status) {
|
|
45
|
+
const name = statusName(status);
|
|
46
|
+
return name === "UNKNOWN" ? undefined : name;
|
|
47
|
+
}
|
|
36
48
|
//# sourceMappingURL=httpError.util.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serving web-standard fetch handlers from an `@zudojs/http` router.
|
|
3
|
+
*/
|
|
4
|
+
import type { HttpFetchHandler, HttpFetchMountTarget, MountFetchHandlerOptions } from "./httpFetchMount.type.js";
|
|
5
|
+
/**
|
|
6
|
+
* Serves a web-standard `(request: Request) => Promise<Response>` handler —
|
|
7
|
+
* an `@zudojs/rpc` server, `@zudojs/api` operations, any fetch-style app —
|
|
8
|
+
* under `basePath` of an `@zudojs/http` router or router group.
|
|
9
|
+
*
|
|
10
|
+
* The handler receives a `Request` with the original method, headers
|
|
11
|
+
* (connection-scoped ones removed), body and query, and a `signal` that
|
|
12
|
+
* aborts when the client disconnects. Its `Response` is streamed back with
|
|
13
|
+
* status, status text and headers intact, every `Set-Cookie` kept separate.
|
|
14
|
+
* A handler that throws, or returns something that is not a `Response`,
|
|
15
|
+
* fails the request like any other route (500 unless the error carries a
|
|
16
|
+
* status).
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* mountFetchHandler(router, "/rpc", createRPCFetchHandler(rpcServer));
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @returns A function that removes the mount.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mountFetchHandler(target: HttpFetchMountTarget, basePath: string, handler: HttpFetchHandler, options?: MountFetchHandlerOptions): () => void;
|
|
25
|
+
//# sourceMappingURL=httpFetchMount.core.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serving web-standard fetch handlers from an `@zudojs/http` router.
|
|
3
|
+
*/
|
|
4
|
+
import { webResponseToContext } from "../httpResponse/httpResponse.fromWeb.js";
|
|
5
|
+
import { normalizeRoutePattern } from "../httpRouter/core/util/httpRoute.util.js";
|
|
6
|
+
import { contextUrl, toWebRequest } from "./httpFetchMount.request.js";
|
|
7
|
+
/** The wildcard every mount registers; its name is reserved for mounts. */
|
|
8
|
+
const MOUNT_WILDCARD = "*fetchMountPath";
|
|
9
|
+
function segments(path) {
|
|
10
|
+
return path.split("/").filter((segment) => segment !== "");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Removes the first `count` path segments, keeping a trailing slash.
|
|
14
|
+
*
|
|
15
|
+
* Counted in segments rather than characters so it holds for a mount under
|
|
16
|
+
* a group prefix, a parameterised mount path and a case-insensitive match.
|
|
17
|
+
*/
|
|
18
|
+
function splitPrefix(pathname, count) {
|
|
19
|
+
const parts = segments(pathname);
|
|
20
|
+
const prefix = `/${parts.slice(0, count).join("/")}`;
|
|
21
|
+
const rest = parts.slice(count);
|
|
22
|
+
if (rest.length === 0)
|
|
23
|
+
return [prefix, "/"];
|
|
24
|
+
return [prefix, `/${rest.join("/")}${pathname.endsWith("/") ? "/" : ""}`];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Serves a web-standard `(request: Request) => Promise<Response>` handler —
|
|
28
|
+
* an `@zudojs/rpc` server, `@zudojs/api` operations, any fetch-style app —
|
|
29
|
+
* under `basePath` of an `@zudojs/http` router or router group.
|
|
30
|
+
*
|
|
31
|
+
* The handler receives a `Request` with the original method, headers
|
|
32
|
+
* (connection-scoped ones removed), body and query, and a `signal` that
|
|
33
|
+
* aborts when the client disconnects. Its `Response` is streamed back with
|
|
34
|
+
* status, status text and headers intact, every `Set-Cookie` kept separate.
|
|
35
|
+
* A handler that throws, or returns something that is not a `Response`,
|
|
36
|
+
* fails the request like any other route (500 unless the error carries a
|
|
37
|
+
* status).
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* mountFetchHandler(router, "/rpc", createRPCFetchHandler(rpcServer));
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @returns A function that removes the mount.
|
|
44
|
+
*/
|
|
45
|
+
export function mountFetchHandler(target, basePath, handler, options = {}) {
|
|
46
|
+
if (typeof handler !== "function") {
|
|
47
|
+
throw new TypeError("mountFetchHandler requires a handler function.");
|
|
48
|
+
}
|
|
49
|
+
const base = normalizeRoutePattern(basePath);
|
|
50
|
+
const pattern = base === "/" ? `/${MOUNT_WILDCARD}` : `${base}/${MOUNT_WILDCARD}`;
|
|
51
|
+
const strip = options.stripPrefix ?? true;
|
|
52
|
+
const routeHandler = async (context) => {
|
|
53
|
+
const url = contextUrl(context.request, options.origin);
|
|
54
|
+
const headers = {};
|
|
55
|
+
if (strip) {
|
|
56
|
+
const mounted = segments(context.route.path).length - 1;
|
|
57
|
+
const [prefix, rest] = splitPrefix(url.pathname, mounted);
|
|
58
|
+
url.pathname = rest;
|
|
59
|
+
if (mounted > 0)
|
|
60
|
+
headers["x-forwarded-prefix"] = prefix;
|
|
61
|
+
}
|
|
62
|
+
const request = toWebRequest(context.request, {
|
|
63
|
+
url,
|
|
64
|
+
signal: context.signal,
|
|
65
|
+
headers,
|
|
66
|
+
});
|
|
67
|
+
const response = await handler(request);
|
|
68
|
+
if (!(response instanceof Response)) {
|
|
69
|
+
throw new TypeError(`The fetch handler mounted at ${base} returned ${typeof response}, not a Response.`);
|
|
70
|
+
}
|
|
71
|
+
return webResponseToContext(response);
|
|
72
|
+
};
|
|
73
|
+
const routeOptions = {
|
|
74
|
+
openapi: options.openapi ?? false,
|
|
75
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
76
|
+
...(options.middleware ? { middleware: options.middleware } : {}),
|
|
77
|
+
};
|
|
78
|
+
const removers = (options.methods ?? ["*"]).map((method) => target.on(method, pattern, routeHandler, routeOptions));
|
|
79
|
+
return () => {
|
|
80
|
+
for (const remove of removers)
|
|
81
|
+
remove();
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=httpFetchMount.core.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversion of a request context into a WHATWG `Request`.
|
|
3
|
+
*/
|
|
4
|
+
import type { HttpRequestContext } from "../httpRequest/httpRequest.context.js";
|
|
5
|
+
import type { ToWebRequestOptions } from "./httpFetchMount.type.js";
|
|
6
|
+
/**
|
|
7
|
+
* The context's URL as an absolute URL. The request target is never read
|
|
8
|
+
* as an authority; the origin is `origin` when given, else the context's.
|
|
9
|
+
*/
|
|
10
|
+
export declare function contextUrl(context: HttpRequestContext, origin?: string): URL;
|
|
11
|
+
/**
|
|
12
|
+
* Builds a web `Request` from a request context.
|
|
13
|
+
*
|
|
14
|
+
* Headers are copied except connection-scoped ones; the body is the one the
|
|
15
|
+
* adapter read (bytes, text, a stream, or a parsed value re-encoded as JSON,
|
|
16
|
+
* labelled `application/json` unless it already had a JSON content type)
|
|
17
|
+
* and is never attached to `GET` / `HEAD`; the signal aborts the request
|
|
18
|
+
* when the client disconnects.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toWebRequest(context: HttpRequestContext, options?: ToWebRequestOptions): Request;
|
|
21
|
+
//# sourceMappingURL=httpFetchMount.request.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversion of a request context into a WHATWG `Request`.
|
|
3
|
+
*/
|
|
4
|
+
import { parseRequestTarget } from "../httpRequest/target/httpRequest.target.js";
|
|
5
|
+
/**
|
|
6
|
+
* Connection-scoped headers. They describe the hop between the client and
|
|
7
|
+
* this server, not the request, and `content-length` is recomputed from the
|
|
8
|
+
* body actually passed on.
|
|
9
|
+
*/
|
|
10
|
+
const HOP_BY_HOP = new Set([
|
|
11
|
+
"connection",
|
|
12
|
+
"keep-alive",
|
|
13
|
+
"proxy-connection",
|
|
14
|
+
"transfer-encoding",
|
|
15
|
+
"te",
|
|
16
|
+
"trailer",
|
|
17
|
+
"upgrade",
|
|
18
|
+
"content-length",
|
|
19
|
+
]);
|
|
20
|
+
const DEFAULT_ORIGIN = "http://localhost";
|
|
21
|
+
const JSON_TYPE = /^application\/(?:[\w.+-]+\+)?json(?:\s*;|$)/i;
|
|
22
|
+
/**
|
|
23
|
+
* The origin of the web request: `pinned` when the caller configured one,
|
|
24
|
+
* otherwise the context's own (from the `Host` header, or a trusted
|
|
25
|
+
* proxy's `X-Forwarded-Host`), otherwise `http://localhost`.
|
|
26
|
+
*/
|
|
27
|
+
function resolveOrigin(context, pinned) {
|
|
28
|
+
for (const candidate of [pinned, context.origin, DEFAULT_ORIGIN]) {
|
|
29
|
+
if (candidate === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
return new URL(candidate).origin;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return DEFAULT_ORIGIN;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The context's URL as an absolute URL. The request target is never read
|
|
42
|
+
* as an authority; the origin is `origin` when given, else the context's.
|
|
43
|
+
*/
|
|
44
|
+
export function contextUrl(context, origin) {
|
|
45
|
+
const target = parseRequestTarget(context.url);
|
|
46
|
+
const url = new URL(resolveOrigin(context, origin));
|
|
47
|
+
url.pathname = target.pathname;
|
|
48
|
+
url.search = target.search;
|
|
49
|
+
return url;
|
|
50
|
+
}
|
|
51
|
+
function toBody(body, headers) {
|
|
52
|
+
if (body === undefined || body === null)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (typeof body === "string")
|
|
55
|
+
return body;
|
|
56
|
+
if (body instanceof Uint8Array)
|
|
57
|
+
return body;
|
|
58
|
+
if (body instanceof ArrayBuffer)
|
|
59
|
+
return body;
|
|
60
|
+
if (body instanceof ReadableStream ||
|
|
61
|
+
body instanceof Blob ||
|
|
62
|
+
body instanceof FormData ||
|
|
63
|
+
body instanceof URLSearchParams) {
|
|
64
|
+
return body;
|
|
65
|
+
}
|
|
66
|
+
if (!JSON_TYPE.test(headers.get("content-type") ?? "")) {
|
|
67
|
+
headers.set("content-type", "application/json");
|
|
68
|
+
}
|
|
69
|
+
return JSON.stringify(body);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Builds a web `Request` from a request context.
|
|
73
|
+
*
|
|
74
|
+
* Headers are copied except connection-scoped ones; the body is the one the
|
|
75
|
+
* adapter read (bytes, text, a stream, or a parsed value re-encoded as JSON,
|
|
76
|
+
* labelled `application/json` unless it already had a JSON content type)
|
|
77
|
+
* and is never attached to `GET` / `HEAD`; the signal aborts the request
|
|
78
|
+
* when the client disconnects.
|
|
79
|
+
*/
|
|
80
|
+
export function toWebRequest(context, options = {}) {
|
|
81
|
+
const method = context.method.toUpperCase();
|
|
82
|
+
const headers = new Headers();
|
|
83
|
+
for (const [name, value] of Object.entries(context.headers)) {
|
|
84
|
+
if (!HOP_BY_HOP.has(name.toLowerCase()))
|
|
85
|
+
headers.append(name, value);
|
|
86
|
+
}
|
|
87
|
+
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
88
|
+
headers.set(name, value);
|
|
89
|
+
}
|
|
90
|
+
const body = method === "GET" || method === "HEAD" ? undefined : toBody(context.body, headers);
|
|
91
|
+
const init = {
|
|
92
|
+
method,
|
|
93
|
+
headers,
|
|
94
|
+
...(body === undefined ? {} : { body }),
|
|
95
|
+
...(body instanceof ReadableStream ? { duplex: "half" } : {}),
|
|
96
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
97
|
+
};
|
|
98
|
+
return new Request(options.url ?? contextUrl(context, options.origin), init);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=httpFetchMount.request.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for serving web-standard fetch handlers from a router.
|
|
3
|
+
*/
|
|
4
|
+
import type { HttpMethod, HttpRouteOpenAPI, RouteOptions, RouterHandler } from "../httpRouter/core/types/httpRouter.type.js";
|
|
5
|
+
import type { HttpMiddleware } from "../httpMiddleware/httpMiddleware.type.js";
|
|
6
|
+
/** A web-standard handler: `Request` in, `Response` out. */
|
|
7
|
+
export type HttpFetchHandler = (request: Request) => Response | Promise<Response>;
|
|
8
|
+
/** Anything routes can be registered on: an `HttpRouter` or a router group. */
|
|
9
|
+
export interface HttpFetchMountTarget {
|
|
10
|
+
on(method: HttpMethod | "*", path: string, handler: RouterHandler, options?: RouteOptions): () => void;
|
|
11
|
+
}
|
|
12
|
+
/** Options for `mountFetchHandler`. */
|
|
13
|
+
export interface MountFetchHandlerOptions {
|
|
14
|
+
/** Methods routed to the handler. Default: every method. */
|
|
15
|
+
readonly methods?: readonly HttpMethod[];
|
|
16
|
+
/**
|
|
17
|
+
* Remove the mount path from the URL the handler sees, so a handler
|
|
18
|
+
* mounted at `/rpc` receives `/users.get` for `/rpc/users.get`. The
|
|
19
|
+
* removed prefix is passed as `x-forwarded-prefix`. Default: `true`.
|
|
20
|
+
*/
|
|
21
|
+
readonly stripPrefix?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Origin of the handler's `request.url`, e.g. `https://api.example.com`.
|
|
24
|
+
* When set it is used for every request. When not, the origin comes from
|
|
25
|
+
* the request — its `Host` header, or `X-Forwarded-Host` from a trusted
|
|
26
|
+
* proxy — which the client chooses; set this whenever the handler builds
|
|
27
|
+
* absolute URLs (redirects, callback or reset links) or checks
|
|
28
|
+
* `Origin` against its own. Falls back to `http://localhost`.
|
|
29
|
+
*/
|
|
30
|
+
readonly origin?: string;
|
|
31
|
+
/** Middleware run before the handler. */
|
|
32
|
+
readonly middleware?: readonly HttpMiddleware[];
|
|
33
|
+
/** Route name. */
|
|
34
|
+
readonly name?: string;
|
|
35
|
+
/**
|
|
36
|
+
* OpenAPI documentation for the mount. Default: `false` — a mounted
|
|
37
|
+
* handler documents its own operations (see `@zudojs/openapi`'s
|
|
38
|
+
* `createOpenAPIDocumentFromRoutes`).
|
|
39
|
+
*/
|
|
40
|
+
readonly openapi?: HttpRouteOpenAPI;
|
|
41
|
+
}
|
|
42
|
+
/** Options for `toWebRequest`. */
|
|
43
|
+
export interface ToWebRequestOptions {
|
|
44
|
+
/** Overrides the request URL (absolute). */
|
|
45
|
+
readonly url?: string | URL;
|
|
46
|
+
/** Aborts the web request; normally the router context's signal. */
|
|
47
|
+
readonly signal?: AbortSignal;
|
|
48
|
+
/**
|
|
49
|
+
* Origin of the web request. Overrides the context's protocol and host
|
|
50
|
+
* (which come from the client's `Host` header) when set.
|
|
51
|
+
*/
|
|
52
|
+
readonly origin?: string;
|
|
53
|
+
/** Extra headers set on the web request, replacing same-named ones. */
|
|
54
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=httpFetchMount.type.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/http/httpFetchMount
|
|
3
|
+
*
|
|
4
|
+
* Serves web-standard fetch handlers (`Request` → `Response`) from an
|
|
5
|
+
* `@zudojs/http` router: `mountFetchHandler` registers one under a path,
|
|
6
|
+
* `toWebRequest` converts a request context into a `Request` on its own.
|
|
7
|
+
*/
|
|
8
|
+
export { mountFetchHandler } from "./httpFetchMount.core.js";
|
|
9
|
+
export { toWebRequest, contextUrl } from "./httpFetchMount.request.js";
|
|
10
|
+
export type { HttpFetchHandler, HttpFetchMountTarget, MountFetchHandlerOptions, ToWebRequestOptions, } from "./httpFetchMount.type.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zudojs/http/httpFetchMount
|
|
3
|
+
*
|
|
4
|
+
* Serves web-standard fetch handlers (`Request` → `Response`) from an
|
|
5
|
+
* `@zudojs/http` router: `mountFetchHandler` registers one under a path,
|
|
6
|
+
* `toWebRequest` converts a request context into a `Request` on its own.
|
|
7
|
+
*/
|
|
8
|
+
export { mountFetchHandler } from "./httpFetchMount.core.js";
|
|
9
|
+
export { toWebRequest, contextUrl } from "./httpFetchMount.request.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Transport-specific socket management belongs to the server/client adapter
|
|
8
8
|
* layer. This module only handles HTTP-level semantics.
|
|
9
9
|
*/
|
|
10
|
+
import { assertSafeHeaderValue, escapeHeaderQuotedString, } from "../httpHeaders/security/index.js";
|
|
10
11
|
/* -------------------------------------------------------------------------- */
|
|
11
12
|
/* Constants */
|
|
12
13
|
/* -------------------------------------------------------------------------- */
|
|
@@ -121,7 +122,9 @@ export function formatKeepAliveHeader(parameters = {}) {
|
|
|
121
122
|
parts.push(`${key}=${quoteIfNeeded(value)}`);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
+
const header = parts.join(", ");
|
|
126
|
+
assertSafeHeaderValue(header);
|
|
127
|
+
return header;
|
|
125
128
|
}
|
|
126
129
|
/* -------------------------------------------------------------------------- */
|
|
127
130
|
/* Connection Header */
|
|
@@ -348,10 +351,19 @@ function unquote(value) {
|
|
|
348
351
|
}
|
|
349
352
|
return trimmed;
|
|
350
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Emits a Keep-Alive parameter value.
|
|
356
|
+
*
|
|
357
|
+
* Quoting does not neutralise a CR or LF, so the control character used to
|
|
358
|
+
* survive into the field value. `escapeHeaderQuotedString` rejects it, the
|
|
359
|
+
* same helper every other quoted-parameter emitter in this package uses.
|
|
360
|
+
*
|
|
361
|
+
* @throws {TypeError} If the value contains a forbidden control character.
|
|
362
|
+
*/
|
|
351
363
|
function quoteIfNeeded(value) {
|
|
352
364
|
if (/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value)) {
|
|
353
365
|
return value;
|
|
354
366
|
}
|
|
355
|
-
return `"${value
|
|
367
|
+
return `"${escapeHeaderQuotedString(value)}"`;
|
|
356
368
|
}
|
|
357
369
|
//# sourceMappingURL=httpKeepAlive.core.js.map
|