@johntalton/http-util 2.0.4 → 3.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/conditional.js +77 -25
- package/src/content-type.js +1 -0
- package/src/response/accepted.js +4 -28
- package/src/response/conflict.js +4 -28
- package/src/response/created.js +12 -27
- package/src/response/defs.js +0 -1
- package/src/response/error.js +6 -36
- package/src/response/header-util.js +42 -0
- package/src/response/index.js +2 -1
- package/src/response/json.js +19 -27
- package/src/response/no-content.js +11 -23
- package/src/response/not-acceptable.js +9 -30
- package/src/response/not-allowed.js +7 -15
- package/src/response/not-found.js +4 -24
- package/src/response/not-modified.js +14 -26
- package/src/response/precondition-failed.js +15 -0
- package/src/response/preflight.js +9 -17
- package/src/response/response.js +6 -4
- package/src/response/send-util.js +39 -0
- package/src/response/sse.js +10 -27
- package/src/response/timeout.js +6 -26
- package/src/response/too-many-requests.js +14 -25
- package/src/response/trace.js +4 -28
- package/src/response/unauthorized.js +5 -18
- package/src/response/unprocessable.js +4 -28
- package/src/response/unsupported-media.js +6 -25
package/package.json
CHANGED
package/src/conditional.js
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {Object} WeakEtagItem
|
|
4
|
+
* @property {true} weak
|
|
5
|
+
* @property {false} any
|
|
6
|
+
* @property {string} etag
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} AnyEtagItem
|
|
11
|
+
* @property {boolean} weak
|
|
12
|
+
* @property {true} any
|
|
13
|
+
* @property {'*'} etag
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} NotWeakEtagItem
|
|
18
|
+
* @property {false} weak
|
|
19
|
+
* @property {false} any
|
|
20
|
+
* @property {string} etag
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** @typedef {WeakEtagItem | NotWeakEtagItem | AnyEtagItem } EtagItem */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} IMFFixDate
|
|
27
|
+
* @property {typeof DATE_DAYS[number]} dayName
|
|
28
|
+
* @property {number} day
|
|
29
|
+
* @property {typeof DATE_MONTHS[number]} month
|
|
30
|
+
* @property {number} year
|
|
31
|
+
* @property {number} hour
|
|
32
|
+
* @property {number} minute
|
|
33
|
+
* @property {number} second
|
|
34
|
+
* @property {Date} date
|
|
35
|
+
*/
|
|
36
|
+
|
|
1
37
|
export const CONDITION_ETAG_SEPARATOR = ','
|
|
2
38
|
export const CONDITION_ETAG_ANY = '*'
|
|
3
39
|
export const CONDITION_ETAG_WEAK_PREFIX = 'W/'
|
|
4
40
|
export const ETAG_QUOTE = '"'
|
|
5
41
|
|
|
42
|
+
/** @type {AnyEtagItem} */
|
|
43
|
+
export const ANY_ETAG_ITEM = { any: true, weak: false, etag: CONDITION_ETAG_ANY }
|
|
44
|
+
|
|
6
45
|
export const DATE_SPACE = ' '
|
|
7
46
|
export const DATE_DAYS = [ 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' ]
|
|
8
47
|
export const DATE_MONTHS = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
|
|
@@ -40,26 +79,25 @@ export function isQuoted(etag) {
|
|
|
40
79
|
return true
|
|
41
80
|
}
|
|
42
81
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
export class Conditional {
|
|
83
|
+
/**
|
|
84
|
+
* @param {EtagItem|undefined} etagItem
|
|
85
|
+
* @returns {string|undefined}
|
|
86
|
+
*/
|
|
87
|
+
static encodeEtag(etagItem) {
|
|
88
|
+
if(etagItem === undefined) { return undefined }
|
|
89
|
+
if(etagItem.any) {
|
|
90
|
+
if(etagItem.etag !== CONDITION_ETAG_ANY) { return undefined }
|
|
91
|
+
return CONDITION_ETAG_ANY
|
|
92
|
+
}
|
|
49
93
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
* @property {number} hour
|
|
57
|
-
* @property {number} minute
|
|
58
|
-
* @property {number} second
|
|
59
|
-
* @property {Date} date
|
|
60
|
-
*/
|
|
94
|
+
if(etagItem.etag === CONDITION_ETAG_ANY) { return undefined }
|
|
95
|
+
if(!isValidEtag(etagItem.etag)) { return undefined }
|
|
96
|
+
|
|
97
|
+
const prefix = etagItem.weak ? CONDITION_ETAG_WEAK_PREFIX : ''
|
|
98
|
+
return `${prefix}${ETAG_QUOTE}${etagItem.etag}${ETAG_QUOTE}`
|
|
99
|
+
}
|
|
61
100
|
|
|
62
|
-
export class Conditional {
|
|
63
101
|
/**
|
|
64
102
|
* @param {string|undefined} matchHeader
|
|
65
103
|
* @returns {Array<EtagItem>}
|
|
@@ -85,12 +123,7 @@ export class Conditional {
|
|
|
85
123
|
}
|
|
86
124
|
})
|
|
87
125
|
.map(item => {
|
|
88
|
-
if(item.etag === CONDITION_ETAG_ANY) {
|
|
89
|
-
return {
|
|
90
|
-
...item,
|
|
91
|
-
any: true
|
|
92
|
-
}
|
|
93
|
-
}
|
|
126
|
+
if(item.etag === CONDITION_ETAG_ANY) { return ANY_ETAG_ITEM }
|
|
94
127
|
|
|
95
128
|
// validated quoted
|
|
96
129
|
if(!isQuoted(item.etag)) { return undefined }
|
|
@@ -98,11 +131,14 @@ export class Conditional {
|
|
|
98
131
|
if(!isValidEtag(etag)) { return undefined }
|
|
99
132
|
if(etag === CONDITION_ETAG_ANY) { return undefined }
|
|
100
133
|
|
|
101
|
-
|
|
134
|
+
/** @type {WeakEtagItem | NotWeakEtagItem} */
|
|
135
|
+
const result = {
|
|
102
136
|
weak: item.weak,
|
|
103
137
|
any: false,
|
|
104
138
|
etag
|
|
105
139
|
}
|
|
140
|
+
|
|
141
|
+
return result
|
|
106
142
|
})
|
|
107
143
|
.filter(item => item !== undefined)
|
|
108
144
|
}
|
|
@@ -187,6 +223,20 @@ export class Conditional {
|
|
|
187
223
|
}
|
|
188
224
|
}
|
|
189
225
|
|
|
226
|
+
// Ok
|
|
227
|
+
// console.log(Conditional.encodeEtag({ any: true, weak: false, etag: '*' }))
|
|
228
|
+
// console.log(Conditional.encodeEtag({ any: true, weak: true, etag: '*' }))
|
|
229
|
+
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo' }))
|
|
230
|
+
// console.log(Conditional.encodeEtag({ any: false, weak: true, etag: 'WeakFoo' }))
|
|
231
|
+
|
|
232
|
+
// Error
|
|
233
|
+
// console.log(Conditional.encodeEtag(undefined))
|
|
234
|
+
// console.log(Conditional.encodeEtag({ any: true, weak: false, etag: 'NotAsterisk' }))
|
|
235
|
+
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo\tBar' }))
|
|
236
|
+
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: 'Foo"Bar' }))
|
|
237
|
+
// console.log(Conditional.encodeEtag({ any: false, weak: false, etag: '*' }))
|
|
238
|
+
|
|
239
|
+
|
|
190
240
|
// Ok
|
|
191
241
|
// console.log(Conditional.parseEtagList('"bfc13a64729c4290ef5b2c2730249c88ca92d82d"'))
|
|
192
242
|
// console.log(Conditional.parseEtagList('W/"67ab43", "54ed21", "7892dd"'))
|
|
@@ -225,6 +275,8 @@ export class Conditional {
|
|
|
225
275
|
// }
|
|
226
276
|
// }
|
|
227
277
|
|
|
278
|
+
|
|
279
|
+
|
|
228
280
|
// const testBad = [
|
|
229
281
|
// undefined,
|
|
230
282
|
// null,
|
package/src/content-type.js
CHANGED
|
@@ -65,6 +65,7 @@ export const CHARSET = 'charset'
|
|
|
65
65
|
export const PARAMETER_CHARSET_UTF8 = `${CHARSET}${CONTENT_TYPE_SEPARATOR.KVP}${CHARSET_UTF8}`
|
|
66
66
|
export const CONTENT_TYPE_JSON = `${MIME_TYPE_JSON}${CONTENT_TYPE_SEPARATOR.PARAMETER}${PARAMETER_CHARSET_UTF8}`
|
|
67
67
|
export const CONTENT_TYPE_TEXT = `${MIME_TYPE_TEXT}${CONTENT_TYPE_SEPARATOR.PARAMETER}${PARAMETER_CHARSET_UTF8}`
|
|
68
|
+
export const CONTENT_TYPE_MESSAGE_HTTP = `${MIME_TYPE_MESSAGE_HTTP}${CONTENT_TYPE_SEPARATOR.PARAMETER}${PARAMETER_CHARSET_UTF8}`
|
|
68
69
|
|
|
69
70
|
/** @type {ContentType} */
|
|
70
71
|
export const WELL_KNOWN_JSON = {
|
package/src/response/accepted.js
CHANGED
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
8
3
|
|
|
9
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
5
|
/** @import { Metadata } from './defs.js' */
|
|
11
6
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_ACCEPTED
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
20
|
-
} = http2.constants
|
|
7
|
+
const { HTTP_STATUS_ACCEPTED } = http2.constants
|
|
21
8
|
|
|
22
9
|
/**
|
|
23
10
|
* @param {ServerHttp2Stream} stream
|
|
24
11
|
* @param {Metadata} meta
|
|
25
12
|
*/
|
|
26
13
|
export function sendAccepted(stream, meta) {
|
|
27
|
-
stream
|
|
28
|
-
|
|
29
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_ACCEPTED,
|
|
30
|
-
// [HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_JSON,
|
|
31
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
32
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
33
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// stream.write(JSON.stringify( ... ))
|
|
37
|
-
|
|
38
|
-
stream.end()
|
|
39
|
-
}
|
|
14
|
+
send(stream, HTTP_STATUS_ACCEPTED, {}, undefined, undefined, meta)
|
|
15
|
+
}
|
package/src/response/conflict.js
CHANGED
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
8
3
|
|
|
9
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
5
|
/** @import { Metadata } from './defs.js' */
|
|
11
6
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_CONFLICT
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
20
|
-
} = http2.constants
|
|
7
|
+
const { HTTP_STATUS_CONFLICT } = http2.constants
|
|
21
8
|
|
|
22
9
|
/**
|
|
23
10
|
* @param {ServerHttp2Stream} stream
|
|
24
11
|
* @param {Metadata} meta
|
|
25
12
|
*/
|
|
26
13
|
export function sendConflict(stream, meta) {
|
|
27
|
-
stream
|
|
28
|
-
|
|
29
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_CONFLICT,
|
|
30
|
-
// [HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_JSON,
|
|
31
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
32
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
33
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// stream.write(JSON.stringify( ... ))
|
|
37
|
-
|
|
38
|
-
stream.end()
|
|
39
|
-
}
|
|
14
|
+
send(stream, HTTP_STATUS_CONFLICT, {}, undefined, undefined, meta)
|
|
15
|
+
}
|
package/src/response/created.js
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
3
|
+
import { Conditional } from '../conditional.js'
|
|
8
4
|
|
|
9
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
6
|
/** @import { Metadata } from './defs.js' */
|
|
7
|
+
/** @import { EtagItem } from '../conditional.js' */
|
|
11
8
|
|
|
12
9
|
const {
|
|
13
|
-
|
|
10
|
+
HTTP2_HEADER_LOCATION,
|
|
11
|
+
HTTP2_HEADER_ETAG
|
|
14
12
|
} = http2.constants
|
|
15
13
|
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
20
|
-
HTTP2_HEADER_LOCATION
|
|
21
|
-
} = http2.constants
|
|
14
|
+
const { HTTP_STATUS_CREATED } = http2.constants
|
|
22
15
|
|
|
23
16
|
/**
|
|
24
17
|
* @param {ServerHttp2Stream} stream
|
|
25
18
|
* @param {URL} location
|
|
19
|
+
* @param {EtagItem|undefined} etag
|
|
26
20
|
* @param {Metadata} meta
|
|
27
21
|
*/
|
|
28
|
-
export function sendCreated(stream, location, meta) {
|
|
29
|
-
stream
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
34
|
-
[HTTP2_HEADER_LOCATION]: location.href,
|
|
35
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
36
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// stream.write(JSON.stringify( ... ))
|
|
40
|
-
|
|
41
|
-
stream.end()
|
|
22
|
+
export function sendCreated(stream, location, etag, meta) {
|
|
23
|
+
send(stream, HTTP_STATUS_CREATED, {
|
|
24
|
+
[HTTP2_HEADER_LOCATION]: location.href,
|
|
25
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
26
|
+
}, undefined, undefined, meta)
|
|
42
27
|
}
|
package/src/response/defs.js
CHANGED
package/src/response/error.js
CHANGED
|
@@ -1,47 +1,17 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from '../content-type.js'
|
|
5
|
-
|
|
6
|
-
const {
|
|
7
|
-
HTTP2_HEADER_STATUS,
|
|
8
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
9
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
10
|
-
HTTP2_HEADER_SERVER
|
|
11
|
-
} = http2.constants
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
HTTP_STATUS_INTERNAL_SERVER_ERROR
|
|
15
|
-
} = http2.constants
|
|
2
|
+
import { CONTENT_TYPE_TEXT } from '../content-type.js'
|
|
3
|
+
import { send } from './send-util.js'
|
|
16
4
|
|
|
17
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
18
6
|
/** @import { Metadata } from './defs.js' */
|
|
19
7
|
|
|
8
|
+
const { HTTP_STATUS_INTERNAL_SERVER_ERROR } = http2.constants
|
|
9
|
+
|
|
20
10
|
/**
|
|
21
11
|
* @param {ServerHttp2Stream} stream
|
|
22
12
|
* @param {string} message
|
|
23
13
|
* @param {Metadata} meta
|
|
24
14
|
*/
|
|
25
15
|
export function sendError(stream, message, meta) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if(stream === undefined) { return }
|
|
29
|
-
if(stream.closed) { return }
|
|
30
|
-
|
|
31
|
-
if(!stream.headersSent) {
|
|
32
|
-
stream.respond({
|
|
33
|
-
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
34
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_INTERNAL_SERVER_ERROR,
|
|
35
|
-
[HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_TEXT,
|
|
36
|
-
[HTTP2_HEADER_SERVER]: meta.servername
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// protect against HEAD calls
|
|
41
|
-
if(stream.writable) {
|
|
42
|
-
if(message !== undefined) { stream.write(message) }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
stream.end()
|
|
46
|
-
if(!stream.closed) { stream.close() }
|
|
47
|
-
}
|
|
16
|
+
send(stream, HTTP_STATUS_INTERNAL_SERVER_ERROR, {}, CONTENT_TYPE_TEXT, message, meta)
|
|
17
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
import {
|
|
3
|
+
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
4
|
+
HTTP_HEADER_SERVER_TIMING,
|
|
5
|
+
ServerTiming
|
|
6
|
+
} from '../server-timing.js'
|
|
7
|
+
|
|
8
|
+
/** @import { OutgoingHttpHeaders } from 'node:http2' */
|
|
9
|
+
/** @import { Metadata } from './defs.js' */
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
HTTP2_HEADER_STATUS,
|
|
13
|
+
HTTP2_HEADER_SERVER,
|
|
14
|
+
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
15
|
+
HTTP2_HEADER_CONTENT_TYPE
|
|
16
|
+
} = http2.constants
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {number} status
|
|
20
|
+
* @param {string|undefined} contentType
|
|
21
|
+
* @param {Metadata} meta
|
|
22
|
+
* @returns {OutgoingHttpHeaders}
|
|
23
|
+
*/
|
|
24
|
+
export function coreHeaders(status, contentType, meta) {
|
|
25
|
+
return {
|
|
26
|
+
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
27
|
+
[HTTP2_HEADER_STATUS]: status,
|
|
28
|
+
[HTTP2_HEADER_CONTENT_TYPE]: contentType,
|
|
29
|
+
[HTTP2_HEADER_SERVER]: meta.servername
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Metadata} meta
|
|
35
|
+
* @returns {OutgoingHttpHeaders}
|
|
36
|
+
*/
|
|
37
|
+
export function performanceHeaders(meta) {
|
|
38
|
+
return {
|
|
39
|
+
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
40
|
+
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/response/index.js
CHANGED
|
@@ -10,11 +10,12 @@ export * from './not-acceptable.js'
|
|
|
10
10
|
export * from './not-allowed.js'
|
|
11
11
|
export * from './not-found.js'
|
|
12
12
|
export * from './not-modified.js'
|
|
13
|
+
export * from './precondition-failed.js'
|
|
13
14
|
export * from './preflight.js'
|
|
14
15
|
export * from './sse.js'
|
|
15
16
|
export * from './timeout.js'
|
|
16
17
|
export * from './too-many-requests.js'
|
|
17
18
|
export * from './trace.js'
|
|
18
19
|
export * from './unauthorized.js'
|
|
19
|
-
export * from './unsupported-media.js'
|
|
20
20
|
export * from './unprocessable.js'
|
|
21
|
+
export * from './unsupported-media.js'
|
package/src/response/json.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
brotliCompressSync,
|
|
4
|
+
deflateSync,
|
|
5
|
+
gzipSync,
|
|
6
|
+
zstdCompressSync
|
|
7
|
+
} from 'node:zlib'
|
|
4
8
|
import {
|
|
5
9
|
CHARSET_UTF8,
|
|
6
10
|
CONTENT_TYPE_JSON
|
|
7
11
|
} from '../content-type.js'
|
|
12
|
+
import { send } from './send-util.js'
|
|
13
|
+
import { Conditional } from '../conditional.js'
|
|
8
14
|
|
|
9
15
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
16
|
/** @import { Metadata } from './defs.js' */
|
|
17
|
+
/** @import { EtagItem } from '../conditional.js' */
|
|
11
18
|
|
|
12
19
|
/** @typedef { (data: string, charset: BufferEncoding) => Buffer } EncoderFun */
|
|
13
20
|
|
|
14
21
|
const {
|
|
15
|
-
HTTP2_HEADER_STATUS,
|
|
16
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
17
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
22
|
HTTP2_HEADER_CONTENT_ENCODING,
|
|
20
23
|
HTTP2_HEADER_VARY,
|
|
21
24
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
22
25
|
HTTP2_HEADER_ETAG
|
|
23
26
|
} = http2.constants
|
|
24
27
|
|
|
25
|
-
const {
|
|
26
|
-
HTTP_STATUS_OK
|
|
27
|
-
} = http2.constants
|
|
28
|
+
const { HTTP_STATUS_OK } = http2.constants
|
|
28
29
|
|
|
29
30
|
/** @type {Map<string, EncoderFun>} */
|
|
30
31
|
export const ENCODER_MAP = new Map([
|
|
@@ -38,9 +39,10 @@ export const ENCODER_MAP = new Map([
|
|
|
38
39
|
* @param {ServerHttp2Stream} stream
|
|
39
40
|
* @param {Object} obj
|
|
40
41
|
* @param {string|undefined} encoding
|
|
42
|
+
* @param {EtagItem|undefined} etag
|
|
41
43
|
* @param {Metadata} meta
|
|
42
44
|
*/
|
|
43
|
-
export function sendJSON_Encoded(stream, obj, encoding, meta) {
|
|
45
|
+
export function sendJSON_Encoded(stream, obj, encoding, etag, meta) {
|
|
44
46
|
if(stream.closed) { return }
|
|
45
47
|
|
|
46
48
|
const json = JSON.stringify(obj)
|
|
@@ -58,21 +60,11 @@ export function sendJSON_Encoded(stream, obj, encoding, meta) {
|
|
|
58
60
|
{ name: 'encode', duration: encodeEnd - encodeStart }
|
|
59
61
|
)
|
|
60
62
|
|
|
61
|
-
stream
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
69
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
70
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
71
|
-
[HTTP2_HEADER_ETAG]: `"${meta.etag}"`
|
|
72
|
-
// [HTTP2_HEADER_AGE]: age
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// stream.write(encodedData)
|
|
76
|
-
stream.end(encodedData)
|
|
63
|
+
send(stream, HTTP_STATUS_OK, {
|
|
64
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: actualEncoding,
|
|
65
|
+
[HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
|
|
66
|
+
[HTTP2_HEADER_CACHE_CONTROL]: 'private',
|
|
67
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
68
|
+
// [HTTP2_HEADER_AGE]: age
|
|
69
|
+
}, CONTENT_TYPE_JSON, encodedData, meta)
|
|
77
70
|
}
|
|
78
|
-
|
|
@@ -1,36 +1,24 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
5
|
-
ServerTiming
|
|
6
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
3
|
+
import { Conditional } from '../conditional.js'
|
|
7
4
|
|
|
8
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
9
6
|
/** @import { Metadata } from './defs.js' */
|
|
7
|
+
/** @import { EtagItem } from '../conditional.js' */
|
|
10
8
|
|
|
11
9
|
const {
|
|
12
|
-
HTTP_STATUS_NO_CONTENT
|
|
13
|
-
} = http2.constants
|
|
14
|
-
|
|
15
|
-
const {
|
|
16
|
-
HTTP2_HEADER_STATUS,
|
|
17
|
-
HTTP2_HEADER_SERVER,
|
|
18
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
19
10
|
HTTP2_HEADER_ETAG
|
|
20
11
|
} = http2.constants
|
|
21
12
|
|
|
13
|
+
const { HTTP_STATUS_NO_CONTENT } = http2.constants
|
|
14
|
+
|
|
22
15
|
/**
|
|
23
16
|
* @param {ServerHttp2Stream} stream
|
|
17
|
+
* @param {EtagItem|undefined} etag
|
|
24
18
|
* @param {Metadata} meta
|
|
25
19
|
*/
|
|
26
|
-
export function sendNoContent(stream, meta) {
|
|
27
|
-
stream
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
32
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
33
|
-
[HTTP2_HEADER_ETAG]: `"${meta.etag}"`
|
|
34
|
-
})
|
|
35
|
-
stream.end()
|
|
36
|
-
}
|
|
20
|
+
export function sendNoContent(stream, etag, meta) {
|
|
21
|
+
send(stream, HTTP_STATUS_NO_CONTENT, {
|
|
22
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
23
|
+
}, undefined, undefined, meta)
|
|
24
|
+
}
|
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
5
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { CONTENT_TYPE_JSON } from '../content-type.js'
|
|
3
|
+
import { send } from './send-util.js'
|
|
8
4
|
|
|
9
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
6
|
/** @import { Metadata } from './defs.js' */
|
|
11
7
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_NOT_ACCEPTABLE
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
20
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
21
|
-
} = http2.constants
|
|
8
|
+
const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
|
|
22
9
|
|
|
23
10
|
/**
|
|
24
11
|
* @param {ServerHttp2Stream} stream
|
|
@@ -29,18 +16,10 @@ export function sendNotAcceptable(stream, supportedTypes, meta) {
|
|
|
29
16
|
const supportedTypesList = Array.isArray(supportedTypes) ? supportedTypes : [ supportedTypes ]
|
|
30
17
|
const has = supportedTypesList.length !== 0
|
|
31
18
|
|
|
32
|
-
stream
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
if(has) {
|
|
42
|
-
stream.write(JSON.stringify(supportedTypes))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
stream.end()
|
|
19
|
+
send(stream,
|
|
20
|
+
HTTP_STATUS_NOT_ACCEPTABLE,
|
|
21
|
+
{},
|
|
22
|
+
has ? CONTENT_TYPE_JSON : undefined,
|
|
23
|
+
has ? JSON.stringify(supportedTypes) : undefined,
|
|
24
|
+
meta)
|
|
46
25
|
}
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
+
import { send } from './send-util.js'
|
|
2
3
|
|
|
3
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
4
5
|
/** @import { Metadata } from './defs.js' */
|
|
5
6
|
|
|
6
|
-
const {
|
|
7
|
-
HTTP2_HEADER_STATUS,
|
|
8
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
9
|
-
HTTP2_HEADER_SERVER,
|
|
10
|
-
HTTP2_HEADER_ALLOW
|
|
11
|
-
} = http2.constants
|
|
12
|
-
|
|
13
7
|
const {
|
|
14
8
|
HTTP_STATUS_METHOD_NOT_ALLOWED
|
|
15
9
|
} = http2.constants
|
|
16
10
|
|
|
11
|
+
const { HTTP2_HEADER_ALLOW } = http2.constants
|
|
12
|
+
|
|
17
13
|
/**
|
|
18
14
|
* @param {ServerHttp2Stream} stream
|
|
19
15
|
* @param {Array<string>} methods
|
|
20
16
|
* @param {Metadata} meta
|
|
21
17
|
*/
|
|
22
18
|
export function sendNotAllowed(stream, methods, meta) {
|
|
23
|
-
stream
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[HTTP2_HEADER_SERVER]: meta.servername
|
|
28
|
-
})
|
|
29
|
-
stream.end()
|
|
30
|
-
}
|
|
19
|
+
send(stream, HTTP_STATUS_METHOD_NOT_ALLOWED, {
|
|
20
|
+
[HTTP2_HEADER_ALLOW]: methods.join(',')
|
|
21
|
+
}, undefined, undefined, meta)
|
|
22
|
+
}
|
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from '../content-type.js'
|
|
2
|
+
import { CONTENT_TYPE_TEXT } from '../content-type.js'
|
|
3
|
+
import { send } from './send-util.js'
|
|
5
4
|
|
|
6
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
6
|
/** @import { Metadata } from './defs.js' */
|
|
8
7
|
|
|
9
|
-
const {
|
|
10
|
-
HTTP2_HEADER_STATUS,
|
|
11
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
12
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
13
|
-
HTTP2_HEADER_SERVER
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP_STATUS_NOT_FOUND
|
|
18
|
-
} = http2.constants
|
|
8
|
+
const { HTTP_STATUS_NOT_FOUND } = http2.constants
|
|
19
9
|
|
|
20
10
|
/**
|
|
21
11
|
* @param {ServerHttp2Stream} stream
|
|
@@ -23,15 +13,5 @@ const {
|
|
|
23
13
|
* @param {Metadata} meta
|
|
24
14
|
*/
|
|
25
15
|
export function sendNotFound(stream, message, meta) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stream.respond({
|
|
29
|
-
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
30
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_NOT_FOUND,
|
|
31
|
-
[HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_TEXT,
|
|
32
|
-
[HTTP2_HEADER_SERVER]: meta.servername
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
if(message !== undefined) { stream.write(message) }
|
|
36
|
-
stream.end()
|
|
16
|
+
send(stream, HTTP_STATUS_NOT_FOUND, {}, CONTENT_TYPE_TEXT, message, meta)
|
|
37
17
|
}
|
|
@@ -1,43 +1,31 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
5
|
-
ServerTiming
|
|
6
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
3
|
+
import { Conditional } from '../conditional.js'
|
|
7
4
|
|
|
8
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
9
6
|
/** @import { Metadata } from './defs.js' */
|
|
7
|
+
/** @import { EtagItem } from '../conditional.js' */
|
|
10
8
|
|
|
11
9
|
const {
|
|
12
|
-
HTTP_STATUS_NOT_MODIFIED
|
|
13
|
-
} = http2.constants
|
|
14
|
-
|
|
15
|
-
const {
|
|
16
|
-
HTTP2_HEADER_STATUS,
|
|
17
|
-
HTTP2_HEADER_SERVER,
|
|
18
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
19
10
|
HTTP2_HEADER_AGE,
|
|
20
11
|
HTTP2_HEADER_ETAG,
|
|
21
12
|
HTTP2_HEADER_VARY,
|
|
22
13
|
HTTP2_HEADER_CACHE_CONTROL
|
|
23
14
|
} = http2.constants
|
|
24
15
|
|
|
16
|
+
const { HTTP_STATUS_NOT_MODIFIED } = http2.constants
|
|
17
|
+
|
|
25
18
|
/**
|
|
26
19
|
* @param {ServerHttp2Stream} stream
|
|
27
|
-
* @param {
|
|
20
|
+
* @param {EtagItem|undefined} etag
|
|
21
|
+
* @param {number|undefined} age
|
|
28
22
|
* @param {Metadata} meta
|
|
29
23
|
*/
|
|
30
|
-
export function sendNotModified(stream, age, meta) {
|
|
31
|
-
stream
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
38
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
39
|
-
[HTTP2_HEADER_ETAG]: `"${meta.etag}"`,
|
|
40
|
-
[HTTP2_HEADER_AGE]: age
|
|
41
|
-
})
|
|
42
|
-
stream.end()
|
|
24
|
+
export function sendNotModified(stream, etag, age, meta) {
|
|
25
|
+
send(stream, HTTP_STATUS_NOT_MODIFIED, {
|
|
26
|
+
[HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
|
|
27
|
+
[HTTP2_HEADER_CACHE_CONTROL]: 'private',
|
|
28
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
29
|
+
[HTTP2_HEADER_AGE]: age !== undefined ? `${age}` : undefined
|
|
30
|
+
}, undefined, undefined, meta)
|
|
43
31
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import http2 from 'node:http2'
|
|
2
|
+
import { send } from './send-util.js'
|
|
3
|
+
|
|
4
|
+
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
5
|
+
/** @import { Metadata } from './defs.js' */
|
|
6
|
+
|
|
7
|
+
const { HTTP_STATUS_PRECONDITION_FAILED } = http2.constants
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {ServerHttp2Stream} stream
|
|
11
|
+
* @param {Metadata} meta
|
|
12
|
+
*/
|
|
13
|
+
export function sendPreconditionFailed(stream, meta) {
|
|
14
|
+
send(stream, HTTP_STATUS_PRECONDITION_FAILED, {}, undefined, undefined, meta)
|
|
15
|
+
}
|
|
@@ -3,22 +3,18 @@ import {
|
|
|
3
3
|
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
|
|
4
4
|
PREFLIGHT_AGE_SECONDS
|
|
5
5
|
} from './defs.js'
|
|
6
|
+
import { send } from './send-util.js'
|
|
6
7
|
|
|
7
8
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
8
9
|
/** @import { Metadata } from './defs.js' */
|
|
9
10
|
|
|
10
11
|
const {
|
|
11
|
-
HTTP2_HEADER_STATUS,
|
|
12
12
|
HTTP2_HEADER_CONTENT_TYPE,
|
|
13
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
14
13
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
|
|
15
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS
|
|
16
|
-
HTTP2_HEADER_SERVER
|
|
14
|
+
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS
|
|
17
15
|
} = http2.constants
|
|
18
16
|
|
|
19
|
-
const {
|
|
20
|
-
HTTP_STATUS_OK
|
|
21
|
-
} = http2.constants
|
|
17
|
+
const { HTTP_STATUS_OK } = http2.constants
|
|
22
18
|
|
|
23
19
|
/**
|
|
24
20
|
* @param {ServerHttp2Stream} stream
|
|
@@ -26,13 +22,9 @@ const {
|
|
|
26
22
|
* @param {Metadata} meta
|
|
27
23
|
*/
|
|
28
24
|
export function sendPreflight(stream, methods, meta) {
|
|
29
|
-
stream
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
[HTTP2_HEADER_SERVER]: meta.servername
|
|
36
|
-
})
|
|
37
|
-
stream.end()
|
|
38
|
-
}
|
|
25
|
+
send(stream, HTTP_STATUS_OK, {
|
|
26
|
+
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: methods.join(','),
|
|
27
|
+
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: ['Authorization', HTTP2_HEADER_CONTENT_TYPE].join(','),
|
|
28
|
+
[HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS
|
|
29
|
+
}, undefined, undefined, meta)
|
|
30
|
+
}
|
package/src/response/response.js
CHANGED
|
@@ -8,32 +8,34 @@ import { sendNotAcceptable } from './not-acceptable.js'
|
|
|
8
8
|
import { sendNotAllowed } from './not-allowed.js'
|
|
9
9
|
import { sendNotFound } from './not-found.js'
|
|
10
10
|
import { sendNotModified } from './not-modified.js'
|
|
11
|
+
import { sendPreconditionFailed } from './precondition-failed.js'
|
|
11
12
|
import { sendPreflight } from './preflight.js'
|
|
12
13
|
import { sendSSE } from './sse.js'
|
|
13
14
|
import { sendTimeout } from './timeout.js'
|
|
14
15
|
import { sendTooManyRequests } from './too-many-requests.js'
|
|
15
16
|
import { sendTrace } from './trace.js'
|
|
16
17
|
import { sendUnauthorized } from './unauthorized.js'
|
|
17
|
-
import { sendUnsupportedMediaType } from './unsupported-media.js'
|
|
18
18
|
import { sendUnprocessable } from './unprocessable.js'
|
|
19
|
+
import { sendUnsupportedMediaType } from './unsupported-media.js'
|
|
19
20
|
|
|
20
21
|
export const Response = {
|
|
21
22
|
accepted: sendAccepted,
|
|
22
23
|
conflict: sendConflict,
|
|
23
24
|
created: sendCreated,
|
|
24
25
|
error: sendError,
|
|
25
|
-
noContent: sendNoContent,
|
|
26
26
|
json: sendJSON_Encoded,
|
|
27
|
+
noContent: sendNoContent,
|
|
27
28
|
notAcceptable: sendNotAcceptable,
|
|
28
29
|
notAllowed: sendNotAllowed,
|
|
29
30
|
notFound: sendNotFound,
|
|
30
31
|
notModified: sendNotModified,
|
|
32
|
+
preconditionFailed: sendPreconditionFailed,
|
|
31
33
|
preflight: sendPreflight,
|
|
32
34
|
sse: sendSSE,
|
|
33
35
|
timeout: sendTimeout,
|
|
34
36
|
tooManyRequests: sendTooManyRequests,
|
|
35
37
|
trace: sendTrace,
|
|
36
38
|
unauthorized: sendUnauthorized,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
unprocessable: sendUnprocessable,
|
|
40
|
+
unsupportedMediaType: sendUnsupportedMediaType
|
|
39
41
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { coreHeaders, performanceHeaders } from './header-util.js'
|
|
2
|
+
|
|
3
|
+
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
4
|
+
/** @import { IncomingHttpHeaders } from 'node:http2' */
|
|
5
|
+
/** @import { Metadata } from './defs.js' */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {ServerHttp2Stream} stream
|
|
9
|
+
* @param {number} status
|
|
10
|
+
* @param {IncomingHttpHeaders} headers
|
|
11
|
+
* @param {string|undefined} contentType
|
|
12
|
+
* @param {ArrayBufferLike|ArrayBufferView|string|undefined} body
|
|
13
|
+
* @param {Metadata} meta
|
|
14
|
+
*/
|
|
15
|
+
export function send(stream, status, headers, contentType, body, meta) {
|
|
16
|
+
// if(status >= 400) { console.warn(status, body) }
|
|
17
|
+
if(status === 401) { console.warn(status, body) }
|
|
18
|
+
if(status === 404) { console.warn(status, body) }
|
|
19
|
+
if(status === 500) { console.warn(status, body) }
|
|
20
|
+
|
|
21
|
+
if(stream === undefined) { return }
|
|
22
|
+
if(stream.closed) { return }
|
|
23
|
+
|
|
24
|
+
if(!stream.headersSent) {
|
|
25
|
+
stream.respond({
|
|
26
|
+
...coreHeaders(status, contentType, meta),
|
|
27
|
+
...performanceHeaders(meta),
|
|
28
|
+
...headers
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if(stream.writable && body !== undefined) {
|
|
33
|
+
stream.end(body)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
stream.end()
|
|
38
|
+
// if(!stream.closed) { stream.close() }
|
|
39
|
+
}
|
package/src/response/sse.js
CHANGED
|
@@ -5,51 +5,34 @@ import {
|
|
|
5
5
|
SSE_BOM,
|
|
6
6
|
ENDING,
|
|
7
7
|
} from '@johntalton/sse-util'
|
|
8
|
+
import { coreHeaders, performanceHeaders } from './header-util.js'
|
|
8
9
|
|
|
9
10
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
11
|
/** @import { Metadata, SSEOptions } from './defs.js' */
|
|
11
12
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP2_HEADER_STATUS,
|
|
14
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
15
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
16
|
-
HTTP2_HEADER_SERVER
|
|
17
|
-
} = http2.constants
|
|
18
|
-
|
|
19
|
-
const {
|
|
20
|
-
HTTP_STATUS_OK,
|
|
21
|
-
HTTP_STATUS_NO_CONTENT
|
|
22
|
-
} = http2.constants
|
|
13
|
+
const { HTTP_STATUS_OK } = http2.constants
|
|
23
14
|
|
|
24
15
|
/**
|
|
25
16
|
* @param {ServerHttp2Stream} stream
|
|
26
17
|
* @param {SSEOptions & Metadata} meta
|
|
27
18
|
*/
|
|
28
19
|
export function sendSSE(stream, meta) {
|
|
29
|
-
// stream.setTimeout(0)
|
|
30
|
-
// stream.session?.setTimeout(0)
|
|
31
|
-
// stream.session?.socket.setTimeout(0)
|
|
32
|
-
// stream.session.socket.setNoDelay(true)
|
|
33
|
-
// stream.session.socket.setKeepAlive(true)
|
|
34
|
-
|
|
35
|
-
// stream.on('close', () => console.log('SSE stream closed'))
|
|
36
|
-
// stream.on('aborted', () => console.log('SSE stream aborted'))
|
|
37
|
-
|
|
38
20
|
const activeStream = meta.active ?? true
|
|
39
21
|
const sendBOM = meta.bom ?? true
|
|
40
22
|
|
|
23
|
+
const status = activeStream ? HTTP_STATUS_OK : SSE_INACTIVE_STATUS_CODE
|
|
24
|
+
|
|
41
25
|
stream.respond({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
26
|
+
...coreHeaders(status, SSE_MIME, meta),
|
|
27
|
+
...performanceHeaders(meta)
|
|
28
|
+
|
|
45
29
|
// [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true'
|
|
46
|
-
|
|
47
|
-
})
|
|
30
|
+
})
|
|
48
31
|
|
|
49
|
-
|
|
32
|
+
if(!activeStream) {
|
|
50
33
|
stream.end()
|
|
51
34
|
return
|
|
52
|
-
|
|
35
|
+
}
|
|
53
36
|
|
|
54
37
|
if(sendBOM) {
|
|
55
38
|
stream.write(SSE_BOM + ENDING.CRLF)
|
package/src/response/timeout.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
8
3
|
|
|
9
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
5
|
/** @import { Metadata } from './defs.js' */
|
|
@@ -13,29 +8,14 @@ const {
|
|
|
13
8
|
HTTP_STATUS_REQUEST_TIMEOUT
|
|
14
9
|
} = http2.constants
|
|
15
10
|
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
20
|
-
HTTP2_HEADER_CONNECTION
|
|
21
|
-
} = http2.constants
|
|
11
|
+
const { HTTP2_HEADER_CONNECTION } = http2.constants
|
|
22
12
|
|
|
23
13
|
/**
|
|
24
14
|
* @param {ServerHttp2Stream} stream
|
|
25
15
|
* @param {Metadata} meta
|
|
26
16
|
*/
|
|
27
17
|
export function sendTimeout(stream, meta) {
|
|
28
|
-
stream
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
33
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
34
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
35
|
-
[HTTP2_HEADER_CONNECTION]: 'close'
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
// stream.write(JSON.stringify( ... ))
|
|
39
|
-
|
|
40
|
-
stream.end()
|
|
41
|
-
}
|
|
18
|
+
send(stream, HTTP_STATUS_REQUEST_TIMEOUT, {
|
|
19
|
+
[HTTP2_HEADER_CONNECTION]: 'close'
|
|
20
|
+
}, undefined, undefined, meta)
|
|
21
|
+
}
|
|
@@ -6,42 +6,31 @@ import {
|
|
|
6
6
|
RateLimit,
|
|
7
7
|
RateLimitPolicy
|
|
8
8
|
} from '../rate-limit.js'
|
|
9
|
-
|
|
9
|
+
import { send } from './send-util.js'
|
|
10
10
|
|
|
11
11
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
12
12
|
/** @import { Metadata } from './defs.js' */
|
|
13
|
+
/** @import { RateLimitInfo, RateLimitPolicyInfo } from '../rate-limit.js' */
|
|
13
14
|
|
|
14
15
|
const {
|
|
15
|
-
|
|
16
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
17
|
-
HTTP2_HEADER_SERVER,
|
|
18
|
-
HTTP2_HEADER_RETRY_AFTER,
|
|
19
|
-
HTTP2_HEADER_CONTENT_TYPE
|
|
16
|
+
HTTP2_HEADER_RETRY_AFTER
|
|
20
17
|
} = http2.constants
|
|
21
18
|
|
|
22
|
-
const {
|
|
23
|
-
HTTP_STATUS_TOO_MANY_REQUESTS
|
|
24
|
-
} = http2.constants
|
|
19
|
+
const { HTTP_STATUS_TOO_MANY_REQUESTS } = http2.constants
|
|
25
20
|
|
|
26
21
|
/**
|
|
27
22
|
* @param {ServerHttp2Stream} stream
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {Array<
|
|
23
|
+
* @param {RateLimitInfo} limitInfo
|
|
24
|
+
* @param {Array<RateLimitPolicyInfo>} policies
|
|
30
25
|
* @param {Metadata} meta
|
|
31
26
|
*/
|
|
32
27
|
export function sendTooManyRequests(stream, limitInfo, policies, meta) {
|
|
33
|
-
stream
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
stream.write(`Retry After ${limitInfo.retryAfterS} Seconds`)
|
|
44
|
-
|
|
45
|
-
stream.end()
|
|
28
|
+
send(stream, HTTP_STATUS_TOO_MANY_REQUESTS, {
|
|
29
|
+
[HTTP2_HEADER_RETRY_AFTER]: `${limitInfo.resetSeconds}`,
|
|
30
|
+
[HTTP_HEADER_RATE_LIMIT]: RateLimit.from(limitInfo),
|
|
31
|
+
[HTTP_HEADER_RATE_LIMIT_POLICY]: RateLimitPolicy.from(...policies)
|
|
32
|
+
},
|
|
33
|
+
CONTENT_TYPE_TEXT,
|
|
34
|
+
`Retry After ${limitInfo.resetSeconds} Seconds`,
|
|
35
|
+
meta)
|
|
46
36
|
}
|
|
47
|
-
|
package/src/response/trace.js
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
} from '../content-type.js'
|
|
5
|
-
import {
|
|
6
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
7
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
8
|
-
ServerTiming
|
|
9
|
-
} from '../server-timing.js'
|
|
2
|
+
import { CONTENT_TYPE_MESSAGE_HTTP } from '../content-type.js'
|
|
3
|
+
import { send } from './send-util.js'
|
|
10
4
|
|
|
11
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
12
6
|
/** @import { IncomingHttpHeaders } from 'node:http2' */
|
|
13
7
|
/** @import { Metadata } from './defs.js' */
|
|
14
8
|
|
|
15
|
-
const {
|
|
16
|
-
HTTP2_HEADER_STATUS,
|
|
17
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
18
|
-
HTTP2_HEADER_SERVER
|
|
19
|
-
} = http2.constants
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
HTTP_STATUS_OK
|
|
23
|
-
} = http2.constants
|
|
9
|
+
const { HTTP_STATUS_OK } = http2.constants
|
|
24
10
|
|
|
25
11
|
/**
|
|
26
12
|
* @param {ServerHttp2Stream} stream
|
|
@@ -38,15 +24,6 @@ export function sendTrace(stream, method, url, headers, meta) {
|
|
|
38
24
|
])
|
|
39
25
|
|
|
40
26
|
const version = HTTP_VERSION.get(stream.session?.alpnProtocol ?? 'h2')
|
|
41
|
-
|
|
42
|
-
stream.respond({
|
|
43
|
-
[HTTP2_HEADER_CONTENT_TYPE]: MIME_TYPE_MESSAGE_HTTP,
|
|
44
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_OK,
|
|
45
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
46
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
47
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
27
|
const reconstructed = [
|
|
51
28
|
`${method} ${url.pathname}${url.search} ${version}`,
|
|
52
29
|
Object.entries(headers)
|
|
@@ -58,6 +35,5 @@ export function sendTrace(stream, method, url, headers, meta) {
|
|
|
58
35
|
]
|
|
59
36
|
.join('\n')
|
|
60
37
|
|
|
61
|
-
stream
|
|
38
|
+
send(stream, HTTP_STATUS_OK, {}, CONTENT_TYPE_MESSAGE_HTTP, reconstructed, meta)
|
|
62
39
|
}
|
|
63
|
-
|
|
@@ -1,30 +1,17 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
+
import { send } from './send-util.js'
|
|
2
3
|
|
|
3
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
4
5
|
/** @import { Metadata } from './defs.js' */
|
|
5
6
|
|
|
6
|
-
const {
|
|
7
|
-
HTTP2_HEADER_STATUS,
|
|
8
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
9
|
-
HTTP2_HEADER_SERVER
|
|
10
|
-
} = http2.constants
|
|
11
|
-
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_UNAUTHORIZED
|
|
14
|
-
} = http2.constants
|
|
7
|
+
const { HTTP_STATUS_UNAUTHORIZED } = http2.constants
|
|
15
8
|
|
|
16
9
|
/**
|
|
17
10
|
* @param {ServerHttp2Stream} stream
|
|
18
11
|
* @param {Metadata} meta
|
|
19
12
|
*/
|
|
20
13
|
export function sendUnauthorized(stream, meta) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
[HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
|
|
25
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_UNAUTHORIZED,
|
|
26
|
-
[HTTP2_HEADER_SERVER]: meta.servername
|
|
27
|
-
// WWW-Authenticate
|
|
28
|
-
})
|
|
29
|
-
stream.end()
|
|
14
|
+
send(stream, HTTP_STATUS_UNAUTHORIZED, {
|
|
15
|
+
// WWW-Authenticate
|
|
16
|
+
}, undefined, undefined, meta)
|
|
30
17
|
}
|
|
@@ -1,39 +1,15 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
2
|
+
import { send } from './send-util.js'
|
|
8
3
|
|
|
9
4
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
5
|
/** @import { Metadata } from './defs.js' */
|
|
11
6
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_UNPROCESSABLE_ENTITY
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
20
|
-
} = http2.constants
|
|
7
|
+
const { HTTP_STATUS_UNPROCESSABLE_ENTITY } = http2.constants
|
|
21
8
|
|
|
22
9
|
/**
|
|
23
10
|
* @param {ServerHttp2Stream} stream
|
|
24
11
|
* @param {Metadata} meta
|
|
25
12
|
*/
|
|
26
13
|
export function sendUnprocessable(stream, meta) {
|
|
27
|
-
stream
|
|
28
|
-
|
|
29
|
-
[HTTP2_HEADER_STATUS]: HTTP_STATUS_UNPROCESSABLE_ENTITY,
|
|
30
|
-
// [HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_JSON,
|
|
31
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
32
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
33
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// stream.write(JSON.stringify( ... ))
|
|
37
|
-
|
|
38
|
-
stream.end()
|
|
39
|
-
}
|
|
14
|
+
send(stream, HTTP_STATUS_UNPROCESSABLE_ENTITY, {}, undefined, undefined, meta)
|
|
15
|
+
}
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
import { HTTP_HEADER_ACCEPT_POST } from './defs.js'
|
|
3
|
-
import {
|
|
4
|
-
HTTP_HEADER_TIMING_ALLOW_ORIGIN,
|
|
5
|
-
HTTP_HEADER_SERVER_TIMING,
|
|
6
|
-
ServerTiming
|
|
7
|
-
} from '../server-timing.js'
|
|
3
|
+
import { send } from './send-util.js'
|
|
8
4
|
|
|
9
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
10
6
|
/** @import { Metadata } from './defs.js' */
|
|
11
7
|
|
|
12
|
-
const {
|
|
13
|
-
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
|
|
14
|
-
} = http2.constants
|
|
15
|
-
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_STATUS,
|
|
18
|
-
HTTP2_HEADER_SERVER,
|
|
19
|
-
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN
|
|
20
|
-
} = http2.constants
|
|
8
|
+
const { HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE } = http2.constants
|
|
21
9
|
|
|
22
10
|
/**
|
|
23
11
|
* @param {ServerHttp2Stream} stream
|
|
@@ -27,14 +15,7 @@ const {
|
|
|
27
15
|
export function sendUnsupportedMediaType(stream, acceptableMediaType, meta) {
|
|
28
16
|
const acceptable = Array.isArray(acceptableMediaType) ? acceptableMediaType : [ acceptableMediaType ]
|
|
29
17
|
|
|
30
|
-
stream
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
[HTTP2_HEADER_SERVER]: meta.servername,
|
|
35
|
-
[HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
|
|
36
|
-
[HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
stream.end()
|
|
40
|
-
}
|
|
18
|
+
send(stream, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, {
|
|
19
|
+
[HTTP_HEADER_ACCEPT_POST]: acceptable.join(',')
|
|
20
|
+
}, undefined, undefined, meta)
|
|
21
|
+
}
|