@johntalton/http-util 3.0.1 → 4.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 +1 -1
- package/src/body.js +3 -3
- package/src/cache-control.js +57 -0
- package/src/conditional.js +29 -1
- package/src/rate-limit.js +2 -0
- package/src/response/accepted.js +1 -1
- package/src/response/conflict.js +1 -1
- package/src/response/created.js +1 -1
- package/src/response/defs.js +1 -0
- package/src/response/error.js +1 -1
- package/src/response/header-util.js +19 -2
- package/src/response/index.js +2 -0
- package/src/response/json.js +11 -6
- package/src/response/no-content.js +1 -1
- package/src/response/not-acceptable.js +1 -0
- package/src/response/not-allowed.js +1 -1
- package/src/response/not-found.js +1 -1
- package/src/response/not-implemented.js +17 -0
- package/src/response/not-modified.js +6 -3
- package/src/response/precondition-failed.js +1 -1
- package/src/response/preflight.js +12 -3
- package/src/response/response.js +4 -0
- package/src/response/send-util.js +14 -5
- package/src/response/sse.js +1 -1
- package/src/response/timeout.js +1 -1
- package/src/response/too-many-requests.js +5 -0
- package/src/response/trace.js +1 -1
- package/src/response/unauthorized.js +1 -1
- package/src/response/unavailable.js +24 -0
- package/src/response/unprocessable.js +1 -1
- package/src/response/unsupported-media.js +1 -1
package/package.json
CHANGED
package/src/body.js
CHANGED
|
@@ -13,9 +13,9 @@ export const DEFAULT_BYTE_LIMIT = 1024 * 1024 //
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Object} BodyOptions
|
|
16
|
-
* @property {AbortSignal} [signal]
|
|
17
|
-
* @property {number} [byteLimit]
|
|
18
|
-
* @property {number} [contentLength]
|
|
16
|
+
* @property {AbortSignal|undefined} [signal]
|
|
17
|
+
* @property {number|undefined} [byteLimit]
|
|
18
|
+
* @property {number|undefined} [contentLength]
|
|
19
19
|
* @property {ContentType|undefined} [contentType]
|
|
20
20
|
*/
|
|
21
21
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** @typedef {'no-cache'|'no-store'|'no-transform'|'must-revalidate'|'immutable'|'must-understand'} Directives */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} CacheControlOptions
|
|
5
|
+
* @property {boolean|undefined} [priv]
|
|
6
|
+
* @property {boolean|undefined} [pub]
|
|
7
|
+
* @property {number|undefined} [maxAge]
|
|
8
|
+
* @property {number|undefined} [staleWhileRevalidate]
|
|
9
|
+
* @property {number|undefined} [staleIfError]
|
|
10
|
+
* @property {Directives|Array<Directives>|undefined} [directives]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class CacheControl {
|
|
14
|
+
/**
|
|
15
|
+
* @param {CacheControlOptions} options
|
|
16
|
+
* @returns {string|undefined}
|
|
17
|
+
*/
|
|
18
|
+
static encode(options) {
|
|
19
|
+
const {
|
|
20
|
+
pub,
|
|
21
|
+
priv,
|
|
22
|
+
maxAge,
|
|
23
|
+
directives,
|
|
24
|
+
staleWhileRevalidate,
|
|
25
|
+
staleIfError
|
|
26
|
+
} = options
|
|
27
|
+
|
|
28
|
+
const result = []
|
|
29
|
+
|
|
30
|
+
if(pub !== undefined && pub && !priv) { result.push('public') }
|
|
31
|
+
if(priv !== undefined && priv && !pub) { result.push('private') }
|
|
32
|
+
|
|
33
|
+
if(directives !== undefined) {
|
|
34
|
+
result.push(...(Array.isArray(directives) ? directives : [ directives ]))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if(maxAge !== undefined && Number.isInteger(maxAge) && maxAge >= 0) {
|
|
38
|
+
result.push(`max-age=${maxAge}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if(staleWhileRevalidate !== undefined && Number.isInteger(staleWhileRevalidate) && staleWhileRevalidate >= 0) {
|
|
42
|
+
result.push(`stale-while-revalidate=${staleWhileRevalidate}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if(staleIfError !== undefined && Number.isInteger(staleIfError) && staleIfError >= 0) {
|
|
46
|
+
result.push(`stale-if-error=${staleIfError}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result.join(', ')
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// console.log(CacheControl.encode({
|
|
54
|
+
// priv: true,
|
|
55
|
+
// maxAge: 60,
|
|
56
|
+
// directives: ['must-revalidate', 'no-transform']
|
|
57
|
+
// }))
|
package/src/conditional.js
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* @property {number} hour
|
|
32
32
|
* @property {number} minute
|
|
33
33
|
* @property {number} second
|
|
34
|
-
* @property {Date} date
|
|
34
|
+
* @property {Date|undefined} [date]
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
export const CONDITION_ETAG_SEPARATOR = ','
|
|
@@ -168,6 +168,34 @@ export class Conditional {
|
|
|
168
168
|
.filter(item => item !== undefined)
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* @param {Array<EtagItem>} etagItemList
|
|
173
|
+
*/
|
|
174
|
+
static hasAny(etagItemList) {
|
|
175
|
+
return etagItemList.find(item => item.any) !== undefined
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {Array<EtagItem>} etagItemList
|
|
180
|
+
* @param {string} etag
|
|
181
|
+
*/
|
|
182
|
+
static hasEtag(etagItemList, etag) {
|
|
183
|
+
return etagItemList.find(item => item.etag === etag) !== undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {IMFFixDate|undefined} fixDate
|
|
188
|
+
* @returns {string|undefined}
|
|
189
|
+
*/
|
|
190
|
+
static encodeFixDate(fixDate) {
|
|
191
|
+
if(fixDate === undefined) { return undefined }
|
|
192
|
+
if(fixDate.date !== undefined) { return fixDate.date.toUTCString() }
|
|
193
|
+
|
|
194
|
+
const { year, month, day, hour, minute, second } = fixDate
|
|
195
|
+
const d = new Date(Date.UTC(year, DATE_MONTHS.indexOf(month), day, hour, minute, second))
|
|
196
|
+
return d.toUTCString()
|
|
197
|
+
}
|
|
198
|
+
|
|
171
199
|
/**
|
|
172
200
|
* @param {String|string|undefined} matchHeader
|
|
173
201
|
* @returns {IMFFixDate|undefined}
|
package/src/rate-limit.js
CHANGED
package/src/response/accepted.js
CHANGED
|
@@ -11,5 +11,5 @@ const { HTTP_STATUS_ACCEPTED } = http2.constants
|
|
|
11
11
|
* @param {Metadata} meta
|
|
12
12
|
*/
|
|
13
13
|
export function sendAccepted(stream, meta) {
|
|
14
|
-
send(stream, HTTP_STATUS_ACCEPTED, {}, undefined, undefined, meta)
|
|
14
|
+
send(stream, HTTP_STATUS_ACCEPTED, {}, [], undefined, undefined, meta)
|
|
15
15
|
}
|
package/src/response/conflict.js
CHANGED
|
@@ -11,5 +11,5 @@ const { HTTP_STATUS_CONFLICT } = http2.constants
|
|
|
11
11
|
* @param {Metadata} meta
|
|
12
12
|
*/
|
|
13
13
|
export function sendConflict(stream, meta) {
|
|
14
|
-
send(stream, HTTP_STATUS_CONFLICT, {}, undefined, undefined, meta)
|
|
14
|
+
send(stream, HTTP_STATUS_CONFLICT, {}, [], undefined, undefined, meta)
|
|
15
15
|
}
|
package/src/response/created.js
CHANGED
|
@@ -23,5 +23,5 @@ export function sendCreated(stream, location, etag, meta) {
|
|
|
23
23
|
send(stream, HTTP_STATUS_CREATED, {
|
|
24
24
|
[HTTP2_HEADER_LOCATION]: location.href,
|
|
25
25
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
26
|
-
}, undefined, undefined, meta)
|
|
26
|
+
}, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
|
|
27
27
|
}
|
package/src/response/defs.js
CHANGED
package/src/response/error.js
CHANGED
|
@@ -13,5 +13,5 @@ const { HTTP_STATUS_INTERNAL_SERVER_ERROR } = http2.constants
|
|
|
13
13
|
* @param {Metadata} meta
|
|
14
14
|
*/
|
|
15
15
|
export function sendError(stream, message, meta) {
|
|
16
|
-
send(stream, HTTP_STATUS_INTERNAL_SERVER_ERROR, {}, CONTENT_TYPE_TEXT, message, meta)
|
|
16
|
+
send(stream, HTTP_STATUS_INTERNAL_SERVER_ERROR, {}, [], CONTENT_TYPE_TEXT, message, meta)
|
|
17
17
|
}
|
|
@@ -12,18 +12,26 @@ const {
|
|
|
12
12
|
HTTP2_HEADER_STATUS,
|
|
13
13
|
HTTP2_HEADER_SERVER,
|
|
14
14
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
15
|
-
HTTP2_HEADER_CONTENT_TYPE
|
|
15
|
+
HTTP2_HEADER_CONTENT_TYPE,
|
|
16
|
+
HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
17
|
+
|
|
18
|
+
HTTP2_HEADER_ETAG
|
|
16
19
|
} = http2.constants
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* @param {number} status
|
|
20
23
|
* @param {string|undefined} contentType
|
|
24
|
+
* @param {Array<string>} exposedHeaders
|
|
21
25
|
* @param {Metadata} meta
|
|
22
26
|
* @returns {OutgoingHttpHeaders}
|
|
23
27
|
*/
|
|
24
|
-
export function coreHeaders(status, contentType, meta) {
|
|
28
|
+
export function coreHeaders(status, contentType, exposedHeaders, meta) {
|
|
29
|
+
const exposed = [ HTTP2_HEADER_ETAG, HTTP2_HEADER_SERVER, ...exposedHeaders ]
|
|
30
|
+
|
|
25
31
|
return {
|
|
26
32
|
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
33
|
+
[HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS]: exposed.join(','),
|
|
34
|
+
// Access-Control-Allow-Credentials // for non-preflight
|
|
27
35
|
[HTTP2_HEADER_STATUS]: status,
|
|
28
36
|
[HTTP2_HEADER_CONTENT_TYPE]: contentType,
|
|
29
37
|
[HTTP2_HEADER_SERVER]: meta.servername
|
|
@@ -40,3 +48,12 @@ export function performanceHeaders(meta) {
|
|
|
40
48
|
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
|
|
41
49
|
}
|
|
42
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Metadata} meta
|
|
54
|
+
* @returns {OutgoingHttpHeaders}
|
|
55
|
+
*/
|
|
56
|
+
export function customHeaders(meta) {
|
|
57
|
+
const m = new Map(meta.customHeaders?.filter(h => h[0].startsWith('X-')))
|
|
58
|
+
return Object.fromEntries(m)
|
|
59
|
+
}
|
package/src/response/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export * from './no-content.js'
|
|
|
9
9
|
export * from './not-acceptable.js'
|
|
10
10
|
export * from './not-allowed.js'
|
|
11
11
|
export * from './not-found.js'
|
|
12
|
+
export * from './not-implemented.js'
|
|
12
13
|
export * from './not-modified.js'
|
|
13
14
|
export * from './precondition-failed.js'
|
|
14
15
|
export * from './preflight.js'
|
|
@@ -17,5 +18,6 @@ export * from './timeout.js'
|
|
|
17
18
|
export * from './too-many-requests.js'
|
|
18
19
|
export * from './trace.js'
|
|
19
20
|
export * from './unauthorized.js'
|
|
21
|
+
export * from './unavailable.js'
|
|
20
22
|
export * from './unprocessable.js'
|
|
21
23
|
export * from './unsupported-media.js'
|
package/src/response/json.js
CHANGED
|
@@ -11,10 +11,12 @@ import {
|
|
|
11
11
|
} from '../content-type.js'
|
|
12
12
|
import { send } from './send-util.js'
|
|
13
13
|
import { Conditional } from '../conditional.js'
|
|
14
|
+
import { CacheControl } from '../cache-control.js'
|
|
14
15
|
|
|
15
16
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
16
17
|
/** @import { Metadata } from './defs.js' */
|
|
17
18
|
/** @import { EtagItem } from '../conditional.js' */
|
|
19
|
+
/** @import { CacheControlOptions } from '../cache-control.js' */
|
|
18
20
|
|
|
19
21
|
/** @typedef { (data: string, charset: BufferEncoding) => Buffer } EncoderFun */
|
|
20
22
|
|
|
@@ -22,7 +24,8 @@ const {
|
|
|
22
24
|
HTTP2_HEADER_CONTENT_ENCODING,
|
|
23
25
|
HTTP2_HEADER_VARY,
|
|
24
26
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
25
|
-
HTTP2_HEADER_ETAG
|
|
27
|
+
HTTP2_HEADER_ETAG,
|
|
28
|
+
HTTP2_HEADER_AGE
|
|
26
29
|
} = http2.constants
|
|
27
30
|
|
|
28
31
|
const { HTTP_STATUS_OK } = http2.constants
|
|
@@ -40,9 +43,11 @@ export const ENCODER_MAP = new Map([
|
|
|
40
43
|
* @param {Object} obj
|
|
41
44
|
* @param {string|undefined} encoding
|
|
42
45
|
* @param {EtagItem|undefined} etag
|
|
46
|
+
* @param {number|undefined} age
|
|
47
|
+
* @param {CacheControlOptions} cacheControl
|
|
43
48
|
* @param {Metadata} meta
|
|
44
49
|
*/
|
|
45
|
-
export function sendJSON_Encoded(stream, obj, encoding, etag, meta) {
|
|
50
|
+
export function sendJSON_Encoded(stream, obj, encoding, etag, age, cacheControl, meta) {
|
|
46
51
|
if(stream.closed) { return }
|
|
47
52
|
|
|
48
53
|
const json = JSON.stringify(obj)
|
|
@@ -63,8 +68,8 @@ export function sendJSON_Encoded(stream, obj, encoding, etag, meta) {
|
|
|
63
68
|
send(stream, HTTP_STATUS_OK, {
|
|
64
69
|
[HTTP2_HEADER_CONTENT_ENCODING]: actualEncoding,
|
|
65
70
|
[HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
|
|
66
|
-
[HTTP2_HEADER_CACHE_CONTROL]:
|
|
67
|
-
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
68
|
-
|
|
69
|
-
}, CONTENT_TYPE_JSON, encodedData, meta)
|
|
71
|
+
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
72
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
73
|
+
[HTTP2_HEADER_AGE]: age !== undefined ? `${age}` : undefined
|
|
74
|
+
}, [ HTTP2_HEADER_AGE ], CONTENT_TYPE_JSON, encodedData, meta)
|
|
70
75
|
}
|
|
@@ -20,5 +20,5 @@ const { HTTP_STATUS_NO_CONTENT } = http2.constants
|
|
|
20
20
|
export function sendNoContent(stream, etag, meta) {
|
|
21
21
|
send(stream, HTTP_STATUS_NO_CONTENT, {
|
|
22
22
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
23
|
-
}, undefined, undefined, meta)
|
|
23
|
+
}, [], undefined, undefined, meta)
|
|
24
24
|
}
|
|
@@ -18,5 +18,5 @@ const { HTTP2_HEADER_ALLOW } = http2.constants
|
|
|
18
18
|
export function sendNotAllowed(stream, methods, meta) {
|
|
19
19
|
send(stream, HTTP_STATUS_METHOD_NOT_ALLOWED, {
|
|
20
20
|
[HTTP2_HEADER_ALLOW]: methods.join(',')
|
|
21
|
-
}, undefined, undefined, meta)
|
|
21
|
+
}, [ HTTP2_HEADER_ALLOW ], undefined, undefined, meta)
|
|
22
22
|
}
|
|
@@ -13,5 +13,5 @@ const { HTTP_STATUS_NOT_FOUND } = http2.constants
|
|
|
13
13
|
* @param {Metadata} meta
|
|
14
14
|
*/
|
|
15
15
|
export function sendNotFound(stream, message, meta) {
|
|
16
|
-
send(stream, HTTP_STATUS_NOT_FOUND, {}, CONTENT_TYPE_TEXT, message, meta)
|
|
16
|
+
send(stream, HTTP_STATUS_NOT_FOUND, {}, [], CONTENT_TYPE_TEXT, message, meta)
|
|
17
17
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
import { CONTENT_TYPE_TEXT } from '../content-type.js'
|
|
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_NOT_IMPLEMENTED } = http2.constants
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {ServerHttp2Stream} stream
|
|
12
|
+
* @param {string|undefined} message
|
|
13
|
+
* @param {Metadata} meta
|
|
14
|
+
*/
|
|
15
|
+
export function sendNotImplemented(stream, message, meta) {
|
|
16
|
+
send(stream, HTTP_STATUS_NOT_IMPLEMENTED, {}, [], CONTENT_TYPE_TEXT, message, meta)
|
|
17
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
import { send } from './send-util.js'
|
|
3
3
|
import { Conditional } from '../conditional.js'
|
|
4
|
+
import { CacheControl } from '../cache-control.js'
|
|
4
5
|
|
|
5
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
7
|
/** @import { Metadata } from './defs.js' */
|
|
7
8
|
/** @import { EtagItem } from '../conditional.js' */
|
|
9
|
+
/** @import { CacheControlOptions } from '../cache-control.js' */
|
|
8
10
|
|
|
9
11
|
const {
|
|
10
12
|
HTTP2_HEADER_AGE,
|
|
@@ -19,13 +21,14 @@ const { HTTP_STATUS_NOT_MODIFIED } = http2.constants
|
|
|
19
21
|
* @param {ServerHttp2Stream} stream
|
|
20
22
|
* @param {EtagItem|undefined} etag
|
|
21
23
|
* @param {number|undefined} age
|
|
24
|
+
* @param {CacheControlOptions} cacheControl
|
|
22
25
|
* @param {Metadata} meta
|
|
23
26
|
*/
|
|
24
|
-
export function sendNotModified(stream, etag, age, meta) {
|
|
27
|
+
export function sendNotModified(stream, etag, age, cacheControl, meta) {
|
|
25
28
|
send(stream, HTTP_STATUS_NOT_MODIFIED, {
|
|
26
29
|
[HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
|
|
27
|
-
[HTTP2_HEADER_CACHE_CONTROL]:
|
|
30
|
+
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
28
31
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
29
32
|
[HTTP2_HEADER_AGE]: age !== undefined ? `${age}` : undefined
|
|
30
|
-
}, undefined, undefined, meta)
|
|
33
|
+
}, [ HTTP2_HEADER_AGE ], undefined, undefined, meta)
|
|
31
34
|
}
|
|
@@ -11,5 +11,5 @@ const { HTTP_STATUS_PRECONDITION_FAILED } = http2.constants
|
|
|
11
11
|
* @param {Metadata} meta
|
|
12
12
|
*/
|
|
13
13
|
export function sendPreconditionFailed(stream, meta) {
|
|
14
|
-
send(stream, HTTP_STATUS_PRECONDITION_FAILED, {}, undefined, undefined, meta)
|
|
14
|
+
send(stream, HTTP_STATUS_PRECONDITION_FAILED, {}, [], undefined, undefined, meta)
|
|
15
15
|
}
|
|
@@ -11,7 +11,10 @@ import { send } from './send-util.js'
|
|
|
11
11
|
const {
|
|
12
12
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
13
13
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
|
|
14
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS
|
|
14
|
+
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
|
|
15
|
+
HTTP2_HEADER_IF_MATCH,
|
|
16
|
+
HTTP2_HEADER_IF_NONE_MATCH,
|
|
17
|
+
HTTP2_HEADER_AUTHORIZATION
|
|
15
18
|
} = http2.constants
|
|
16
19
|
|
|
17
20
|
const { HTTP_STATUS_OK } = http2.constants
|
|
@@ -24,7 +27,13 @@ const { HTTP_STATUS_OK } = http2.constants
|
|
|
24
27
|
export function sendPreflight(stream, methods, meta) {
|
|
25
28
|
send(stream, HTTP_STATUS_OK, {
|
|
26
29
|
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: methods.join(','),
|
|
27
|
-
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: [
|
|
30
|
+
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: [
|
|
31
|
+
HTTP2_HEADER_IF_MATCH,
|
|
32
|
+
HTTP2_HEADER_IF_NONE_MATCH,
|
|
33
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
34
|
+
HTTP2_HEADER_CONTENT_TYPE
|
|
35
|
+
].join(','),
|
|
28
36
|
[HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS
|
|
29
|
-
|
|
37
|
+
// Access-Control-Allow-Credentials
|
|
38
|
+
}, [], undefined, undefined, meta)
|
|
30
39
|
}
|
package/src/response/response.js
CHANGED
|
@@ -7,6 +7,7 @@ import { sendNoContent } from './no-content.js'
|
|
|
7
7
|
import { sendNotAcceptable } from './not-acceptable.js'
|
|
8
8
|
import { sendNotAllowed } from './not-allowed.js'
|
|
9
9
|
import { sendNotFound } from './not-found.js'
|
|
10
|
+
import { sendNotImplemented } from './not-implemented.js'
|
|
10
11
|
import { sendNotModified } from './not-modified.js'
|
|
11
12
|
import { sendPreconditionFailed } from './precondition-failed.js'
|
|
12
13
|
import { sendPreflight } from './preflight.js'
|
|
@@ -15,6 +16,7 @@ import { sendTimeout } from './timeout.js'
|
|
|
15
16
|
import { sendTooManyRequests } from './too-many-requests.js'
|
|
16
17
|
import { sendTrace } from './trace.js'
|
|
17
18
|
import { sendUnauthorized } from './unauthorized.js'
|
|
19
|
+
import { sendUnavailable } from './unavailable.js'
|
|
18
20
|
import { sendUnprocessable } from './unprocessable.js'
|
|
19
21
|
import { sendUnsupportedMediaType } from './unsupported-media.js'
|
|
20
22
|
|
|
@@ -28,6 +30,7 @@ export const Response = {
|
|
|
28
30
|
notAcceptable: sendNotAcceptable,
|
|
29
31
|
notAllowed: sendNotAllowed,
|
|
30
32
|
notFound: sendNotFound,
|
|
33
|
+
notImplemented: sendNotImplemented,
|
|
31
34
|
notModified: sendNotModified,
|
|
32
35
|
preconditionFailed: sendPreconditionFailed,
|
|
33
36
|
preflight: sendPreflight,
|
|
@@ -36,6 +39,7 @@ export const Response = {
|
|
|
36
39
|
tooManyRequests: sendTooManyRequests,
|
|
37
40
|
trace: sendTrace,
|
|
38
41
|
unauthorized: sendUnauthorized,
|
|
42
|
+
unavailable: sendUnavailable,
|
|
39
43
|
unprocessable: sendUnprocessable,
|
|
40
44
|
unsupportedMediaType: sendUnsupportedMediaType
|
|
41
45
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
coreHeaders,
|
|
3
|
+
customHeaders,
|
|
4
|
+
performanceHeaders
|
|
5
|
+
} from './header-util.js'
|
|
2
6
|
|
|
3
7
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
4
8
|
/** @import { IncomingHttpHeaders } from 'node:http2' */
|
|
@@ -8,24 +12,29 @@ import { coreHeaders, performanceHeaders } from './header-util.js'
|
|
|
8
12
|
* @param {ServerHttp2Stream} stream
|
|
9
13
|
* @param {number} status
|
|
10
14
|
* @param {IncomingHttpHeaders} headers
|
|
15
|
+
* @param {Array<string>} exposedHeaders
|
|
11
16
|
* @param {string|undefined} contentType
|
|
12
17
|
* @param {ArrayBufferLike|ArrayBufferView|string|undefined} body
|
|
13
18
|
* @param {Metadata} meta
|
|
14
19
|
*/
|
|
15
|
-
export function send(stream, status, headers, contentType, body, meta) {
|
|
20
|
+
export function send(stream, status, headers, exposedHeaders, contentType, body, meta) {
|
|
16
21
|
// if(status >= 400) { console.warn(status, body) }
|
|
17
22
|
if(status === 401) { console.warn(status, body) }
|
|
18
23
|
if(status === 404) { console.warn(status, body) }
|
|
19
|
-
if(status
|
|
24
|
+
if(status >= 500) { console.warn(status, body) }
|
|
20
25
|
|
|
21
26
|
if(stream === undefined) { return }
|
|
22
27
|
if(stream.closed) { return }
|
|
23
28
|
|
|
24
29
|
if(!stream.headersSent) {
|
|
30
|
+
const custom = customHeaders(meta)
|
|
31
|
+
const exposed = [ ...exposedHeaders, ...Object.keys(custom) ]
|
|
32
|
+
|
|
25
33
|
stream.respond({
|
|
26
|
-
...coreHeaders(status, contentType, meta),
|
|
34
|
+
...coreHeaders(status, contentType, exposed, meta),
|
|
27
35
|
...performanceHeaders(meta),
|
|
28
|
-
...headers
|
|
36
|
+
...headers,
|
|
37
|
+
...custom
|
|
29
38
|
})
|
|
30
39
|
}
|
|
31
40
|
|
package/src/response/sse.js
CHANGED
|
@@ -23,7 +23,7 @@ export function sendSSE(stream, meta) {
|
|
|
23
23
|
const status = activeStream ? HTTP_STATUS_OK : SSE_INACTIVE_STATUS_CODE
|
|
24
24
|
|
|
25
25
|
stream.respond({
|
|
26
|
-
...coreHeaders(status, SSE_MIME, meta),
|
|
26
|
+
...coreHeaders(status, SSE_MIME, [], meta),
|
|
27
27
|
...performanceHeaders(meta)
|
|
28
28
|
|
|
29
29
|
// [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true'
|
package/src/response/timeout.js
CHANGED
|
@@ -30,6 +30,11 @@ export function sendTooManyRequests(stream, limitInfo, policies, meta) {
|
|
|
30
30
|
[HTTP_HEADER_RATE_LIMIT]: RateLimit.from(limitInfo),
|
|
31
31
|
[HTTP_HEADER_RATE_LIMIT_POLICY]: RateLimitPolicy.from(...policies)
|
|
32
32
|
},
|
|
33
|
+
[
|
|
34
|
+
HTTP2_HEADER_RETRY_AFTER,
|
|
35
|
+
HTTP_HEADER_RATE_LIMIT,
|
|
36
|
+
HTTP_HEADER_RATE_LIMIT_POLICY
|
|
37
|
+
],
|
|
33
38
|
CONTENT_TYPE_TEXT,
|
|
34
39
|
`Retry After ${limitInfo.resetSeconds} Seconds`,
|
|
35
40
|
meta)
|
package/src/response/trace.js
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
import { CONTENT_TYPE_TEXT } from '../content-type.js'
|
|
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_RETRY_AFTER
|
|
10
|
+
} = http2.constants
|
|
11
|
+
|
|
12
|
+
const { HTTP_STATUS_SERVICE_UNAVAILABLE } = http2.constants
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {ServerHttp2Stream} stream
|
|
16
|
+
* @param {string|undefined} message
|
|
17
|
+
* @param {number|undefined} retryAfter
|
|
18
|
+
* @param {Metadata} meta
|
|
19
|
+
*/
|
|
20
|
+
export function sendUnavailable(stream, message, retryAfter, meta) {
|
|
21
|
+
send(stream, HTTP_STATUS_SERVICE_UNAVAILABLE, {
|
|
22
|
+
[HTTP2_HEADER_RETRY_AFTER]: Number.isInteger(retryAfter) ? `${retryAfter}` : undefined
|
|
23
|
+
}, [ HTTP2_HEADER_RETRY_AFTER ], CONTENT_TYPE_TEXT, message, meta)
|
|
24
|
+
}
|
|
@@ -11,5 +11,5 @@ const { HTTP_STATUS_UNPROCESSABLE_ENTITY } = http2.constants
|
|
|
11
11
|
* @param {Metadata} meta
|
|
12
12
|
*/
|
|
13
13
|
export function sendUnprocessable(stream, meta) {
|
|
14
|
-
send(stream, HTTP_STATUS_UNPROCESSABLE_ENTITY, {}, undefined, undefined, meta)
|
|
14
|
+
send(stream, HTTP_STATUS_UNPROCESSABLE_ENTITY, {}, [], undefined, undefined, meta)
|
|
15
15
|
}
|
|
@@ -17,5 +17,5 @@ export function sendUnsupportedMediaType(stream, acceptableMediaType, meta) {
|
|
|
17
17
|
|
|
18
18
|
send(stream, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, {
|
|
19
19
|
[HTTP_HEADER_ACCEPT_POST]: acceptable.join(',')
|
|
20
|
-
}, undefined, undefined, meta)
|
|
20
|
+
}, [ HTTP_HEADER_ACCEPT_POST ], undefined, undefined, meta)
|
|
21
21
|
}
|