@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/response/response.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { sendAccepted } from './2xx/accepted.js'
|
|
2
2
|
import { sendBytes } from './2xx/bytes.js'
|
|
3
3
|
import { sendCreated } from './2xx/created.js'
|
|
4
|
-
import {
|
|
4
|
+
import { sendJSON } from './2xx/json.js'
|
|
5
5
|
import { sendNoContent } from './2xx/no-content.js'
|
|
6
6
|
import { sendPartialContent } from './2xx/partial-content.js'
|
|
7
7
|
import { sendPreflight } from './2xx/preflight.js'
|
|
8
8
|
import { sendSSE } from './2xx/sse.js'
|
|
9
9
|
import { sendTrace } from './2xx/trace.js'
|
|
10
|
+
import { sendFound } from './3xx/found.js'
|
|
10
11
|
import { sendMovedPermanently } from './3xx/moved-permanently.js'
|
|
11
12
|
import { sendMultipleChoices } from './3xx/multiple-choices.js'
|
|
12
13
|
import { sendNotModified } from './3xx/not-modified.js'
|
|
13
14
|
import { sendPermanentRedirect } from './3xx/permanent-redirect.js'
|
|
14
15
|
import { sendSeeOther } from './3xx/see-other.js'
|
|
15
16
|
import { sendTemporaryRedirect } from './3xx/temporary-redirect.js'
|
|
17
|
+
import { sendBadRequest } from './4xx/bad-request.js'
|
|
16
18
|
import { sendConflict } from './4xx/conflict.js'
|
|
17
19
|
import { sendContentTooLarge } from './4xx/content-too-large.js'
|
|
18
20
|
import { sendForbidden } from './4xx/forbidden.js'
|
|
@@ -21,6 +23,7 @@ import { sendImATeapot } from './4xx/im-a-teapot.js'
|
|
|
21
23
|
import { sendNotAcceptable } from './4xx/not-acceptable.js'
|
|
22
24
|
import { sendNotAllowed } from './4xx/not-allowed.js'
|
|
23
25
|
import { sendNotFound } from './4xx/not-found.js'
|
|
26
|
+
import { sendPaymentRequired } from './4xx/payment-required.js'
|
|
24
27
|
import { sendPreconditionFailed } from './4xx/precondition-failed.js'
|
|
25
28
|
import { sendRangeNotSatisfiable } from './4xx/range-not-satisfiable.js'
|
|
26
29
|
import { sendTimeout } from './4xx/timeout.js'
|
|
@@ -35,16 +38,18 @@ import { sendUnavailable } from './5xx/unavailable.js'
|
|
|
35
38
|
|
|
36
39
|
export const Response = {
|
|
37
40
|
accepted: sendAccepted,
|
|
41
|
+
badRequest: sendBadRequest,
|
|
38
42
|
bytes: sendBytes,
|
|
39
43
|
conflict: sendConflict,
|
|
40
44
|
contentTooLarge: sendContentTooLarge,
|
|
41
45
|
created: sendCreated,
|
|
42
46
|
error: sendError,
|
|
43
47
|
forbidden: sendForbidden,
|
|
48
|
+
found: sendFound,
|
|
44
49
|
gone: sendGone,
|
|
45
50
|
imATeapot: sendImATeapot,
|
|
46
51
|
insufficientStorage: sendInsufficientStorage,
|
|
47
|
-
json:
|
|
52
|
+
json: sendJSON,
|
|
48
53
|
movedPermanently: sendMovedPermanently,
|
|
49
54
|
multipleChoices: sendMultipleChoices,
|
|
50
55
|
noContent: sendNoContent,
|
|
@@ -54,6 +59,7 @@ export const Response = {
|
|
|
54
59
|
notImplemented: sendNotImplemented,
|
|
55
60
|
notModified: sendNotModified,
|
|
56
61
|
partialContent: sendPartialContent,
|
|
62
|
+
paymentRequired: sendPaymentRequired,
|
|
57
63
|
permanentRedirect: sendPermanentRedirect,
|
|
58
64
|
preconditionFailed: sendPreconditionFailed,
|
|
59
65
|
preflight: sendPreflight,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http2 from 'node:http2'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
2
|
+
import { pipeline, Readable } from 'node:stream'
|
|
3
3
|
import { ReadableStream } from 'node:stream/web'
|
|
4
4
|
import {
|
|
5
5
|
brotliCompressSync,
|
|
@@ -12,7 +12,7 @@ import { HTTP_HEADER_ACCEPT_QUERY } from '../defs.js'
|
|
|
12
12
|
import { CacheControl } from '../headers/cache-control.js'
|
|
13
13
|
import { Conditional } from '../headers/conditional.js'
|
|
14
14
|
import { ContentRange } from '../headers/content-range.js'
|
|
15
|
-
import { CHARSET_UTF8 } from '../headers/content-type.js'
|
|
15
|
+
import { CHARSET_UTF8, CONTENT_TYPE_JSON } from '../headers/content-type.js'
|
|
16
16
|
import {
|
|
17
17
|
coreHeaders,
|
|
18
18
|
customHeaders,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
/** @import { ServerHttp2Stream } from 'node:http2' */
|
|
23
23
|
/** @import { OutgoingHttpHeaders } from 'node:http2' */
|
|
24
24
|
/** @import { InputType } from 'node:zlib' */
|
|
25
|
-
/** @import { Metadata, SendBody } from '../defs.js' */
|
|
26
|
-
/** @import { EtagItem } from '../headers/conditional.js' */
|
|
25
|
+
/** @import { AcceptRangeUnits, Metadata, SendBody } from '../defs.js' */
|
|
26
|
+
/** @import { EtagItem, IMFFixDateInput } from '../headers/conditional.js' */
|
|
27
27
|
/** @import { CacheControlOptions } from '../headers/cache-control.js' */
|
|
28
28
|
/** @import { ContentRangeDirective } from '../headers/content-range.js' */
|
|
29
29
|
|
|
@@ -38,16 +38,37 @@ const {
|
|
|
38
38
|
HTTP2_HEADER_VARY,
|
|
39
39
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
40
40
|
HTTP2_HEADER_ETAG,
|
|
41
|
+
HTTP2_HEADER_LAST_MODIFIED,
|
|
41
42
|
HTTP2_HEADER_AGE,
|
|
42
43
|
HTTP2_HEADER_ACCEPT_RANGES,
|
|
43
44
|
HTTP2_HEADER_CONTENT_RANGE,
|
|
44
45
|
HTTP2_HEADER_CONTENT_LENGTH,
|
|
45
46
|
HTTP2_HEADER_ACCEPT,
|
|
46
47
|
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
47
|
-
HTTP2_HEADER_RANGE
|
|
48
|
+
HTTP2_HEADER_RANGE,
|
|
49
|
+
HTTP2_HEADER_RETRY_AFTER
|
|
48
50
|
} = http2.constants
|
|
49
51
|
|
|
50
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* @param {ServerHttp2Stream} stream
|
|
54
|
+
* @param {number} status
|
|
55
|
+
* @param {string|undefined} message
|
|
56
|
+
* @param {number|undefined} retryAfter
|
|
57
|
+
* @param {Metadata} meta
|
|
58
|
+
*/
|
|
59
|
+
export function send_error(stream, status, message, retryAfter, meta) {
|
|
60
|
+
const obj = JSON.stringify({
|
|
61
|
+
message: message ?? 'Error'
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const exposedHeaders = Number.isInteger(retryAfter) ? [ HTTP2_HEADER_RETRY_AFTER ] : []
|
|
65
|
+
|
|
66
|
+
send(stream, status, {
|
|
67
|
+
[HTTP2_HEADER_RETRY_AFTER]: Number.isInteger(retryAfter) ? `${retryAfter}` : undefined
|
|
68
|
+
}, exposedHeaders, CONTENT_TYPE_JSON, obj, meta) // todo should this be plain text
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @typedef { (data: InputType) => Buffer<ArrayBuffer> } EncoderFun */
|
|
51
72
|
|
|
52
73
|
/** @type {Map<string, EncoderFun>} */
|
|
53
74
|
export const ENCODER_MAP = new Map([
|
|
@@ -64,13 +85,14 @@ export const ENCODER_MAP = new Map([
|
|
|
64
85
|
* @param {SendBody} body
|
|
65
86
|
* @param {string|undefined} encoding
|
|
66
87
|
* @param {EtagItem|undefined} etag
|
|
88
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
67
89
|
* @param {number|undefined} age
|
|
68
90
|
* @param {CacheControlOptions|undefined} cacheControl
|
|
69
|
-
* @param {
|
|
91
|
+
* @param {AcceptRangeUnits|undefined} acceptRanges
|
|
70
92
|
* @param {Array<string>|undefined} supportedQueryTypes
|
|
71
93
|
* @param {Metadata} meta
|
|
72
94
|
*/
|
|
73
|
-
export function send_encoded(stream, status, contentType, body, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
95
|
+
export function send_encoded(stream, status, contentType, body, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
74
96
|
const obj = (typeof body === 'string') ? Buffer.from(body, CHARSET_UTF8) : body
|
|
75
97
|
|
|
76
98
|
const useIdentity = encoding === 'identity'
|
|
@@ -86,10 +108,9 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
|
|
|
86
108
|
{ name: 'encode', duration: encodeEnd - encodeStart }
|
|
87
109
|
)
|
|
88
110
|
|
|
89
|
-
send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
|
|
111
|
+
send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
|
|
90
112
|
}
|
|
91
113
|
|
|
92
|
-
|
|
93
114
|
/**
|
|
94
115
|
* @param {ServerHttp2Stream} stream
|
|
95
116
|
* @param {number} status
|
|
@@ -99,13 +120,14 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
|
|
|
99
120
|
* @param {number|undefined} contentLength
|
|
100
121
|
* @param {string|undefined} encoding
|
|
101
122
|
* @param {EtagItem|undefined} etag
|
|
123
|
+
* @param {IMFFixDateInput|string|undefined} lastModified
|
|
102
124
|
* @param {number|undefined} age
|
|
103
125
|
* @param {CacheControlOptions|undefined} cacheControl
|
|
104
|
-
* @param {
|
|
126
|
+
* @param {AcceptRangeUnits|undefined} acceptRanges
|
|
105
127
|
* @param {Array<string>|undefined} supportedQueryTypes
|
|
106
128
|
* @param {Metadata} meta
|
|
107
129
|
*/
|
|
108
|
-
export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
130
|
+
export function send_bytes(stream, status, contentType, obj, range, contentLength, encoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta) {
|
|
109
131
|
const contentLen = Number.isInteger(contentLength) ? `${contentLength}` : undefined
|
|
110
132
|
const supportsQuery = supportedQueryTypes !== undefined && supportedQueryTypes.length > 0
|
|
111
133
|
|
|
@@ -123,7 +145,8 @@ export function send_bytes(stream, status, contentType, obj, range, contentLengt
|
|
|
123
145
|
[HTTP2_HEADER_VARY]: varyHeaders.join(','),
|
|
124
146
|
[HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
|
|
125
147
|
[HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
|
|
126
|
-
[
|
|
148
|
+
[HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
|
|
149
|
+
[HTTP2_HEADER_AGE]: Number.isInteger(age) ? `${age}` : undefined,
|
|
127
150
|
[HTTP2_HEADER_CONTENT_LENGTH]: contentLen,
|
|
128
151
|
[HTTP2_HEADER_CONTENT_RANGE]: ContentRange.encode(range),
|
|
129
152
|
[HTTP2_HEADER_ACCEPT_RANGES]: acceptRanges,
|
|
@@ -164,7 +187,16 @@ export function send(stream, status, headers, exposedHeaders, contentType, body,
|
|
|
164
187
|
|
|
165
188
|
if(stream.writable && body !== undefined) {
|
|
166
189
|
if(body instanceof ReadableStream) {
|
|
167
|
-
|
|
190
|
+
const signal = undefined // AbortSignal.timeout(1000)
|
|
191
|
+
pipeline(
|
|
192
|
+
Readable.fromWeb(body, { signal }),
|
|
193
|
+
stream,
|
|
194
|
+
err => {
|
|
195
|
+
if(err !== null && err !== undefined) {
|
|
196
|
+
console.warn('pipeline error', err)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
168
200
|
return
|
|
169
201
|
}
|
|
170
202
|
|