@johntalton/http-util 3.0.1 → 4.0.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 +1 -1
- package/src/cache-control.js +57 -0
- package/src/conditional.js +29 -1
- package/src/response/header-util.js +2 -0
- package/src/response/json.js +10 -5
- package/src/response/not-modified.js +5 -2
- package/src/response/preflight.js +11 -2
package/package.json
CHANGED
package/src/body.js
CHANGED
|
@@ -13,7 +13,7 @@ export const DEFAULT_BYTE_LIMIT = 1024 * 1024 //
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Object} BodyOptions
|
|
16
|
-
* @property {AbortSignal} [signal]
|
|
16
|
+
* @property {AbortSignal|undefined} [signal]
|
|
17
17
|
* @property {number} [byteLimit]
|
|
18
18
|
* @property {number} [contentLength]
|
|
19
19
|
* @property {ContentType|undefined} [contentType]
|
|
@@ -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}
|
|
@@ -24,6 +24,8 @@ const {
|
|
|
24
24
|
export function coreHeaders(status, contentType, meta) {
|
|
25
25
|
return {
|
|
26
26
|
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
27
|
+
// Access-Control-Expose-Headers
|
|
28
|
+
// Access-Control-Allow-Credentials // for non-preflight
|
|
27
29
|
[HTTP2_HEADER_STATUS]: status,
|
|
28
30
|
[HTTP2_HEADER_CONTENT_TYPE]: contentType,
|
|
29
31
|
[HTTP2_HEADER_SERVER]: meta.servername
|
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
|
-
|
|
71
|
+
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
72
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
73
|
+
[HTTP2_HEADER_AGE]: age !== undefined ? `${age}` : undefined
|
|
69
74
|
}, CONTENT_TYPE_JSON, encodedData, meta)
|
|
70
75
|
}
|
|
@@ -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,12 +21,13 @@ 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
33
|
}, undefined, undefined, meta)
|
|
@@ -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
|
|
37
|
+
// Access-Control-Allow-Credentials
|
|
29
38
|
}, undefined, undefined, meta)
|
|
30
39
|
}
|