@johntalton/http-util 6.0.0 → 6.1.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/package.json +16 -2
- package/src/defs.js +34 -1
- package/src/headers/accept-encoding.js +9 -12
- package/src/headers/accept-language.js +10 -4
- package/src/headers/accept.js +0 -28
- package/src/headers/cache-control.js +0 -6
- package/src/headers/clear-site-data.js +4 -10
- package/src/headers/client-hints.js +9 -2
- package/src/headers/conditional.js +169 -105
- package/src/headers/content-disposition.js +0 -14
- package/src/headers/content-range.js +0 -17
- package/src/headers/content-type.js +1 -1
- package/src/headers/forwarded.js +2 -33
- package/src/headers/link.js +10 -11
- package/src/headers/multipart.js +17 -9
- package/src/headers/preference.js +0 -43
- package/src/headers/range.js +3 -31
- package/src/headers/rate-limit.js +6 -1
- package/src/headers/server-timing.js +1 -14
- package/src/headers/strict-transport-security.js +1 -0
- package/src/headers/util/mime.js +2 -2
- package/src/headers/util/whitespace.js +3 -1
- package/src/response/2xx/bytes.js +41 -6
- package/src/response/2xx/created.js +26 -5
- package/src/response/2xx/json.js +36 -4
- package/src/response/2xx/no-content.js +25 -5
- package/src/response/2xx/partial-content.js +38 -8
- package/src/response/2xx/preflight.js +26 -7
- package/src/response/3xx/found.js +23 -0
- package/src/response/3xx/not-modified.js +27 -3
- package/src/response/4xx/bad-request.js +19 -0
- package/src/response/4xx/not-acceptable.js +12 -1
- package/src/response/4xx/not-allowed.js +15 -4
- package/src/response/4xx/payment-required.js +17 -0
- package/src/response/4xx/precondition-failed.js +32 -3
- package/src/response/4xx/range-not-satisfiable.js +12 -1
- package/src/response/4xx/too-many-requests.js +18 -1
- package/src/response/4xx/unauthorized.js +1 -1
- package/src/response/4xx/unsupported-media.js +19 -2
- package/src/response/5xx/unavailable.js +13 -1
- package/src/response/index.js +4 -0
- package/src/response/response.js +6 -0
- package/src/response/send-util.js +23 -10
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { send } from '../send-util.js'
|
|
10
10
|
|
|
11
11
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
12
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
12
|
+
/** @import { AcceptRangeUnits, SendInfo, Metadata } from '../../defs.js' */
|
|
13
13
|
|
|
14
14
|
const {
|
|
15
15
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
@@ -27,18 +27,37 @@ const { HTTP_STATUS_OK } = http2.constants
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* @param {ServerHttp2Stream} stream
|
|
30
|
-
* @param {Array<string>}
|
|
30
|
+
* @param {Array<string>} supportedMethods
|
|
31
31
|
* @param {Array<string>|undefined} supportedQueryTypes
|
|
32
|
-
* @param {
|
|
32
|
+
* @param {AcceptRangeUnits|undefined} acceptRanges
|
|
33
33
|
* @param {Metadata} meta
|
|
34
34
|
*/
|
|
35
|
-
export function sendPreflight(stream,
|
|
36
|
-
|
|
35
|
+
export function sendPreflight(stream, supportedMethods, supportedQueryTypes, acceptRanges, meta) {
|
|
36
|
+
_sendPreflight(stream, {
|
|
37
|
+
supportedMethods,
|
|
38
|
+
supportedQueryTypes,
|
|
39
|
+
acceptRanges
|
|
40
|
+
}, meta)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {ServerHttp2Stream} stream
|
|
45
|
+
* @param {Pick<SendInfo, 'supportedMethods' | 'supportedQueryTypes' | 'acceptRanges'>} info
|
|
46
|
+
* @param {Metadata} meta
|
|
47
|
+
*/
|
|
48
|
+
export function _sendPreflight(stream, info, meta) {
|
|
49
|
+
const {
|
|
50
|
+
supportedMethods,
|
|
51
|
+
supportedQueryTypes,
|
|
52
|
+
acceptRanges
|
|
53
|
+
} = info
|
|
54
|
+
|
|
55
|
+
const supportsQuery = supportedMethods.includes(HTTP_METHOD_QUERY) && supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
|
|
37
56
|
const exposedHeadersAcceptQuery = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY ] : []
|
|
38
57
|
const exposedHeaders = acceptRanges === undefined ? exposedHeadersAcceptQuery : [ HTTP2_HEADER_ACCEPT_RANGES, ...exposedHeadersAcceptQuery ]
|
|
39
58
|
|
|
40
59
|
send(stream, HTTP_STATUS_OK, {
|
|
41
|
-
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]:
|
|
60
|
+
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: supportedMethods.join(','),
|
|
42
61
|
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: [
|
|
43
62
|
HTTP2_HEADER_IF_MATCH,
|
|
44
63
|
HTTP2_HEADER_IF_NONE_MATCH,
|
|
@@ -49,7 +68,7 @@ export function sendPreflight(stream, methods, supportedQueryTypes, acceptRanges
|
|
|
49
68
|
].join(','),
|
|
50
69
|
[HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS,
|
|
51
70
|
[HTTP2_HEADER_ACCEPT_RANGES]: acceptRanges,
|
|
52
|
-
[HTTP_HEADER_ACCEPT_QUERY]: supportedQueryTypes?.join(',')
|
|
71
|
+
[HTTP_HEADER_ACCEPT_QUERY]: supportedQueryTypes?.join(',') // todo should empty array return undef
|
|
53
72
|
// Access-Control-Allow-Credentials
|
|
54
73
|
}, exposedHeaders, undefined, undefined, meta)
|
|
55
74
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
|
|
3
|
+
import { send } from '../send-util.js'
|
|
4
|
+
|
|
5
|
+
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
HTTP2_HEADER_LOCATION
|
|
10
|
+
} = http2.constants
|
|
11
|
+
|
|
12
|
+
const { HTTP_STATUS_FOUND } = http2.constants
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {ServerHttp2Stream} stream
|
|
16
|
+
* @param {URL} location
|
|
17
|
+
* @param {Metadata} meta
|
|
18
|
+
*/
|
|
19
|
+
export function sendFound(stream, location, meta) {
|
|
20
|
+
send(stream, HTTP_STATUS_FOUND, {
|
|
21
|
+
[HTTP2_HEADER_LOCATION]: location.href
|
|
22
|
+
}, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
|
|
23
|
+
}
|
|
@@ -5,13 +5,14 @@ import { Conditional } from '../../headers/conditional.js'
|
|
|
5
5
|
import { send } from '../send-util.js'
|
|
6
6
|
|
|
7
7
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
8
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
9
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
8
|
+
/** @import { SendContent, Metadata } from '../../defs.js' */
|
|
9
|
+
/** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
|
|
10
10
|
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
11
11
|
|
|
12
12
|
const {
|
|
13
13
|
HTTP2_HEADER_AGE,
|
|
14
14
|
HTTP2_HEADER_ETAG,
|
|
15
|
+
HTTP2_HEADER_LAST_MODIFIED,
|
|
15
16
|
HTTP2_HEADER_VARY,
|
|
16
17
|
HTTP2_HEADER_CACHE_CONTROL
|
|
17
18
|
} = http2.constants
|
|
@@ -21,15 +22,38 @@ const { HTTP_STATUS_NOT_MODIFIED } = http2.constants
|
|
|
21
22
|
/**
|
|
22
23
|
* @param {ServerHttp2Stream} stream
|
|
23
24
|
* @param {EtagItem|undefined} etag
|
|
25
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
24
26
|
* @param {number|undefined} age
|
|
25
27
|
* @param {CacheControlOptions} cacheControl
|
|
26
28
|
* @param {Metadata} meta
|
|
27
29
|
*/
|
|
28
|
-
export function sendNotModified(stream, etag, age, cacheControl, meta) {
|
|
30
|
+
export function sendNotModified(stream, etag, lastModified, age, cacheControl, meta) {
|
|
31
|
+
_sendNotModified(stream, {
|
|
32
|
+
etag,
|
|
33
|
+
lastModified,
|
|
34
|
+
age,
|
|
35
|
+
cacheControl
|
|
36
|
+
}, meta)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {ServerHttp2Stream} stream
|
|
41
|
+
* @param {Pick<SendContent, 'etag' | 'lastModified' | 'age' | 'cacheControl'>} content
|
|
42
|
+
* @param {Metadata} meta
|
|
43
|
+
*/
|
|
44
|
+
export function _sendNotModified(stream, content, meta) {
|
|
45
|
+
const {
|
|
46
|
+
etag,
|
|
47
|
+
lastModified,
|
|
48
|
+
age,
|
|
49
|
+
cacheControl
|
|
50
|
+
} = content
|
|
51
|
+
|
|
29
52
|
send(stream, HTTP_STATUS_NOT_MODIFIED, {
|
|
30
53
|
[HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
|
|
31
54
|
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
32
55
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
56
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
|
|
33
57
|
[HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`
|
|
34
58
|
}, [ HTTP2_HEADER_AGE ], undefined, undefined, meta)
|
|
35
59
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
|
|
3
|
+
import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
|
|
4
|
+
import { send } from '../send-util.js'
|
|
5
|
+
|
|
6
|
+
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
+
|
|
9
|
+
const { HTTP_STATUS_BAD_REQUEST } = http2.constants
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {ServerHttp2Stream} stream
|
|
13
|
+
* @param {string} message
|
|
14
|
+
* @param {Metadata} meta
|
|
15
|
+
*/
|
|
16
|
+
export function sendBadRequest(stream, message, meta) {
|
|
17
|
+
send(stream, HTTP_STATUS_BAD_REQUEST, {
|
|
18
|
+
}, [ ], CONTENT_TYPE_TEXT, message, meta)
|
|
19
|
+
}
|
|
@@ -4,7 +4,7 @@ import { CONTENT_TYPE_JSON } from '../../headers/content-type.js'
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { SendInfo, Metadata } from '../../defs.js' */
|
|
8
8
|
|
|
9
9
|
const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
|
|
10
10
|
|
|
@@ -14,6 +14,17 @@ const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
|
|
|
14
14
|
* @param {Metadata} meta
|
|
15
15
|
*/
|
|
16
16
|
export function sendNotAcceptable(stream, supportedTypes, meta) {
|
|
17
|
+
_sendNotAcceptable(stream, { supportedTypes }, meta)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {ServerHttp2Stream} stream
|
|
22
|
+
* @param {Pick<SendInfo, 'supportedTypes'>} info
|
|
23
|
+
* @param {Metadata} meta
|
|
24
|
+
*/
|
|
25
|
+
export function _sendNotAcceptable(stream, info, meta) {
|
|
26
|
+
const { supportedTypes } = info
|
|
27
|
+
|
|
17
28
|
const supportedTypesList = Array.isArray(supportedTypes) ? supportedTypes : [ supportedTypes ]
|
|
18
29
|
const has = supportedTypesList.length > 0
|
|
19
30
|
|
|
@@ -3,7 +3,7 @@ import http2 from 'node:http2'
|
|
|
3
3
|
import { send } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
6
|
+
/** @import { SendInfo, Metadata } from '../../defs.js' */
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
HTTP_STATUS_METHOD_NOT_ALLOWED
|
|
@@ -13,11 +13,22 @@ const { HTTP2_HEADER_ALLOW } = http2.constants
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @param {ServerHttp2Stream} stream
|
|
16
|
-
* @param {Array<string>}
|
|
16
|
+
* @param {Array<string>} supportedMethods
|
|
17
17
|
* @param {Metadata} meta
|
|
18
18
|
*/
|
|
19
|
-
export function sendNotAllowed(stream,
|
|
19
|
+
export function sendNotAllowed(stream, supportedMethods, meta) {
|
|
20
|
+
_sendNotAllowed(stream, { supportedMethods }, meta)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {ServerHttp2Stream} stream
|
|
25
|
+
* @param {Pick<SendInfo, 'supportedMethods'>} info
|
|
26
|
+
* @param {Metadata} meta
|
|
27
|
+
*/
|
|
28
|
+
export function _sendNotAllowed(stream, info, meta) {
|
|
29
|
+
const { supportedMethods } = info
|
|
30
|
+
|
|
20
31
|
send(stream, HTTP_STATUS_METHOD_NOT_ALLOWED, {
|
|
21
|
-
[HTTP2_HEADER_ALLOW]:
|
|
32
|
+
[HTTP2_HEADER_ALLOW]: supportedMethods.join(',')
|
|
22
33
|
}, [ HTTP2_HEADER_ALLOW ], undefined, undefined, meta)
|
|
23
34
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
|
|
3
|
+
import { send } from '../send-util.js'
|
|
4
|
+
|
|
5
|
+
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
+
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
|
|
8
|
+
const { HTTP_STATUS_PAYMENT_REQUIRED } = http2.constants
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {ServerHttp2Stream} stream
|
|
12
|
+
* @param {Metadata} meta
|
|
13
|
+
*/
|
|
14
|
+
export function sendPaymentRequired(stream, meta) {
|
|
15
|
+
send(stream, HTTP_STATUS_PAYMENT_REQUIRED, {
|
|
16
|
+
}, [ ], undefined, undefined, meta)
|
|
17
|
+
}
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
|
+
import { Conditional } from '../../headers/conditional.js'
|
|
3
4
|
import { send } from '../send-util.js'
|
|
4
5
|
|
|
5
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { SendContent, Metadata } from '../../defs.js' */
|
|
8
|
+
/** @import { EtagItem ,IMFFixDateInput } from '../../headers/conditional.js' */
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
HTTP2_HEADER_ETAG,
|
|
12
|
+
HTTP2_HEADER_LAST_MODIFIED
|
|
13
|
+
} = http2.constants
|
|
7
14
|
|
|
8
15
|
const { HTTP_STATUS_PRECONDITION_FAILED } = http2.constants
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* @param {ServerHttp2Stream} stream
|
|
19
|
+
* @param {EtagItem|undefined} etag
|
|
20
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
21
|
+
* @param {Metadata} meta
|
|
22
|
+
*/
|
|
23
|
+
export function sendPreconditionFailed(stream, etag, lastModified, meta) {
|
|
24
|
+
_sendPreconditionFailed(stream, {
|
|
25
|
+
etag,
|
|
26
|
+
lastModified
|
|
27
|
+
}, meta)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {ServerHttp2Stream} stream
|
|
32
|
+
* @param {Pick<SendContent, 'etag' | 'lastModified'>} content
|
|
12
33
|
* @param {Metadata} meta
|
|
13
34
|
*/
|
|
14
|
-
export function
|
|
15
|
-
|
|
35
|
+
export function _sendPreconditionFailed(stream, content, meta) {
|
|
36
|
+
const {
|
|
37
|
+
etag,
|
|
38
|
+
lastModified
|
|
39
|
+
} = content
|
|
40
|
+
|
|
41
|
+
send(stream, HTTP_STATUS_PRECONDITION_FAILED, {
|
|
42
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
43
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified)
|
|
44
|
+
}, [], undefined, undefined, meta)
|
|
16
45
|
}
|
|
@@ -4,7 +4,7 @@ import { CONTENT_RANGE_UNKNOWN, ContentRange } from '../../headers/content-range
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { SendContent, Metadata } from '../../defs.js' */
|
|
8
8
|
/** @import { ContentRangeDirective} from '../../headers/content-range.js' */
|
|
9
9
|
|
|
10
10
|
const {
|
|
@@ -19,6 +19,17 @@ const { HTTP_STATUS_RANGE_NOT_SATISFIABLE } = http2.constants
|
|
|
19
19
|
* @param {Metadata} meta
|
|
20
20
|
*/
|
|
21
21
|
export function sendRangeNotSatisfiable(stream, rangeDirective, meta) {
|
|
22
|
+
_sendRangeNotSatisfiable(stream, { rangeDirective }, meta)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {ServerHttp2Stream} stream
|
|
27
|
+
* @param {Pick<SendContent, 'rangeDirective'>} content
|
|
28
|
+
* @param {Metadata} meta
|
|
29
|
+
*/
|
|
30
|
+
export function _sendRangeNotSatisfiable(stream, content, meta) {
|
|
31
|
+
const { rangeDirective } = content
|
|
32
|
+
|
|
22
33
|
/** @type {ContentRangeDirective} */
|
|
23
34
|
const invalidRange = { size: rangeDirective.size, range: CONTENT_RANGE_UNKNOWN }
|
|
24
35
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { send } from '../send-util.js'
|
|
11
11
|
|
|
12
12
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
13
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
13
|
+
/** @import { SendInfo, Metadata } from '../../defs.js' */
|
|
14
14
|
/** @import { RateLimitInfo, RateLimitPolicyInfo } from '../../headers/rate-limit.js' */
|
|
15
15
|
|
|
16
16
|
const {
|
|
@@ -26,6 +26,23 @@ const { HTTP_STATUS_TOO_MANY_REQUESTS } = http2.constants
|
|
|
26
26
|
* @param {Metadata} meta
|
|
27
27
|
*/
|
|
28
28
|
export function sendTooManyRequests(stream, limitInfo, policies, meta) {
|
|
29
|
+
_sendTooManyRequests(stream, {
|
|
30
|
+
limitInfo,
|
|
31
|
+
policies
|
|
32
|
+
}, meta)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {ServerHttp2Stream} stream
|
|
37
|
+
* @param {Pick<SendInfo, 'limitInfo' | 'policies'>} info
|
|
38
|
+
* @param {Metadata} meta
|
|
39
|
+
*/
|
|
40
|
+
export function _sendTooManyRequests(stream, info, meta) {
|
|
41
|
+
const {
|
|
42
|
+
limitInfo,
|
|
43
|
+
policies
|
|
44
|
+
} = info
|
|
45
|
+
|
|
29
46
|
send(stream, HTTP_STATUS_TOO_MANY_REQUESTS, {
|
|
30
47
|
[HTTP2_HEADER_RETRY_AFTER]: `${limitInfo.resetSeconds}`,
|
|
31
48
|
[HTTP_HEADER_RATE_LIMIT]: RateLimit.from(limitInfo),
|
|
@@ -20,6 +20,6 @@ const { HTTP_STATUS_UNAUTHORIZED } = http2.constants
|
|
|
20
20
|
*/
|
|
21
21
|
export function sendUnauthorized(stream, challenge, meta) {
|
|
22
22
|
send(stream, HTTP_STATUS_UNAUTHORIZED, {
|
|
23
|
-
[HTTP2_HEADER_WWW_AUTHENTICATE]: challenge?.map(Challenge.encode),
|
|
23
|
+
[HTTP2_HEADER_WWW_AUTHENTICATE]: challenge?.map(Challenge.encode), // todo stringify ?
|
|
24
24
|
}, [ HTTP2_HEADER_WWW_AUTHENTICATE ], undefined, undefined, meta)
|
|
25
25
|
}
|
|
@@ -4,7 +4,7 @@ import { HTTP_HEADER_ACCEPT_PATCH, HTTP_HEADER_ACCEPT_POST, HTTP_HEADER_ACCEPT_Q
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { SendInfo, Metadata } from '../../defs.js' */
|
|
8
8
|
|
|
9
9
|
const { HTTP2_METHOD_POST, HTTP2_METHOD_PATCH } = http2.constants
|
|
10
10
|
|
|
@@ -17,11 +17,28 @@ const { HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE } = http2.constants
|
|
|
17
17
|
* @param {Metadata} meta
|
|
18
18
|
*/
|
|
19
19
|
export function sendUnsupportedMediaType(stream, acceptableMediaType, supportedQueryTypes, meta) {
|
|
20
|
+
_sendUnsupportedMediaType(stream, {
|
|
21
|
+
acceptableMediaType,
|
|
22
|
+
supportedQueryTypes
|
|
23
|
+
}, meta)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {ServerHttp2Stream} stream
|
|
28
|
+
* @param {Pick<SendInfo, 'acceptableMediaType' | 'supportedQueryTypes'>} info
|
|
29
|
+
* @param {Metadata} meta
|
|
30
|
+
*/
|
|
31
|
+
export function _sendUnsupportedMediaType(stream, info, meta) {
|
|
32
|
+
const {
|
|
33
|
+
supportedQueryTypes,
|
|
34
|
+
acceptableMediaType
|
|
35
|
+
} = info
|
|
36
|
+
|
|
20
37
|
const supportsQuery = supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
|
|
21
38
|
const exposedHeaders = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY, HTTP_HEADER_ACCEPT_POST ] : [ HTTP_HEADER_ACCEPT_POST ]
|
|
22
39
|
|
|
23
40
|
const method = HTTP2_METHOD_POST // todo pass in as parameter or split acceptable to post and patch types
|
|
24
|
-
const acceptable = Array.isArray(acceptableMediaType) ? acceptableMediaType : [ acceptableMediaType ]
|
|
41
|
+
const acceptable = Array.isArray(acceptableMediaType) ? acceptableMediaType : [ acceptableMediaType ] // todo undefined creates array of one item
|
|
25
42
|
const acceptHeader = (method === HTTP2_METHOD_POST) ? HTTP_HEADER_ACCEPT_POST : HTTP_HEADER_ACCEPT_PATCH
|
|
26
43
|
const acceptValue = ((method === HTTP2_METHOD_POST) || (method === HTTP2_METHOD_PATCH)) ? acceptable.join(',') : undefined
|
|
27
44
|
|
|
@@ -4,7 +4,7 @@ import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
+
/** @import { SendInfo, Metadata } from '../../defs.js' */
|
|
8
8
|
|
|
9
9
|
const {
|
|
10
10
|
HTTP2_HEADER_RETRY_AFTER
|
|
@@ -19,6 +19,18 @@ const { HTTP_STATUS_SERVICE_UNAVAILABLE } = http2.constants
|
|
|
19
19
|
* @param {Metadata} meta
|
|
20
20
|
*/
|
|
21
21
|
export function sendUnavailable(stream, message, retryAfter, meta) {
|
|
22
|
+
_sendUnavailable(stream, message, { retryAfter }, meta)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {ServerHttp2Stream} stream
|
|
27
|
+
* @param {string|undefined} message
|
|
28
|
+
* @param {Pick<SendInfo, 'retryAfter'>} info
|
|
29
|
+
* @param {Metadata} meta
|
|
30
|
+
*/
|
|
31
|
+
export function _sendUnavailable(stream, message, info, meta) {
|
|
32
|
+
const { retryAfter } = info
|
|
33
|
+
|
|
22
34
|
send(stream, HTTP_STATUS_SERVICE_UNAVAILABLE, {
|
|
23
35
|
[HTTP2_HEADER_RETRY_AFTER]: Number.isInteger(retryAfter) ? `${retryAfter}` : undefined
|
|
24
36
|
}, [ HTTP2_HEADER_RETRY_AFTER ], CONTENT_TYPE_TEXT, message, meta)
|
package/src/response/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** biome-ignore-all lint/performance/noBarrelFile: entry point */
|
|
2
2
|
/** biome-ignore-all lint/performance/noReExportAll: entry point */
|
|
3
3
|
export * from '../defs.js'
|
|
4
|
+
export * from './header-util.js'
|
|
4
5
|
export * from './send-util.js'
|
|
5
6
|
// end common headers
|
|
6
7
|
|
|
@@ -13,12 +14,14 @@ export * from './2xx/partial-content.js'
|
|
|
13
14
|
export * from './2xx/preflight.js'
|
|
14
15
|
export * from './2xx/sse.js'
|
|
15
16
|
export * from './2xx/trace.js'
|
|
17
|
+
export * from './3xx/found.js'
|
|
16
18
|
export * from './3xx/moved-permanently.js'
|
|
17
19
|
export * from './3xx/multiple-choices.js'
|
|
18
20
|
export * from './3xx/not-modified.js'
|
|
19
21
|
export * from './3xx/permanent-redirect.js'
|
|
20
22
|
export * from './3xx/see-other.js'
|
|
21
23
|
export * from './3xx/temporary-redirect.js'
|
|
24
|
+
export * from './4xx/bad-request.js'
|
|
22
25
|
export * from './4xx/conflict.js'
|
|
23
26
|
export * from './4xx/content-too-large.js'
|
|
24
27
|
export * from './4xx/forbidden.js'
|
|
@@ -27,6 +30,7 @@ export * from './4xx/im-a-teapot.js'
|
|
|
27
30
|
export * from './4xx/not-acceptable.js'
|
|
28
31
|
export * from './4xx/not-allowed.js'
|
|
29
32
|
export * from './4xx/not-found.js'
|
|
33
|
+
export * from './4xx/payment-required.js'
|
|
30
34
|
export * from './4xx/precondition-failed.js'
|
|
31
35
|
export * from './4xx/range-not-satisfiable.js'
|
|
32
36
|
export * from './4xx/timeout.js'
|
package/src/response/response.js
CHANGED
|
@@ -7,12 +7,14 @@ import { sendPartialContent } from './2xx/partial-content.js'
|
|
|
7
7
|
import { sendPreflight } from './2xx/preflight.js'
|
|
8
8
|
import { sendSSE } from './2xx/sse.js'
|
|
9
9
|
import { sendTrace } from './2xx/trace.js'
|
|
10
|
+
import { sendFound } from './3xx/found.js'
|
|
10
11
|
import { sendMovedPermanently } from './3xx/moved-permanently.js'
|
|
11
12
|
import { sendMultipleChoices } from './3xx/multiple-choices.js'
|
|
12
13
|
import { sendNotModified } from './3xx/not-modified.js'
|
|
13
14
|
import { sendPermanentRedirect } from './3xx/permanent-redirect.js'
|
|
14
15
|
import { sendSeeOther } from './3xx/see-other.js'
|
|
15
16
|
import { sendTemporaryRedirect } from './3xx/temporary-redirect.js'
|
|
17
|
+
import { sendBadRequest } from './4xx/bad-request.js'
|
|
16
18
|
import { sendConflict } from './4xx/conflict.js'
|
|
17
19
|
import { sendContentTooLarge } from './4xx/content-too-large.js'
|
|
18
20
|
import { sendForbidden } from './4xx/forbidden.js'
|
|
@@ -21,6 +23,7 @@ import { sendImATeapot } from './4xx/im-a-teapot.js'
|
|
|
21
23
|
import { sendNotAcceptable } from './4xx/not-acceptable.js'
|
|
22
24
|
import { sendNotAllowed } from './4xx/not-allowed.js'
|
|
23
25
|
import { sendNotFound } from './4xx/not-found.js'
|
|
26
|
+
import { sendPaymentRequired } from './4xx/payment-required.js'
|
|
24
27
|
import { sendPreconditionFailed } from './4xx/precondition-failed.js'
|
|
25
28
|
import { sendRangeNotSatisfiable } from './4xx/range-not-satisfiable.js'
|
|
26
29
|
import { sendTimeout } from './4xx/timeout.js'
|
|
@@ -35,12 +38,14 @@ import { sendUnavailable } from './5xx/unavailable.js'
|
|
|
35
38
|
|
|
36
39
|
export const Response = {
|
|
37
40
|
accepted: sendAccepted,
|
|
41
|
+
badRequest: sendBadRequest,
|
|
38
42
|
bytes: sendBytes,
|
|
39
43
|
conflict: sendConflict,
|
|
40
44
|
contentTooLarge: sendContentTooLarge,
|
|
41
45
|
created: sendCreated,
|
|
42
46
|
error: sendError,
|
|
43
47
|
forbidden: sendForbidden,
|
|
48
|
+
found: sendFound,
|
|
44
49
|
gone: sendGone,
|
|
45
50
|
imATeapot: sendImATeapot,
|
|
46
51
|
insufficientStorage: sendInsufficientStorage,
|
|
@@ -54,6 +59,7 @@ export const Response = {
|
|
|
54
59
|
notImplemented: sendNotImplemented,
|
|
55
60
|
notModified: sendNotModified,
|
|
56
61
|
partialContent: sendPartialContent,
|
|
62
|
+
paymentRequired: sendPaymentRequired,
|
|
57
63
|
permanentRedirect: sendPermanentRedirect,
|
|
58
64
|
preconditionFailed: sendPreconditionFailed,
|
|
59
65
|
preflight: sendPreflight,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
2
|
+
import { pipeline, Readable } from 'node:stream'
|
|
3
3
|
import { ReadableStream } from 'node:stream/web'
|
|
4
4
|
import {
|
|
5
5
|
brotliCompressSync,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
23
23
|
/** @import { OutgoingHttpHeaders } from 'node:http2' */
|
|
24
24
|
/** @import { InputType } from 'node:zlib' */
|
|
25
|
-
/** @import { Metadata, SendBody } from '../defs.js' */
|
|
26
|
-
/** @import { EtagItem } from '../headers/conditional.js' */
|
|
25
|
+
/** @import { AcceptRangeUnits, Metadata, SendBody } from '../defs.js' */
|
|
26
|
+
/** @import { EtagItem, IMFFixDateInput } from '../headers/conditional.js' */
|
|
27
27
|
/** @import { CacheControlOptions } from '../headers/cache-control.js' */
|
|
28
28
|
/** @import { ContentRangeDirective } from '../headers/content-range.js' */
|
|
29
29
|
|
|
@@ -38,6 +38,7 @@ const {
|
|
|
38
38
|
HTTP2_HEADER_VARY,
|
|
39
39
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
40
40
|
HTTP2_HEADER_ETAG,
|
|
41
|
+
HTTP2_HEADER_LAST_MODIFIED,
|
|
41
42
|
HTTP2_HEADER_AGE,
|
|
42
43
|
HTTP2_HEADER_ACCEPT_RANGES,
|
|
43
44
|
HTTP2_HEADER_CONTENT_RANGE,
|
|
@@ -47,7 +48,7 @@ const {
|
|
|
47
48
|
HTTP2_HEADER_RANGE
|
|
48
49
|
} = http2.constants
|
|
49
50
|
|
|
50
|
-
/** @typedef { (data: InputType) => Buffer } EncoderFun */
|
|
51
|
+
/** @typedef { (data: InputType) => Buffer<ArrayBuffer> } EncoderFun */
|
|
51
52
|
|
|
52
53
|
/** @type {Map<string, EncoderFun>} */
|
|
53
54
|
export const ENCODER_MAP = new Map([
|
|
@@ -64,13 +65,14 @@ export const ENCODER_MAP = new Map([
|
|
|
64
65
|
* @param {SendBody} body
|
|
65
66
|
* @param {string|undefined} encoding
|
|
66
67
|
* @param {EtagItem|undefined} etag
|
|
68
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
67
69
|
* @param {number|undefined} age
|
|
68
70
|
* @param {CacheControlOptions|undefined} cacheControl
|
|
69
|
-
* @param {
|
|
71
|
+
* @param {AcceptRangeUnits|undefined} acceptRanges
|
|
70
72
|
* @param {Array<string>|undefined} supportedQueryTypes
|
|
71
73
|
* @param {Metadata} meta
|
|
72
74
|
*/
|
|
73
|
-
export function send_encoded(stream, status, contentType, body, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
75
|
+
export function send_encoded(stream, status, contentType, body, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
74
76
|
const obj = (typeof body === 'string') ? Buffer.from(body, CHARSET_UTF8) : body
|
|
75
77
|
|
|
76
78
|
const useIdentity = encoding === 'identity'
|
|
@@ -86,7 +88,7 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
|
|
|
86
88
|
{ name: 'encode', duration: encodeEnd - encodeStart }
|
|
87
89
|
)
|
|
88
90
|
|
|
89
|
-
send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
|
|
91
|
+
send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
|
|
@@ -99,13 +101,14 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
|
|
|
99
101
|
* @param {number|undefined} contentLength
|
|
100
102
|
* @param {string|undefined} encoding
|
|
101
103
|
* @param {EtagItem|undefined} etag
|
|
104
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
102
105
|
* @param {number|undefined} age
|
|
103
106
|
* @param {CacheControlOptions|undefined} cacheControl
|
|
104
|
-
* @param {
|
|
107
|
+
* @param {AcceptRangeUnits|undefined} acceptRanges
|
|
105
108
|
* @param {Array<string>|undefined} supportedQueryTypes
|
|
106
109
|
* @param {Metadata} meta
|
|
107
110
|
*/
|
|
108
|
-
export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
111
|
+
export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
109
112
|
const contentLen = Number.isInteger(contentLength) ? `${contentLength}` : undefined
|
|
110
113
|
const supportsQuery = supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
|
|
111
114
|
|
|
@@ -123,6 +126,7 @@ export function send_bytes(stream, status, contentType, obj, range, contentLengt
|
|
|
123
126
|
[HTTP2_HEADER_VARY]: varyHeaders.join(','),
|
|
124
127
|
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
125
128
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
129
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
|
|
126
130
|
[HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`,
|
|
127
131
|
[HTTP2_HEADER_CONTENT_LENGTH]: contentLen,
|
|
128
132
|
[HTTP2_HEADER_CONTENT_RANGE]: ContentRange.encode(range),
|
|
@@ -164,7 +168,16 @@ export function send(stream, status, headers, exposedHeaders, contentType, body,
|
|
|
164
168
|
|
|
165
169
|
if(stream.writable && body !== undefined) {
|
|
166
170
|
if(body instanceof ReadableStream) {
|
|
167
|
-
|
|
171
|
+
const signal = undefined // AbortSignal.timeout(1000)
|
|
172
|
+
pipeline(
|
|
173
|
+
Readable.fromWeb(body, { signal }),
|
|
174
|
+
stream,
|
|
175
|
+
err => {
|
|
176
|
+
if(err !== null) {
|
|
177
|
+
console.warn('pipeline error')
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
168
181
|
return
|
|
169
182
|
}
|
|
170
183
|
|