@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.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -2
  3. package/src/defs.js +34 -1
  4. package/src/headers/accept-encoding.js +9 -12
  5. package/src/headers/accept-language.js +10 -4
  6. package/src/headers/accept.js +0 -28
  7. package/src/headers/cache-control.js +0 -6
  8. package/src/headers/clear-site-data.js +4 -10
  9. package/src/headers/client-hints.js +9 -2
  10. package/src/headers/conditional.js +170 -106
  11. package/src/headers/content-disposition.js +0 -14
  12. package/src/headers/content-range.js +0 -17
  13. package/src/headers/content-type.js +1 -1
  14. package/src/headers/forwarded.js +2 -33
  15. package/src/headers/link.js +11 -12
  16. package/src/headers/multipart.js +19 -18
  17. package/src/headers/preference.js +0 -43
  18. package/src/headers/range.js +5 -31
  19. package/src/headers/rate-limit.js +7 -2
  20. package/src/headers/server-timing.js +1 -14
  21. package/src/headers/strict-transport-security.js +1 -0
  22. package/src/headers/util/mime.js +3 -3
  23. package/src/headers/util/whitespace.js +3 -1
  24. package/src/headers/www-authenticate.js +0 -1
  25. package/src/response/2xx/bytes.js +19 -13
  26. package/src/response/2xx/created.js +16 -8
  27. package/src/response/2xx/json.js +29 -10
  28. package/src/response/2xx/no-content.js +12 -6
  29. package/src/response/2xx/partial-content.js +17 -14
  30. package/src/response/2xx/preflight.js +22 -10
  31. package/src/response/3xx/found.js +25 -0
  32. package/src/response/3xx/moved-permanently.js +7 -5
  33. package/src/response/3xx/not-modified.js +13 -8
  34. package/src/response/3xx/permanent-redirect.js +4 -2
  35. package/src/response/3xx/see-other.js +7 -5
  36. package/src/response/3xx/temporary-redirect.js +4 -2
  37. package/src/response/4xx/bad-request.js +19 -0
  38. package/src/response/4xx/content-too-large.js +1 -1
  39. package/src/response/4xx/gone.js +1 -1
  40. package/src/response/4xx/im-a-teapot.js +1 -1
  41. package/src/response/4xx/not-acceptable.js +6 -4
  42. package/src/response/4xx/not-allowed.js +6 -4
  43. package/src/response/4xx/payment-required.js +17 -0
  44. package/src/response/4xx/precondition-failed.js +18 -3
  45. package/src/response/4xx/range-not-satisfiable.js +5 -3
  46. package/src/response/4xx/too-many-requests.js +8 -5
  47. package/src/response/4xx/unauthorized.js +1 -1
  48. package/src/response/4xx/unsupported-media.js +9 -5
  49. package/src/response/5xx/error.js +2 -3
  50. package/src/response/5xx/insufficient-storage.js +2 -2
  51. package/src/response/5xx/not-implemented.js +2 -3
  52. package/src/response/5xx/unavailable.js +7 -12
  53. package/src/response/header-util.js +1 -1
  54. package/src/response/index.js +4 -0
  55. package/src/response/response.js +8 -2
  56. package/src/response/send-util.js +46 -14
@@ -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 { sendJSON_Encoded } from './2xx/json.js'
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: sendJSON_Encoded,
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
- /** @typedef { (data: InputType) => Buffer } EncoderFun */
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 {'bytes'|'none'|undefined} acceptRanges
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 {'bytes'|'none'|undefined} acceptRanges
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
- [HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`,
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
- Readable.fromWeb(body).pipe(stream)
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