@johntalton/http-util 6.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +17 -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 +170 -106
- 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 +11 -12
- package/src/headers/multipart.js +19 -18
- package/src/headers/preference.js +0 -43
- package/src/headers/range.js +5 -31
- package/src/headers/rate-limit.js +7 -2
- package/src/headers/server-timing.js +1 -14
- package/src/headers/strict-transport-security.js +1 -0
- package/src/headers/util/mime.js +3 -3
- package/src/headers/util/whitespace.js +3 -1
- package/src/headers/www-authenticate.js +0 -1
- package/src/response/2xx/bytes.js +19 -13
- package/src/response/2xx/created.js +16 -8
- package/src/response/2xx/json.js +29 -10
- package/src/response/2xx/no-content.js +12 -6
- package/src/response/2xx/partial-content.js +17 -14
- package/src/response/2xx/preflight.js +22 -10
- package/src/response/3xx/found.js +25 -0
- package/src/response/3xx/moved-permanently.js +7 -5
- package/src/response/3xx/not-modified.js +13 -8
- package/src/response/3xx/permanent-redirect.js +4 -2
- package/src/response/3xx/see-other.js +7 -5
- package/src/response/3xx/temporary-redirect.js +4 -2
- package/src/response/4xx/bad-request.js +19 -0
- package/src/response/4xx/content-too-large.js +1 -1
- package/src/response/4xx/gone.js +1 -1
- package/src/response/4xx/im-a-teapot.js +1 -1
- package/src/response/4xx/not-acceptable.js +6 -4
- package/src/response/4xx/not-allowed.js +6 -4
- package/src/response/4xx/payment-required.js +17 -0
- package/src/response/4xx/precondition-failed.js +18 -3
- package/src/response/4xx/range-not-satisfiable.js +5 -3
- package/src/response/4xx/too-many-requests.js +8 -5
- package/src/response/4xx/unauthorized.js +1 -1
- package/src/response/4xx/unsupported-media.js +9 -5
- package/src/response/5xx/error.js +2 -3
- package/src/response/5xx/insufficient-storage.js +2 -2
- package/src/response/5xx/not-implemented.js +2 -3
- package/src/response/5xx/unavailable.js +7 -12
- package/src/response/header-util.js +1 -1
- package/src/response/index.js +4 -0
- package/src/response/response.js +8 -2
- package/src/response/send-util.js +46 -14
package/src/headers/forwarded.js
CHANGED
|
@@ -41,6 +41,8 @@ export class Forwarded {
|
|
|
41
41
|
* @returns {Map<string, string>|undefined}
|
|
42
42
|
*/
|
|
43
43
|
static selectRightMost(forwardedList, skipList = []) {
|
|
44
|
+
if(forwardedList === undefined) { return undefined }
|
|
45
|
+
|
|
44
46
|
const iter = skipList[Symbol.iterator]()
|
|
45
47
|
|
|
46
48
|
for(const forwarded of forwardedList.toReversed()) {
|
|
@@ -54,39 +56,6 @@ export class Forwarded {
|
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
const examples = [
|
|
60
|
-
{ f: [], s: [], ef: undefined },
|
|
61
|
-
|
|
62
|
-
{ f: [], s: [ '1.1.1.1' ] , ef: undefined },
|
|
63
|
-
{ f: [], s: [ '*' ] , ef: undefined },
|
|
64
|
-
|
|
65
|
-
{ f: [ { for: '1.1.1.1' } ], s: [], ef: '1.1.1.1' },
|
|
66
|
-
{ f: [ { for: '1.1.1.1' } ], s: [ '*' ], ef: undefined },
|
|
67
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' } ], s: [], ef: '2.2.2.2' },
|
|
68
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' } ], s: [ '2.2.2.2' ], ef: '1.1.1.1' },
|
|
69
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3', '2.2.2.2' ], ef: '1.1.1.1' },
|
|
70
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3', '*' ], ef: '1.1.1.1' },
|
|
71
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*', '*' ], ef: '1.1.1.1' },
|
|
72
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*', '*', '*' ], ef: undefined },
|
|
73
|
-
|
|
74
|
-
{ f: [ { for: '1.1.1.1' } ], s: [ '*', '*' ], ef: undefined },
|
|
75
|
-
|
|
76
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '3.3.3.3'], ef: '2.2.2.2' },
|
|
77
|
-
{ f: [ { for: '1.1.1.1' }, { for: '2.2.2.2' }, { for: '3.3.3.3' } ], s: [ '*'], ef: '2.2.2.2' },
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
for(const { f, s, ef } of examples) {
|
|
81
|
-
const result = Forwarded.selectRightMost(f.map(i => new Map(Object.entries(i))), s)
|
|
82
|
-
const resultFor = result?.get('for')
|
|
83
|
-
if(resultFor !== ef) {
|
|
84
|
-
console.log(`mismatch ${ef} !== ${resultFor}`)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
*/
|
|
88
|
-
|
|
89
|
-
|
|
90
59
|
/*
|
|
91
60
|
const examples = [
|
|
92
61
|
null,
|
package/src/headers/link.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} LinkItem
|
|
3
|
-
* @property {string} url
|
|
3
|
+
* @property {URL|string} url
|
|
4
4
|
* @property {string|undefined} [relation]
|
|
5
5
|
* @property {Map<string, string>|undefined} [parameters]
|
|
6
6
|
*/
|
|
@@ -9,8 +9,8 @@ export class Link {
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {LinkItem} link
|
|
11
11
|
*/
|
|
12
|
-
static
|
|
13
|
-
const encodedUri = encodeURI(link.url)
|
|
12
|
+
static *#encode(link) {
|
|
13
|
+
const encodedUri = (link.url instanceof URL) ? link.url : encodeURI(link.url)
|
|
14
14
|
|
|
15
15
|
yield `<${encodedUri}>`
|
|
16
16
|
if(link.relation !== undefined) { yield `rel="${link.relation}"` }
|
|
@@ -20,16 +20,15 @@ export class Link {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
|
-
* @param {LinkItem}
|
|
24
|
+
* @param {Array<LinkItem>|LinkItem|undefined} links
|
|
26
25
|
*/
|
|
27
|
-
static encode(
|
|
28
|
-
|
|
26
|
+
static encode(links) {
|
|
27
|
+
if(links === undefined) { return undefined }
|
|
28
|
+
const linkAry = Array.isArray(links) ? links : [ links ]
|
|
29
|
+
if(linkAry.length === 0) { return undefined }
|
|
30
|
+
return linkAry
|
|
31
|
+
.map(link => [ ...Link.#encode(link) ].join('; '))
|
|
32
|
+
.join(', ')
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
|
-
|
|
32
|
-
// console.log(Link.encode({ url: '/index.html', parameters: new Map([ [ 'as', 'style' ], [ 'fetchpriority', 'high' ] ]) }))
|
|
33
|
-
// console.log(Link.encode({ url: '/index.html', relation: 'next', parameters: new Map([ [ 'fetchpriority', 'high' ] ]) }))
|
|
34
|
-
// console.log(Link.encode({ url: '/index.html', relation: 'next' }))
|
|
35
|
-
// console.log(Link.encode({ url: 'https://example.com/苗条', relation: 'preconnect' }))
|
package/src/headers/multipart.js
CHANGED
|
@@ -49,11 +49,14 @@ export class Multipart {
|
|
|
49
49
|
* @param {string} text
|
|
50
50
|
* @param {string} boundary
|
|
51
51
|
* @param {string} [_charset='utf8']
|
|
52
|
+
* @returns {FormData}
|
|
52
53
|
*/
|
|
53
54
|
static parse_FormData(text, boundary, _charset = 'utf8') {
|
|
54
|
-
// console.log({ boundary, text })
|
|
55
55
|
const formData = new FormData()
|
|
56
56
|
|
|
57
|
+
if(text === undefined) { return formData }
|
|
58
|
+
if(boundary === undefined) { return formData }
|
|
59
|
+
|
|
57
60
|
if(text === '') {
|
|
58
61
|
// empty body
|
|
59
62
|
return formData
|
|
@@ -61,11 +64,6 @@ export class Multipart {
|
|
|
61
64
|
|
|
62
65
|
const lines = text.split(MULTIPART_SEPARATOR)
|
|
63
66
|
|
|
64
|
-
if(lines.length === 0) {
|
|
65
|
-
// missing body?
|
|
66
|
-
return formData
|
|
67
|
-
}
|
|
68
|
-
|
|
69
67
|
const boundaryBegin = `${BOUNDARY_MARK}${boundary}`
|
|
70
68
|
const boundaryEnd = `${BOUNDARY_MARK}${boundary}${BOUNDARY_MARK}`
|
|
71
69
|
|
|
@@ -94,8 +92,8 @@ export class Multipart {
|
|
|
94
92
|
const name = rawName?.toLowerCase()
|
|
95
93
|
// console.log('header', name, value)
|
|
96
94
|
if(name === MULTIPART_HEADER.CONTENT_TYPE) {
|
|
97
|
-
const
|
|
98
|
-
//
|
|
95
|
+
const contentType = ContentType.parse(value)
|
|
96
|
+
//console.log({ contentType })
|
|
99
97
|
}
|
|
100
98
|
else if(name === MULTIPART_HEADER.CONTENT_DISPOSITION) {
|
|
101
99
|
const disposition = ContentDisposition.parse(value)
|
|
@@ -103,8 +101,7 @@ export class Multipart {
|
|
|
103
101
|
throw new Error('disposition not form-data')
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
partName = disposition.name?.slice(1, -1)
|
|
104
|
+
partName = disposition.name
|
|
108
105
|
}
|
|
109
106
|
else {
|
|
110
107
|
// unsupported part header - ignore
|
|
@@ -128,10 +125,9 @@ export class Multipart {
|
|
|
128
125
|
}
|
|
129
126
|
state = MULTIPART_STATE.HEADERS
|
|
130
127
|
}
|
|
131
|
-
else {
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
128
|
+
// else {
|
|
129
|
+
// throw new Error('unknown state')
|
|
130
|
+
// }
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
return formData
|
|
@@ -150,6 +146,7 @@ export class Multipart {
|
|
|
150
146
|
|
|
151
147
|
return new ReadableStream({
|
|
152
148
|
type: 'bytes',
|
|
149
|
+
// async pull(controller) {},
|
|
153
150
|
async start(controller) {
|
|
154
151
|
const encoder = new TextEncoder()
|
|
155
152
|
|
|
@@ -158,7 +155,6 @@ export class Multipart {
|
|
|
158
155
|
controller.enqueue(encoder.encode(`${MULTIPART_HEADER.CONTENT_TYPE}: ${contentType}${MULTIPART_SEPARATOR}`))
|
|
159
156
|
controller.enqueue(encoder.encode(`${MULTIPART_HEADER.CONTENT_RANGE}: ${ContentRange.encode({ ...part.range, size: contentLength })}${MULTIPART_SEPARATOR}`))
|
|
160
157
|
controller.enqueue(encoder.encode(MULTIPART_SEPARATOR))
|
|
161
|
-
// controller.enqueue(encoder.encode(MULTIPART_SEPARATOR))
|
|
162
158
|
|
|
163
159
|
if(part.obj instanceof ReadableStream) {
|
|
164
160
|
// biome-ignore lint/performance/noAwaitInLoops: readable
|
|
@@ -175,7 +171,10 @@ export class Multipart {
|
|
|
175
171
|
}
|
|
176
172
|
}
|
|
177
173
|
}
|
|
178
|
-
else if(part.obj instanceof ArrayBuffer
|
|
174
|
+
else if(part.obj instanceof ArrayBuffer) {
|
|
175
|
+
controller.enqueue(new Uint8Array(part.obj))
|
|
176
|
+
}
|
|
177
|
+
else if(ArrayBuffer.isView(part.obj)) {
|
|
179
178
|
controller.enqueue(part.obj)
|
|
180
179
|
}
|
|
181
180
|
else if(typeof part.obj === 'string'){
|
|
@@ -183,7 +182,7 @@ export class Multipart {
|
|
|
183
182
|
}
|
|
184
183
|
else {
|
|
185
184
|
// console.log('error', typeof part.obj, part.obj)
|
|
186
|
-
|
|
185
|
+
controller.error(new Error('unknown part type'))
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
controller.enqueue(encoder.encode(MULTIPART_SEPARATOR))
|
|
@@ -191,7 +190,9 @@ export class Multipart {
|
|
|
191
190
|
|
|
192
191
|
controller.enqueue(encoder.encode(boundaryEnd))
|
|
193
192
|
|
|
194
|
-
|
|
193
|
+
// controller.enqueue(encoder.encode(MULTIPART_SEPARATOR))
|
|
194
|
+
|
|
195
|
+
controller.close()
|
|
195
196
|
}
|
|
196
197
|
})
|
|
197
198
|
}
|
|
@@ -128,46 +128,3 @@ export class AppliedPreferences {
|
|
|
128
128
|
return AppliedPreferences.#encode_Map(applied)
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// console.log(AppliedPreferences.encode(undefined))
|
|
134
|
-
// console.log(AppliedPreferences.encode({ }))
|
|
135
|
-
// console.log(AppliedPreferences.encode({ wait: 10 }))
|
|
136
|
-
// console.log(AppliedPreferences.encode({ asynchronous: undefined }))
|
|
137
|
-
// console.log(AppliedPreferences.encode({ asynchronous: false }))
|
|
138
|
-
// console.log(AppliedPreferences.encode({ asynchronous: true }))
|
|
139
|
-
// console.log(AppliedPreferences.encode({ preferences: new Map([
|
|
140
|
-
// [ 'respond-async', { value: undefined } ]
|
|
141
|
-
// ]) }))
|
|
142
|
-
// console.log(AppliedPreferences.encode({
|
|
143
|
-
// asynchronous: false,
|
|
144
|
-
// preferences: new Map([
|
|
145
|
-
// [ 'respond-async', { value: 'fake' } ]
|
|
146
|
-
// ]) }))
|
|
147
|
-
// console.log(AppliedPreferences.encode({ asynchronous: true, wait: 100 }))
|
|
148
|
-
// console.log(AppliedPreferences.encode({
|
|
149
|
-
// representation: DIRECTIVE_REPRESENTATION_MINIMAL,
|
|
150
|
-
// preferences: new Map([
|
|
151
|
-
// ['foo', { value: 'bar', parameters: new Map([ [ 'biz', 'bang' ] ]) } ],
|
|
152
|
-
// [ 'fake', undefined ]
|
|
153
|
-
// ])
|
|
154
|
-
// }))
|
|
155
|
-
|
|
156
|
-
// console.log(Preferences.parse('handling=lenient, wait=100, respond-async'))
|
|
157
|
-
// console.log(Preferences.parse(' foo; bar')?.preferences)
|
|
158
|
-
// console.log(Preferences.parse(' foo; bar=""')?.preferences)
|
|
159
|
-
// console.log(Preferences.parse(' foo=""; bar')?.preferences)
|
|
160
|
-
// console.log(Preferences.parse(' foo =""; bar;biz; bang ')?.preferences)
|
|
161
|
-
// console.log(Preferences.parse('return=minimal; foo="some parameter"')?.preferences)
|
|
162
|
-
|
|
163
|
-
// console.log(Preferences.parse('timezone=America/Los_Angeles'))
|
|
164
|
-
// console.log(Preferences.parse('return=headers-only'))
|
|
165
|
-
// console.log(Preferences.parse('return=minimal'))
|
|
166
|
-
// console.log(Preferences.parse('return=representation'))
|
|
167
|
-
// console.log(Preferences.parse('respond-async, wait=10`'))
|
|
168
|
-
// console.log(Preferences.parse('priority=5'))
|
|
169
|
-
// console.log(Preferences.parse('foo; bar'))
|
|
170
|
-
// console.log(Preferences.parse('foo; bar=""'))
|
|
171
|
-
// console.log(Preferences.parse('foo=""; bar'))
|
|
172
|
-
// console.log(Preferences.parse('handling=lenient, wait=100, respond-async'))
|
|
173
|
-
// console.log(Preferences.parse('return=minimal; foo="some parameter"'))
|
package/src/headers/range.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { RANGE_UNITS_BYTES } from '../defs.js'
|
|
2
2
|
|
|
3
|
+
/** @import { AcceptRangeUnits } from '../defs.js' */
|
|
4
|
+
|
|
3
5
|
export const RANGE_EQUAL = '='
|
|
4
6
|
export const RANGE_SEPARATOR = '-'
|
|
5
7
|
export const RANGE_LIST_SEPARATOR = ','
|
|
@@ -32,7 +34,7 @@ export const RANGE_EMPTY = ''
|
|
|
32
34
|
/**
|
|
33
35
|
* @template RV
|
|
34
36
|
* @typedef {Object} RangeDirective
|
|
35
|
-
* @property {
|
|
37
|
+
* @property {AcceptRangeUnits|undefined} units
|
|
36
38
|
* @property {Array<RV>} ranges
|
|
37
39
|
*/
|
|
38
40
|
|
|
@@ -97,6 +99,8 @@ export class Range {
|
|
|
97
99
|
*/
|
|
98
100
|
static normalize(directive, contentLength) {
|
|
99
101
|
if(directive === undefined) { return undefined }
|
|
102
|
+
if(!Number.isInteger(contentLength)) { return undefined }
|
|
103
|
+
if(contentLength <= 0) { return undefined }
|
|
100
104
|
|
|
101
105
|
/** @type {Array<NormalizedRangeValue>} */
|
|
102
106
|
const normalizedRanges = directive.ranges.map(({ start, end }) => {
|
|
@@ -122,33 +126,3 @@ export class Range {
|
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
|
-
|
|
126
|
-
// console.log(Range.parse(''))
|
|
127
|
-
// console.log(Range.parse('='))
|
|
128
|
-
// console.log(Range.parse('foo'))
|
|
129
|
-
// console.log(Range.parse('bytes'))
|
|
130
|
-
// console.log(Range.parse('bytes='))
|
|
131
|
-
// console.log(Range.parse('bytes=-'))
|
|
132
|
-
// console.log(Range.parse('bytes=foo'))
|
|
133
|
-
// console.log(Range.parse('bytes=0-foo'))
|
|
134
|
-
// console.log(Range.parse('bytes=0-0xff'))
|
|
135
|
-
// console.log()
|
|
136
|
-
// console.log(Range.parse('bytes=1024-'))
|
|
137
|
-
// console.log(Range.parse('bytes=-1024'))
|
|
138
|
-
// console.log(Range.parse('bytes=0-1024'))
|
|
139
|
-
// console.log()
|
|
140
|
-
// console.log(Range.parse('bytes=0-0,-1'))
|
|
141
|
-
// console.log(Range.parse('bytes=0-1024, -1024'))
|
|
142
|
-
// console.log(Range.parse('bytes= 0-999, 4500-5499, -1000'))
|
|
143
|
-
// console.log(Range.parse('bytes=500-600,601-999'))
|
|
144
|
-
|
|
145
|
-
// console.log('------')
|
|
146
|
-
// console.log(Range.normalize(Range.parse('bytes=1024-'), 5000))
|
|
147
|
-
// console.log(Range.normalize(Range.parse('bytes=-1024'), 5000))
|
|
148
|
-
// console.log(Range.normalize(Range.parse('bytes=0-1024'), 5000))
|
|
149
|
-
// console.log(Range.normalize(Range.parse('bytes=0-0,-1'), 10000)) // 0 and 9999
|
|
150
|
-
// console.log(Range.normalize(Range.parse('bytes=0-1024, -1024'), 5000))
|
|
151
|
-
// console.log(Range.normalize(Range.parse('bytes= 0-999, 4500-5499, -1000'), 5000))
|
|
152
|
-
// console.log(Range.normalize(Range.parse('bytes=500-600,601-999'), 5000))
|
|
153
|
-
|
|
154
|
-
// console.log(Range.normalize(Range.parse('bytes=-500'), 10000)) // 9500-9999
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-10.html
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export const HTTP_HEADER_RATE_LIMIT = 'RateLimit'
|
|
4
4
|
export const HTTP_HEADER_RATE_LIMIT_POLICY = 'RateLimit-Policy'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -45,6 +45,7 @@ export class RateLimit {
|
|
|
45
45
|
* @param {RateLimitInfo} limitInfo
|
|
46
46
|
*/
|
|
47
47
|
static from(limitInfo) {
|
|
48
|
+
if(limitInfo === undefined) { return undefined }
|
|
48
49
|
const { name, remaining, resetSeconds, partitionKey } = limitInfo
|
|
49
50
|
|
|
50
51
|
if(name === undefined || remaining === undefined) { return undefined }
|
|
@@ -61,8 +62,12 @@ export class RateLimitPolicy {
|
|
|
61
62
|
*/
|
|
62
63
|
static from(...policies) {
|
|
63
64
|
if(policies === undefined) { return undefined }
|
|
65
|
+
if(policies.length === 0) { return undefined }
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
const remainingPolicies = policies.filter(pol => pol !== undefined)
|
|
68
|
+
if(remainingPolicies.length === 0) { return undefined }
|
|
69
|
+
|
|
70
|
+
return remainingPolicies
|
|
66
71
|
.filter(policy => policy.name !== undefined && policy.quota !== undefined)
|
|
67
72
|
.map(policy => {
|
|
68
73
|
const {
|
|
@@ -19,7 +19,7 @@ export const SERVER_TIMING_SEPARATOR = {
|
|
|
19
19
|
|
|
20
20
|
export class ServerTiming {
|
|
21
21
|
/**
|
|
22
|
-
* @param {Array<TimingsInfo
|
|
22
|
+
* @param {Array<TimingsInfo>|undefined} timings
|
|
23
23
|
*/
|
|
24
24
|
static encode(timings) {
|
|
25
25
|
if(timings === undefined) { return undefined }
|
|
@@ -36,16 +36,3 @@ export class ServerTiming {
|
|
|
36
36
|
.join(SERVER_TIMING_SEPARATOR.METRIC)
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// console.log(ServerTiming.encode([{ name: 'missedCache' }]))
|
|
42
|
-
// console.log(ServerTiming.encode([{ name: 'cpu', duration: 2.4 }]))
|
|
43
|
-
|
|
44
|
-
// // cache;desc="Cache Read";dur=23.2
|
|
45
|
-
// console.log(ServerTiming.encode([{ name: 'cache', duration: 23.2, description: "Cache Read" }]))
|
|
46
|
-
|
|
47
|
-
// // db;dur=53, app;dur=47.2
|
|
48
|
-
// console.log(ServerTiming.encode([
|
|
49
|
-
// { name: 'db', duration: 54 },
|
|
50
|
-
// { name: 'app', duration: 47.2 }
|
|
51
|
-
// ]))
|
package/src/headers/util/mime.js
CHANGED
|
@@ -19,7 +19,7 @@ export const SPECIAL_CHARS = [
|
|
|
19
19
|
'{', '}',
|
|
20
20
|
'@', ',', ';', ':',
|
|
21
21
|
'\\', '"', '/', '?', '=', // '.',
|
|
22
|
-
// '%', // '!', '$', '&', // #
|
|
22
|
+
// '%', // '!', '$', '&', // # ^ * | ~ `
|
|
23
23
|
// space
|
|
24
24
|
' ', '\u000B', '\u000C',
|
|
25
25
|
// control
|
|
@@ -66,10 +66,10 @@ export class Mime {
|
|
|
66
66
|
// if(candidateSubtype === '') { return undefined }
|
|
67
67
|
if(hasSpecialChar(candidateSubtype)) { return undefined }
|
|
68
68
|
|
|
69
|
-
const subtype = (candidateSubtype === '') ? MIME_ANY : candidateSubtype ?? MIME_ANY
|
|
69
|
+
const subtype = (candidateSubtype === '') ? MIME_ANY : (candidateSubtype ?? MIME_ANY)
|
|
70
70
|
|
|
71
71
|
return {
|
|
72
|
-
mimetype: `${type}${MIME_SEPARATOR.SUBTYPE}${subtype
|
|
72
|
+
mimetype: `${type}${MIME_SEPARATOR.SUBTYPE}${subtype}`,
|
|
73
73
|
type,
|
|
74
74
|
subtype
|
|
75
75
|
}
|
|
@@ -3,25 +3,31 @@ import http2 from 'node:http2'
|
|
|
3
3
|
import { send_bytes } from '../send-util.js'
|
|
4
4
|
|
|
5
5
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
6
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
7
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
8
|
-
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
9
|
-
/** @import { SendBody } from '../send-util.js' */
|
|
6
|
+
/** @import { SendContent, SendInfo, Metadata, SendBody } from '../../defs.js' */
|
|
10
7
|
|
|
11
8
|
const { HTTP_STATUS_OK } = http2.constants
|
|
12
9
|
|
|
13
10
|
/**
|
|
14
11
|
* @param {ServerHttp2Stream} stream
|
|
15
12
|
* @param {SendBody|undefined} obj
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {string|undefined} encoding
|
|
19
|
-
* @param {EtagItem|undefined} etag
|
|
20
|
-
* @param {number|undefined} age
|
|
21
|
-
* @param {CacheControlOptions} cacheControl
|
|
22
|
-
* @param {'bytes'|'none'|undefined} acceptRanges
|
|
13
|
+
* @param {Omit<SendContent, 'rangeDirective'>} content
|
|
14
|
+
* @param {Pick<SendInfo, 'acceptRanges'>} info
|
|
23
15
|
* @param {Metadata} meta
|
|
24
16
|
*/
|
|
25
|
-
export function sendBytes(stream,
|
|
26
|
-
|
|
17
|
+
export function sendBytes(stream, obj, content, info, meta) {
|
|
18
|
+
const {
|
|
19
|
+
contentType,
|
|
20
|
+
contentLength,
|
|
21
|
+
encoding,
|
|
22
|
+
etag,
|
|
23
|
+
lastModified,
|
|
24
|
+
age,
|
|
25
|
+
cacheControl,
|
|
26
|
+
} = content
|
|
27
|
+
|
|
28
|
+
const { acceptRanges } = info
|
|
29
|
+
|
|
30
|
+
const supportedQueryType = undefined
|
|
31
|
+
const range = undefined
|
|
32
|
+
send_bytes(stream, HTTP_STATUS_OK, contentType, obj, range, contentLength, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryType, meta)
|
|
27
33
|
}
|
|
@@ -4,25 +4,33 @@ import { Conditional } from '../../headers/conditional.js'
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
7
|
+
/** @import { SendContent, Metadata } from '../../defs.js' */
|
|
9
8
|
|
|
10
9
|
const {
|
|
11
10
|
HTTP2_HEADER_LOCATION,
|
|
12
|
-
HTTP2_HEADER_ETAG
|
|
11
|
+
HTTP2_HEADER_ETAG,
|
|
12
|
+
HTTP2_HEADER_LAST_MODIFIED
|
|
13
13
|
} = http2.constants
|
|
14
14
|
|
|
15
15
|
const { HTTP_STATUS_CREATED } = http2.constants
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @param {ServerHttp2Stream} stream
|
|
19
|
-
* @param {URL} location
|
|
20
|
-
* @param {
|
|
19
|
+
* @param {URL|string} location
|
|
20
|
+
* @param {Pick<SendContent, 'etag' | 'lastModified'>} content
|
|
21
21
|
* @param {Metadata} meta
|
|
22
22
|
*/
|
|
23
|
-
export function sendCreated(stream, location,
|
|
23
|
+
export function sendCreated(stream, location, content, meta) {
|
|
24
|
+
const {
|
|
25
|
+
etag,
|
|
26
|
+
lastModified
|
|
27
|
+
} = content
|
|
28
|
+
|
|
29
|
+
const loc = (location instanceof URL) ? location.href : location
|
|
30
|
+
|
|
24
31
|
send(stream, HTTP_STATUS_CREATED, {
|
|
25
|
-
[HTTP2_HEADER_LOCATION]:
|
|
26
|
-
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
32
|
+
[HTTP2_HEADER_LOCATION]: loc,
|
|
33
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
34
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified)
|
|
27
35
|
}, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
|
|
28
36
|
}
|
package/src/response/2xx/json.js
CHANGED
|
@@ -4,25 +4,44 @@ import { CONTENT_TYPE_JSON } from '../../headers/content-type.js'
|
|
|
4
4
|
import { send_encoded } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
9
|
-
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
7
|
+
/** @import { SendContent, SendInfo, Metadata } from '../../defs.js' */
|
|
10
8
|
|
|
11
9
|
const { HTTP_STATUS_OK } = http2.constants
|
|
12
10
|
|
|
13
11
|
/**
|
|
12
|
+
* @deprecated
|
|
14
13
|
* @param {ServerHttp2Stream} stream
|
|
15
14
|
* @param {Object} obj
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {number|undefined} age
|
|
19
|
-
* @param {CacheControlOptions} cacheControl
|
|
20
|
-
* @param {Array<string>|undefined} supportedQueryTypes
|
|
15
|
+
* @param {Omit<SendContent, 'contentType' | 'contentLength' | 'rangeDirective'>} content
|
|
16
|
+
* @param {Pick<SendInfo, 'supportedQueryTypes'>} info
|
|
21
17
|
* @param {Metadata} meta
|
|
22
18
|
*/
|
|
23
|
-
export function sendJSON_Encoded(stream, obj,
|
|
19
|
+
export function sendJSON_Encoded(stream, obj, content, info, meta) {
|
|
20
|
+
sendJSON(stream, obj, content, info, meta)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {ServerHttp2Stream} stream
|
|
25
|
+
* @param {Object} obj
|
|
26
|
+
* @param {Omit<SendContent, 'contentType' | 'contentLength' | 'rangeDirective'>} content
|
|
27
|
+
* @param {Pick<SendInfo, 'supportedQueryTypes'>} info
|
|
28
|
+
* @param {Metadata} meta
|
|
29
|
+
*/
|
|
30
|
+
export function sendJSON(stream, obj, content, info, meta) {
|
|
31
|
+
const {
|
|
32
|
+
encoding,
|
|
33
|
+
etag,
|
|
34
|
+
lastModified,
|
|
35
|
+
age,
|
|
36
|
+
cacheControl
|
|
37
|
+
} = content
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
supportedQueryTypes
|
|
41
|
+
} = info
|
|
42
|
+
|
|
24
43
|
if(stream.closed) { return }
|
|
25
44
|
|
|
26
45
|
const json = JSON.stringify(obj)
|
|
27
|
-
send_encoded(stream, HTTP_STATUS_OK, CONTENT_TYPE_JSON, json, encoding, etag, age, cacheControl, undefined, supportedQueryTypes, meta)
|
|
46
|
+
send_encoded(stream, HTTP_STATUS_OK, CONTENT_TYPE_JSON, json, encoding, etag, lastModified, age, cacheControl, undefined, supportedQueryTypes, meta)
|
|
28
47
|
}
|
|
@@ -4,22 +4,28 @@ import { Conditional } from '../../headers/conditional.js'
|
|
|
4
4
|
import { send } from '../send-util.js'
|
|
5
5
|
|
|
6
6
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
7
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
8
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
7
|
+
/** @import { SendContent, Metadata } from '../../defs.js' */
|
|
9
8
|
|
|
10
9
|
const {
|
|
11
|
-
HTTP2_HEADER_ETAG
|
|
10
|
+
HTTP2_HEADER_ETAG,
|
|
11
|
+
HTTP2_HEADER_LAST_MODIFIED
|
|
12
12
|
} = http2.constants
|
|
13
13
|
|
|
14
14
|
const { HTTP_STATUS_NO_CONTENT } = http2.constants
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @param {ServerHttp2Stream} stream
|
|
18
|
-
* @param {
|
|
18
|
+
* @param {Pick<SendContent, 'etag' | 'lastModified'>} content
|
|
19
19
|
* @param {Metadata} meta
|
|
20
20
|
*/
|
|
21
|
-
export function sendNoContent(stream,
|
|
21
|
+
export function sendNoContent(stream, content, meta) {
|
|
22
|
+
const {
|
|
23
|
+
etag,
|
|
24
|
+
lastModified
|
|
25
|
+
} = content
|
|
26
|
+
|
|
22
27
|
send(stream, HTTP_STATUS_NO_CONTENT, {
|
|
23
|
-
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag)
|
|
28
|
+
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
29
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified)
|
|
24
30
|
}, [], undefined, undefined, meta)
|
|
25
31
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
2
|
|
|
3
3
|
import { RANGE_UNITS_BYTES } from '../../defs.js'
|
|
4
|
-
import { MIME_TYPE_MULTIPART_RANGE } from '../../headers/content-type.js'
|
|
4
|
+
import { MIME_TYPE_MULTIPART_RANGE, MIME_TYPE_OCTET_STREAM } from '../../headers/content-type.js'
|
|
5
5
|
import { Multipart } from '../../headers/multipart.js'
|
|
6
6
|
import { send_bytes } from '../send-util.js'
|
|
7
7
|
|
|
8
8
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
9
|
-
/** @import { Metadata } from '../../defs.js' */
|
|
10
|
-
/** @import { EtagItem } from '../../headers/conditional.js' */
|
|
11
|
-
/** @import { CacheControlOptions } from '../../headers/cache-control.js' */
|
|
9
|
+
/** @import { SendContent, Metadata, SendBody } from '../../defs.js' */
|
|
12
10
|
/** @import { ContentRangeDirective } from '../../headers/content-range.js' */
|
|
13
|
-
/** @import { SendBody } from '../send-util.js' */
|
|
14
11
|
|
|
15
12
|
const { HTTP_STATUS_PARTIAL_CONTENT } = http2.constants
|
|
16
13
|
|
|
@@ -27,23 +24,28 @@ const { HTTP_STATUS_PARTIAL_CONTENT } = http2.constants
|
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* @param {ServerHttp2Stream} stream
|
|
30
|
-
* @param {string} contentType
|
|
31
27
|
* @param {NonEmptyArray<PartialBytes>|PartialBytes} objs
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {string|undefined} encoding
|
|
34
|
-
* @param {EtagItem|undefined} etag
|
|
35
|
-
* @param {number|undefined} age
|
|
36
|
-
* @param {CacheControlOptions} cacheControl
|
|
28
|
+
* @param {Omit<SendContent, 'rangeDirective'>} content
|
|
37
29
|
* @param {Metadata} meta
|
|
38
30
|
*/
|
|
39
|
-
export function sendPartialContent(stream,
|
|
31
|
+
export function sendPartialContent(stream, objs, content, meta) {
|
|
32
|
+
const {
|
|
33
|
+
contentType,
|
|
34
|
+
contentLength,
|
|
35
|
+
encoding,
|
|
36
|
+
etag,
|
|
37
|
+
lastModified,
|
|
38
|
+
age,
|
|
39
|
+
cacheControl
|
|
40
|
+
} = content
|
|
41
|
+
|
|
40
42
|
const acceptRanges = RANGE_UNITS_BYTES
|
|
41
43
|
const supportedQueryTypes = undefined
|
|
42
44
|
|
|
43
45
|
if(Array.isArray(objs) && objs.length > 1) {
|
|
44
46
|
// send using multipart bytes
|
|
45
47
|
const boundary = 'PARTIAL_CONTENT_BOUNDARY' // todo make unique for content
|
|
46
|
-
const obj = Multipart.encode_Bytes(contentType, objs, contentLength, boundary)
|
|
48
|
+
const obj = Multipart.encode_Bytes(contentType ?? MIME_TYPE_OCTET_STREAM, objs, contentLength, boundary)
|
|
47
49
|
|
|
48
50
|
const multipartContentType = `${MIME_TYPE_MULTIPART_RANGE}; boundary=${boundary}`
|
|
49
51
|
|
|
@@ -56,6 +58,7 @@ export function sendPartialContent(stream, contentType, objs, contentLength, enc
|
|
|
56
58
|
undefined,
|
|
57
59
|
encoding,
|
|
58
60
|
etag,
|
|
61
|
+
lastModified,
|
|
59
62
|
age,
|
|
60
63
|
cacheControl,
|
|
61
64
|
acceptRanges,
|
|
@@ -67,5 +70,5 @@ export function sendPartialContent(stream, contentType, objs, contentLength, enc
|
|
|
67
70
|
|
|
68
71
|
// single range, send as regular object
|
|
69
72
|
const obj = Array.isArray(objs) ? objs[0] : objs
|
|
70
|
-
send_bytes(stream, HTTP_STATUS_PARTIAL_CONTENT, contentType, obj.obj, obj.range, undefined, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta)
|
|
73
|
+
send_bytes(stream, HTTP_STATUS_PARTIAL_CONTENT, contentType, obj.obj, obj.range, undefined, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta)
|
|
71
74
|
}
|