@johntalton/http-util 6.1.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 (41) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/src/defs.js +1 -1
  4. package/src/headers/conditional.js +1 -1
  5. package/src/headers/link.js +1 -1
  6. package/src/headers/multipart.js +4 -11
  7. package/src/headers/range.js +2 -0
  8. package/src/headers/rate-limit.js +1 -1
  9. package/src/headers/util/mime.js +1 -1
  10. package/src/headers/util/whitespace.js +1 -1
  11. package/src/headers/www-authenticate.js +0 -1
  12. package/src/response/2xx/bytes.js +2 -31
  13. package/src/response/2xx/created.js +5 -18
  14. package/src/response/2xx/json.js +6 -19
  15. package/src/response/2xx/no-content.js +1 -15
  16. package/src/response/2xx/partial-content.js +1 -28
  17. package/src/response/2xx/preflight.js +12 -19
  18. package/src/response/3xx/found.js +5 -3
  19. package/src/response/3xx/moved-permanently.js +7 -5
  20. package/src/response/3xx/not-modified.js +2 -21
  21. package/src/response/3xx/permanent-redirect.js +4 -2
  22. package/src/response/3xx/see-other.js +7 -5
  23. package/src/response/3xx/temporary-redirect.js +4 -2
  24. package/src/response/4xx/bad-request.js +3 -3
  25. package/src/response/4xx/content-too-large.js +1 -1
  26. package/src/response/4xx/gone.js +1 -1
  27. package/src/response/4xx/im-a-teapot.js +1 -1
  28. package/src/response/4xx/not-acceptable.js +2 -11
  29. package/src/response/4xx/not-allowed.js +1 -10
  30. package/src/response/4xx/payment-required.js +1 -1
  31. package/src/response/4xx/precondition-failed.js +1 -15
  32. package/src/response/4xx/range-not-satisfiable.js +1 -10
  33. package/src/response/4xx/too-many-requests.js +1 -15
  34. package/src/response/4xx/unsupported-media.js +1 -14
  35. package/src/response/5xx/error.js +2 -3
  36. package/src/response/5xx/insufficient-storage.js +2 -2
  37. package/src/response/5xx/not-implemented.js +2 -3
  38. package/src/response/5xx/unavailable.js +3 -20
  39. package/src/response/header-util.js +1 -1
  40. package/src/response/response.js +2 -2
  41. package/src/response/send-util.js +25 -6
package/README.md CHANGED
@@ -64,7 +64,7 @@ All responders take in a `stream` as well as a metadata object to hint on server
64
64
  - [`sendGone`](#responsegone)
65
65
  - [`sendImATeapot`](#)
66
66
  - [`sendInsufficientStorage`](#)
67
- - [`sendJSON_Encoded`](#responsejson) - Standard Ok response with encoding
67
+ - [`sendJSON`](#responsejson) - Standard Ok response with encoding
68
68
  - [`sendMovedPermanently`](#responsemovedpermanently)
69
69
  - [`sendMultipleChoices`](#)
70
70
  - [`sendNoContent`](#responsenocontent)
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@johntalton/http-util",
3
- "version": "6.1.0",
3
+ "version": "7.0.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "imports": {
7
8
  "#util": "./src/headers/util/index.js"
8
9
  },
package/src/defs.js CHANGED
@@ -28,7 +28,7 @@ export const RANGE_UNITS_NONE = 'none'
28
28
 
29
29
 
30
30
  /** @import { TimingsInfo } from './headers/server-timing.js' */
31
- /** @import { EtagItem, IMFFixDateInput, } from './headers/conditional.js' */
31
+ /** @import { EtagItem, IMFFixDateInput } from './headers/conditional.js' */
32
32
  /** @import { CacheControlOptions } from './headers/cache-control.js' */
33
33
  /** @import { ContentRangeDirective } from './headers/content-range.js' */
34
34
  /** @import { RateLimitPolicyInfo, RateLimitInfo } from './headers/rate-limit.js' */
@@ -102,7 +102,7 @@ export class ETag {
102
102
  static isValid(etag) {
103
103
  if(etag === undefined) { return false }
104
104
 
105
- // %x21 / %x23-7E and %x80-FF
105
+ // %x21 / %x23-7E and %x80-FF
106
106
  for(const c of etag) {
107
107
  if(c.charCodeAt(0) < 0x21) { return false }
108
108
  if(c.charCodeAt(0) > 0xFF) { return false }
@@ -9,7 +9,7 @@ export class Link {
9
9
  /**
10
10
  * @param {LinkItem} link
11
11
  */
12
- static *#encode(link) {
12
+ static *#encode(link) {
13
13
  const encodedUri = (link.url instanceof URL) ? link.url : encodeURI(link.url)
14
14
 
15
15
  yield `<${encodedUri}>`
@@ -1,6 +1,5 @@
1
1
  import { ReadableStream } from 'node:stream/web'
2
2
 
3
- import { isQuoted, stripQuotes } from '../headers/util/quote.js'
4
3
  import { ContentDisposition } from './content-disposition.js'
5
4
  import { ContentRange } from './content-range.js'
6
5
  import { ContentType } from './content-type.js'
@@ -65,11 +64,6 @@ export class Multipart {
65
64
 
66
65
  const lines = text.split(MULTIPART_SEPARATOR)
67
66
 
68
- if(lines.length === 0) {
69
- // missing body?
70
- return formData
71
- }
72
-
73
67
  const boundaryBegin = `${BOUNDARY_MARK}${boundary}`
74
68
  const boundaryEnd = `${BOUNDARY_MARK}${boundary}${BOUNDARY_MARK}`
75
69
 
@@ -107,7 +101,7 @@ export class Multipart {
107
101
  throw new Error('disposition not form-data')
108
102
  }
109
103
 
110
- partName = isQuoted(disposition.name) ? stripQuotes(disposition.name) : disposition.name
104
+ partName = disposition.name
111
105
  }
112
106
  else {
113
107
  // unsupported part header - ignore
@@ -131,10 +125,9 @@ export class Multipart {
131
125
  }
132
126
  state = MULTIPART_STATE.HEADERS
133
127
  }
134
- else {
135
- throw new Error('unknown state')
136
- }
137
-
128
+ // else {
129
+ // throw new Error('unknown state')
130
+ // }
138
131
  }
139
132
 
140
133
  return formData
@@ -99,6 +99,8 @@ export class Range {
99
99
  */
100
100
  static normalize(directive, contentLength) {
101
101
  if(directive === undefined) { return undefined }
102
+ if(!Number.isInteger(contentLength)) { return undefined }
103
+ if(contentLength <= 0) { return undefined }
102
104
 
103
105
  /** @type {Array<NormalizedRangeValue>} */
104
106
  const normalizedRanges = directive.ranges.map(({ start, end }) => {
@@ -1,6 +1,6 @@
1
1
  // https://www.ietf.org/archive/id/draft-ietf-httpapi-ratelimit-headers-10.html
2
2
 
3
- export const HTTP_HEADER_RATE_LIMIT = 'RateLimit'
3
+ export const HTTP_HEADER_RATE_LIMIT = 'RateLimit'
4
4
  export const HTTP_HEADER_RATE_LIMIT_POLICY = 'RateLimit-Policy'
5
5
 
6
6
  /**
@@ -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
@@ -4,5 +4,5 @@ export const WHITESPACE_REGEX = /\s/
4
4
  * @param {string} c
5
5
  */
6
6
  export function isWhitespace(c){
7
- return WHITESPACE_REGEX.test(c)
7
+ return WHITESPACE_REGEX.test(c)
8
8
  }
@@ -31,7 +31,6 @@ export function paramNeedQuotes(paramName) {
31
31
  return PARAMETERS_THAT_NEED_QUOTES.includes(paramName.toLowerCase())
32
32
  }
33
33
 
34
-
35
34
  export class Challenge {
36
35
  /**
37
36
  * @param {string} realm
@@ -3,39 +3,10 @@ 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 { AcceptRangeUnits, SendContent, SendInfo, Metadata, SendBody } from '../../defs.js' */
7
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
8
- /** @import { CacheControlOptions } from '../../headers/cache-control.js' */
6
+ /** @import { SendContent, SendInfo, Metadata, SendBody } from '../../defs.js' */
9
7
 
10
8
  const { HTTP_STATUS_OK } = http2.constants
11
9
 
12
- /**
13
- * @param {ServerHttp2Stream} stream
14
- * @param {SendBody|undefined} obj
15
- * @param {string|undefined} contentType
16
- * @param {number|undefined} contentLength
17
- * @param {string|undefined} encoding
18
- * @param {EtagItem|undefined} etag
19
- * @param {IMFFixDateInput|string|undefined} lastModified
20
- * @param {number|undefined} age
21
- * @param {CacheControlOptions} cacheControl
22
- * @param {AcceptRangeUnits|undefined} acceptRanges
23
- * @param {Metadata} meta
24
- */
25
- export function sendBytes(stream, contentType, obj, contentLength, encoding, etag, lastModified, age, cacheControl, acceptRanges, meta) {
26
- _sendBytes(stream, obj, {
27
- contentType,
28
- contentLength,
29
- encoding,
30
- etag,
31
- lastModified,
32
- age,
33
- cacheControl
34
- }, {
35
- acceptRanges
36
- }, meta)
37
- }
38
-
39
10
  /**
40
11
  * @param {ServerHttp2Stream} stream
41
12
  * @param {SendBody|undefined} obj
@@ -43,7 +14,7 @@ export function sendBytes(stream, contentType, obj, contentLength, encoding, eta
43
14
  * @param {Pick<SendInfo, 'acceptRanges'>} info
44
15
  * @param {Metadata} meta
45
16
  */
46
- export function _sendBytes(stream, obj, content, info, meta) {
17
+ export function sendBytes(stream, obj, content, info, meta) {
47
18
  const {
48
19
  contentType,
49
20
  contentLength,
@@ -5,7 +5,6 @@ import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
7
  /** @import { SendContent, Metadata } from '../../defs.js' */
8
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
9
8
 
10
9
  const {
11
10
  HTTP2_HEADER_LOCATION,
@@ -17,32 +16,20 @@ const { HTTP_STATUS_CREATED } = http2.constants
17
16
 
18
17
  /**
19
18
  * @param {ServerHttp2Stream} stream
20
- * @param {URL} location
21
- * @param {EtagItem|undefined} etag
22
- * @param {IMFFixDateInput|string|undefined} lastModified
23
- * @param {Metadata} meta
24
- */
25
- export function sendCreated(stream, location, etag, lastModified, meta) {
26
- _sendCreated(stream, location, {
27
- etag,
28
- lastModified
29
- }, meta)
30
- }
31
-
32
- /**
33
- * @param {ServerHttp2Stream} stream
34
- * @param {URL} location
19
+ * @param {URL|string} location
35
20
  * @param {Pick<SendContent, 'etag' | 'lastModified'>} content
36
21
  * @param {Metadata} meta
37
22
  */
38
- export function _sendCreated(stream, location, content, meta) {
23
+ export function sendCreated(stream, location, content, meta) {
39
24
  const {
40
25
  etag,
41
26
  lastModified
42
27
  } = content
43
28
 
29
+ const loc = (location instanceof URL) ? location.href : location
30
+
44
31
  send(stream, HTTP_STATUS_CREATED, {
45
- [HTTP2_HEADER_LOCATION]: location.href,
32
+ [HTTP2_HEADER_LOCATION]: loc,
46
33
  [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
47
34
  [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified)
48
35
  }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
@@ -5,32 +5,19 @@ import { send_encoded } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
7
  /** @import { SendContent, SendInfo, Metadata } from '../../defs.js' */
8
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
9
- /** @import { CacheControlOptions } from '../../headers/cache-control.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 {string|undefined} encoding
17
- * @param {EtagItem|undefined} etag
18
- * @param {IMFFixDateInput|string|undefined} lastModified
19
- * @param {number|undefined} age
20
- * @param {CacheControlOptions} cacheControl
21
- * @param {Array<string>|undefined} supportedQueryTypes
15
+ * @param {Omit<SendContent, 'contentType' | 'contentLength' | 'rangeDirective'>} content
16
+ * @param {Pick<SendInfo, 'supportedQueryTypes'>} info
22
17
  * @param {Metadata} meta
23
18
  */
24
- export function sendJSON_Encoded(stream, obj, encoding, etag, lastModified, age, cacheControl, supportedQueryTypes, meta) {
25
- _sendJSON_Encoded(stream, obj, {
26
- encoding,
27
- etag,
28
- lastModified,
29
- age,
30
- cacheControl
31
- }, {
32
- supportedQueryTypes
33
- }, meta)
19
+ export function sendJSON_Encoded(stream, obj, content, info, meta) {
20
+ sendJSON(stream, obj, content, info, meta)
34
21
  }
35
22
 
36
23
  /**
@@ -40,7 +27,7 @@ export function sendJSON_Encoded(stream, obj, encoding, etag, lastModified, age,
40
27
  * @param {Pick<SendInfo, 'supportedQueryTypes'>} info
41
28
  * @param {Metadata} meta
42
29
  */
43
- export function _sendJSON_Encoded(stream, obj, content, info, meta) {
30
+ export function sendJSON(stream, obj, content, info, meta) {
44
31
  const {
45
32
  encoding,
46
33
  etag,
@@ -5,7 +5,6 @@ import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
7
  /** @import { SendContent, Metadata } from '../../defs.js' */
8
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
9
8
 
10
9
  const {
11
10
  HTTP2_HEADER_ETAG,
@@ -14,25 +13,12 @@ const {
14
13
 
15
14
  const { HTTP_STATUS_NO_CONTENT } = http2.constants
16
15
 
17
- /**
18
- * @param {ServerHttp2Stream} stream
19
- * @param {EtagItem|undefined} etag
20
- * @param {IMFFixDateInput|string|undefined} lastModified
21
- * @param {Metadata} meta
22
- */
23
- export function sendNoContent(stream, etag, lastModified, meta) {
24
- _sendNoContent(stream, {
25
- etag,
26
- lastModified
27
- }, meta)
28
- }
29
-
30
16
  /**
31
17
  * @param {ServerHttp2Stream} stream
32
18
  * @param {Pick<SendContent, 'etag' | 'lastModified'>} content
33
19
  * @param {Metadata} meta
34
20
  */
35
- export function _sendNoContent(stream, content, meta) {
21
+ export function sendNoContent(stream, content, meta) {
36
22
  const {
37
23
  etag,
38
24
  lastModified
@@ -7,11 +7,8 @@ import { send_bytes } from '../send-util.js'
7
7
 
8
8
  /** @import { ServerHttp2Stream } from 'node:http2' */
9
9
  /** @import { SendContent, Metadata, SendBody } from '../../defs.js' */
10
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
11
- /** @import { CacheControlOptions } from '../../headers/cache-control.js' */
12
10
  /** @import { ContentRangeDirective } from '../../headers/content-range.js' */
13
11
 
14
-
15
12
  const { HTTP_STATUS_PARTIAL_CONTENT } = http2.constants
16
13
 
17
14
  /**
@@ -25,37 +22,13 @@ const { HTTP_STATUS_PARTIAL_CONTENT } = http2.constants
25
22
  * @property {ContentRangeDirective} range
26
23
  */
27
24
 
28
- /**
29
- * @param {ServerHttp2Stream} stream
30
- * @param {string|undefined} contentType
31
- * @param {NonEmptyArray<PartialBytes>|PartialBytes} objs
32
- * @param {number|undefined} contentLength
33
- * @param {string|undefined} encoding
34
- * @param {EtagItem|undefined} etag
35
- * @param {IMFFixDateInput|string|undefined} lastModified
36
- * @param {number|undefined} age
37
- * @param {CacheControlOptions} cacheControl
38
- * @param {Metadata} meta
39
- */
40
- export function sendPartialContent(stream, contentType, objs, contentLength, encoding, etag, lastModified, age, cacheControl, meta) {
41
- return _sendPartialContent(stream, objs, {
42
- contentType,
43
- contentLength,
44
- encoding,
45
- etag,
46
- lastModified,
47
- age,
48
- cacheControl
49
- }, meta)
50
- }
51
-
52
25
  /**
53
26
  * @param {ServerHttp2Stream} stream
54
27
  * @param {NonEmptyArray<PartialBytes>|PartialBytes} objs
55
28
  * @param {Omit<SendContent, 'rangeDirective'>} content
56
29
  * @param {Metadata} meta
57
30
  */
58
- export function _sendPartialContent(stream, objs, content, meta) {
31
+ export function sendPartialContent(stream, objs, content, meta) {
59
32
  const {
60
33
  contentType,
61
34
  contentLength,
@@ -1,15 +1,18 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
3
  import {
4
+ HTTP_HEADER_ACCEPT_PATCH,
5
+ HTTP_HEADER_ACCEPT_POST,
4
6
  HTTP_HEADER_ACCEPT_QUERY,
5
7
  HTTP_METHOD_QUERY,
6
8
  HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
7
9
  PREFLIGHT_AGE_SECONDS
10
+
8
11
  } from '../../defs.js'
9
12
  import { send } from '../send-util.js'
10
13
 
11
14
  /** @import { ServerHttp2Stream } from 'node:http2' */
12
- /** @import { AcceptRangeUnits, SendInfo, Metadata } from '../../defs.js' */
15
+ /** @import { SendInfo, Metadata } from '../../defs.js' */
13
16
 
14
17
  const {
15
18
  HTTP2_HEADER_CONTENT_TYPE,
@@ -25,27 +28,12 @@ const {
25
28
 
26
29
  const { HTTP_STATUS_OK } = http2.constants
27
30
 
28
- /**
29
- * @param {ServerHttp2Stream} stream
30
- * @param {Array<string>} supportedMethods
31
- * @param {Array<string>|undefined} supportedQueryTypes
32
- * @param {AcceptRangeUnits|undefined} acceptRanges
33
- * @param {Metadata} meta
34
- */
35
- export function sendPreflight(stream, supportedMethods, supportedQueryTypes, acceptRanges, meta) {
36
- _sendPreflight(stream, {
37
- supportedMethods,
38
- supportedQueryTypes,
39
- acceptRanges
40
- }, meta)
41
- }
42
-
43
31
  /**
44
32
  * @param {ServerHttp2Stream} stream
45
33
  * @param {Pick<SendInfo, 'supportedMethods' | 'supportedQueryTypes' | 'acceptRanges'>} info
46
34
  * @param {Metadata} meta
47
35
  */
48
- export function _sendPreflight(stream, info, meta) {
36
+ export function sendPreflight(stream, info, meta) {
49
37
  const {
50
38
  supportedMethods,
51
39
  supportedQueryTypes,
@@ -56,17 +44,22 @@ export function _sendPreflight(stream, info, meta) {
56
44
  const exposedHeadersAcceptQuery = supportsQuery ? [ HTTP_HEADER_ACCEPT_QUERY ] : []
57
45
  const exposedHeaders = acceptRanges === undefined ? exposedHeadersAcceptQuery : [ HTTP2_HEADER_ACCEPT_RANGES, ...exposedHeadersAcceptQuery ]
58
46
 
47
+ // todo: if supportedMethods includes POST | PATCH
48
+ // include accept-post / accept-patch headers
49
+
59
50
  send(stream, HTTP_STATUS_OK, {
60
51
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: supportedMethods.join(','),
61
52
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: [
62
53
  HTTP2_HEADER_IF_MATCH,
63
54
  HTTP2_HEADER_IF_NONE_MATCH,
64
55
  HTTP2_HEADER_AUTHORIZATION,
65
- HTTP2_HEADER_CONTENT_TYPE,
66
- HTTP2_HEADER_RANGE,
56
+ HTTP2_HEADER_CONTENT_TYPE, // overrides cors safe restriction (for json)
57
+ HTTP2_HEADER_RANGE, // todo cors safe override not needed
67
58
  HTTP2_HEADER_IF_RANGE
68
59
  ].join(','),
69
60
  [HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS,
61
+ // [HTTP_HEADER_ACCEPT_POST]: ,
62
+ // [HTTP_HEADER_ACCEPT_PATCH]: ,
70
63
  [HTTP2_HEADER_ACCEPT_RANGES]: acceptRanges,
71
64
  [HTTP_HEADER_ACCEPT_QUERY]: supportedQueryTypes?.join(',') // todo should empty array return undef
72
65
  // Access-Control-Allow-Credentials
@@ -1,4 +1,4 @@
1
- import http2 from 'node:http2'
1
+ import http2 from 'node:http2'
2
2
 
3
3
  import { send } from '../send-util.js'
4
4
 
@@ -13,11 +13,13 @@ const { HTTP_STATUS_FOUND } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {URL} location
16
+ * @param {URL|string} location
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendFound(stream, location, meta) {
20
+ const loc = (location instanceof URL) ? location.href : location
21
+
20
22
  send(stream, HTTP_STATUS_FOUND, {
21
- [HTTP2_HEADER_LOCATION]: location.href
23
+ [HTTP2_HEADER_LOCATION]: loc
22
24
  }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
23
25
  }
@@ -6,18 +6,20 @@ import { send } from '../send-util.js'
6
6
  /** @import { Metadata } from '../../defs.js' */
7
7
 
8
8
  const {
9
- HTTP2_HEADER_LOCATION
9
+ HTTP2_HEADER_LOCATION
10
10
  } = http2.constants
11
11
 
12
12
  const { HTTP_STATUS_MOVED_PERMANENTLY } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {URL} location
16
+ * @param {URL|string} location
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendMovedPermanently(stream, location, meta) {
20
- send(stream, HTTP_STATUS_MOVED_PERMANENTLY, {
21
- [HTTP2_HEADER_LOCATION]: location.href
22
- }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
20
+ const loc = (location instanceof URL) ? location.href : location
21
+
22
+ send(stream, HTTP_STATUS_MOVED_PERMANENTLY, {
23
+ [HTTP2_HEADER_LOCATION]: loc
24
+ }, [HTTP2_HEADER_LOCATION], undefined, undefined, meta)
23
25
  }
@@ -6,8 +6,6 @@ import { send } from '../send-util.js'
6
6
 
7
7
  /** @import { ServerHttp2Stream } from 'node:http2' */
8
8
  /** @import { SendContent, Metadata } from '../../defs.js' */
9
- /** @import { EtagItem, IMFFixDateInput } from '../../headers/conditional.js' */
10
- /** @import { CacheControlOptions } from '../../headers/cache-control.js' */
11
9
 
12
10
  const {
13
11
  HTTP2_HEADER_AGE,
@@ -19,29 +17,12 @@ const {
19
17
 
20
18
  const { HTTP_STATUS_NOT_MODIFIED } = http2.constants
21
19
 
22
- /**
23
- * @param {ServerHttp2Stream} stream
24
- * @param {EtagItem|undefined} etag
25
- * @param {IMFFixDateInput|string|undefined} lastModified
26
- * @param {number|undefined} age
27
- * @param {CacheControlOptions} cacheControl
28
- * @param {Metadata} meta
29
- */
30
- export function sendNotModified(stream, etag, lastModified, age, cacheControl, meta) {
31
- _sendNotModified(stream, {
32
- etag,
33
- lastModified,
34
- age,
35
- cacheControl
36
- }, meta)
37
- }
38
-
39
20
  /**
40
21
  * @param {ServerHttp2Stream} stream
41
22
  * @param {Pick<SendContent, 'etag' | 'lastModified' | 'age' | 'cacheControl'>} content
42
23
  * @param {Metadata} meta
43
24
  */
44
- export function _sendNotModified(stream, content, meta) {
25
+ export function sendNotModified(stream, content, meta) {
45
26
  const {
46
27
  etag,
47
28
  lastModified,
@@ -54,6 +35,6 @@ export function _sendNotModified(stream, content, meta) {
54
35
  [HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
55
36
  [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
56
37
  [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
57
- [HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`
38
+ [HTTP2_HEADER_AGE]: Number.isInteger(age) ? `${age}` : undefined
58
39
  }, [ HTTP2_HEADER_AGE ], undefined, undefined, meta)
59
40
  }
@@ -13,11 +13,13 @@ const { HTTP_STATUS_PERMANENT_REDIRECT } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {URL} location
16
+ * @param {URL|string} location
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendPermanentRedirect(stream, location, meta) {
20
+ const loc = (location instanceof URL) ? location.href : location
21
+
20
22
  send(stream, HTTP_STATUS_PERMANENT_REDIRECT, {
21
- [HTTP2_HEADER_LOCATION]: location.href
23
+ [HTTP2_HEADER_LOCATION]: loc
22
24
  }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
23
25
  }
@@ -6,18 +6,20 @@ import { send } from '../send-util.js'
6
6
  /** @import { Metadata } from '../../defs.js' */
7
7
 
8
8
  const {
9
- HTTP2_HEADER_LOCATION
9
+ HTTP2_HEADER_LOCATION
10
10
  } = http2.constants
11
11
 
12
12
  const { HTTP_STATUS_SEE_OTHER } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {URL} location
16
+ * @param {URL|string} location
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendSeeOther(stream, location, meta) {
20
- send(stream, HTTP_STATUS_SEE_OTHER, {
21
- [HTTP2_HEADER_LOCATION]: location.href
22
- }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
20
+ const loc = (location instanceof URL) ? location.href : location
21
+
22
+ send(stream, HTTP_STATUS_SEE_OTHER, {
23
+ [HTTP2_HEADER_LOCATION]: loc
24
+ }, [HTTP2_HEADER_LOCATION], undefined, undefined, meta)
23
25
  }
@@ -13,11 +13,13 @@ const { HTTP_STATUS_TEMPORARY_REDIRECT } = http2.constants
13
13
 
14
14
  /**
15
15
  * @param {ServerHttp2Stream} stream
16
- * @param {URL} location
16
+ * @param {URL|string} location
17
17
  * @param {Metadata} meta
18
18
  */
19
19
  export function sendTemporaryRedirect(stream, location, meta) {
20
+ const loc = (location instanceof URL) ? location.href : location
21
+
20
22
  send(stream, HTTP_STATUS_TEMPORARY_REDIRECT, {
21
- [HTTP2_HEADER_LOCATION]: location.href
23
+ [HTTP2_HEADER_LOCATION]: loc
22
24
  }, [ HTTP2_HEADER_LOCATION ], undefined, undefined, meta)
23
25
  }
@@ -1,4 +1,4 @@
1
- import http2 from 'node:http2'
1
+ import http2 from 'node:http2'
2
2
 
3
3
  import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
4
  import { send } from '../send-util.js'
@@ -14,6 +14,6 @@ const { HTTP_STATUS_BAD_REQUEST } = http2.constants
14
14
  * @param {Metadata} meta
15
15
  */
16
16
  export function sendBadRequest(stream, message, meta) {
17
- send(stream, HTTP_STATUS_BAD_REQUEST, {
18
- }, [ ], CONTENT_TYPE_TEXT, message, meta)
17
+ send(stream, HTTP_STATUS_BAD_REQUEST, {
18
+ }, [], CONTENT_TYPE_TEXT, message, meta)
19
19
  }
@@ -12,5 +12,5 @@ const { HTTP_STATUS_PAYLOAD_TOO_LARGE } = http2.constants
12
12
  * @param {Metadata} meta
13
13
  */
14
14
  export function sendContentTooLarge(stream, meta) {
15
- send(stream, HTTP_STATUS_PAYLOAD_TOO_LARGE, {}, [], undefined, undefined, meta)
15
+ send(stream, HTTP_STATUS_PAYLOAD_TOO_LARGE, {}, [], undefined, undefined, meta)
16
16
  }
@@ -12,5 +12,5 @@ const { HTTP_STATUS_GONE } = http2.constants
12
12
  * @param {Metadata} meta
13
13
  */
14
14
  export function sendGone(stream, meta) {
15
- send(stream, HTTP_STATUS_GONE, {}, [], undefined, undefined, meta)
15
+ send(stream, HTTP_STATUS_GONE, {}, [], undefined, undefined, meta)
16
16
  }
@@ -12,5 +12,5 @@ const { HTTP_STATUS_TEAPOT } = http2.constants
12
12
  * @param {Metadata} meta
13
13
  */
14
14
  export function sendImATeapot(stream, meta) {
15
- send(stream, HTTP_STATUS_TEAPOT, {}, [], undefined, undefined, meta)
15
+ send(stream, HTTP_STATUS_TEAPOT, {}, [], undefined, undefined, meta)
16
16
  }
@@ -8,21 +8,12 @@ import { send } from '../send-util.js'
8
8
 
9
9
  const { HTTP_STATUS_NOT_ACCEPTABLE } = http2.constants
10
10
 
11
- /**
12
- * @param {ServerHttp2Stream} stream
13
- * @param {Array<string>|string} supportedTypes
14
- * @param {Metadata} meta
15
- */
16
- export function sendNotAcceptable(stream, supportedTypes, meta) {
17
- _sendNotAcceptable(stream, { supportedTypes }, meta)
18
- }
19
-
20
11
  /**
21
12
  * @param {ServerHttp2Stream} stream
22
13
  * @param {Pick<SendInfo, 'supportedTypes'>} info
23
14
  * @param {Metadata} meta
24
15
  */
25
- export function _sendNotAcceptable(stream, info, meta) {
16
+ export function sendNotAcceptable(stream, info, meta) {
26
17
  const { supportedTypes } = info
27
18
 
28
19
  const supportedTypesList = Array.isArray(supportedTypes) ? supportedTypes : [ supportedTypes ]
@@ -33,6 +24,6 @@ export function _sendNotAcceptable(stream, info, meta) {
33
24
  {},
34
25
  [],
35
26
  has ? CONTENT_TYPE_JSON : undefined,
36
- has ? JSON.stringify(supportedTypes) : undefined,
27
+ has ? JSON.stringify({ supportedTypes: supportedTypesList }) : undefined,
37
28
  meta)
38
29
  }
@@ -11,21 +11,12 @@ const {
11
11
 
12
12
  const { HTTP2_HEADER_ALLOW } = http2.constants
13
13
 
14
- /**
15
- * @param {ServerHttp2Stream} stream
16
- * @param {Array<string>} supportedMethods
17
- * @param {Metadata} meta
18
- */
19
- export function sendNotAllowed(stream, supportedMethods, meta) {
20
- _sendNotAllowed(stream, { supportedMethods }, meta)
21
- }
22
-
23
14
  /**
24
15
  * @param {ServerHttp2Stream} stream
25
16
  * @param {Pick<SendInfo, 'supportedMethods'>} info
26
17
  * @param {Metadata} meta
27
18
  */
28
- export function _sendNotAllowed(stream, info, meta) {
19
+ export function sendNotAllowed(stream, info, meta) {
29
20
  const { supportedMethods } = info
30
21
 
31
22
  send(stream, HTTP_STATUS_METHOD_NOT_ALLOWED, {
@@ -1,4 +1,4 @@
1
- import http2 from 'node:http2'
1
+ import http2 from 'node:http2'
2
2
 
3
3
  import { send } from '../send-util.js'
4
4
 
@@ -5,7 +5,6 @@ import { send } from '../send-util.js'
5
5
 
6
6
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
7
  /** @import { SendContent, Metadata } from '../../defs.js' */
8
- /** @import { EtagItem ,IMFFixDateInput } from '../../headers/conditional.js' */
9
8
 
10
9
  const {
11
10
  HTTP2_HEADER_ETAG,
@@ -14,25 +13,12 @@ const {
14
13
 
15
14
  const { HTTP_STATUS_PRECONDITION_FAILED } = http2.constants
16
15
 
17
- /**
18
- * @param {ServerHttp2Stream} stream
19
- * @param {EtagItem|undefined} etag
20
- * @param {IMFFixDateInput|string|undefined} lastModified
21
- * @param {Metadata} meta
22
- */
23
- export function sendPreconditionFailed(stream, etag, lastModified, meta) {
24
- _sendPreconditionFailed(stream, {
25
- etag,
26
- lastModified
27
- }, meta)
28
- }
29
-
30
16
  /**
31
17
  * @param {ServerHttp2Stream} stream
32
18
  * @param {Pick<SendContent, 'etag' | 'lastModified'>} content
33
19
  * @param {Metadata} meta
34
20
  */
35
- export function _sendPreconditionFailed(stream, content, meta) {
21
+ export function sendPreconditionFailed(stream, content, meta) {
36
22
  const {
37
23
  etag,
38
24
  lastModified
@@ -13,21 +13,12 @@ const {
13
13
 
14
14
  const { HTTP_STATUS_RANGE_NOT_SATISFIABLE } = http2.constants
15
15
 
16
- /**
17
- * @param {ServerHttp2Stream} stream
18
- * @param {ContentRangeDirective} rangeDirective
19
- * @param {Metadata} meta
20
- */
21
- export function sendRangeNotSatisfiable(stream, rangeDirective, meta) {
22
- _sendRangeNotSatisfiable(stream, { rangeDirective }, meta)
23
- }
24
-
25
16
  /**
26
17
  * @param {ServerHttp2Stream} stream
27
18
  * @param {Pick<SendContent, 'rangeDirective'>} content
28
19
  * @param {Metadata} meta
29
20
  */
30
- export function _sendRangeNotSatisfiable(stream, content, meta) {
21
+ export function sendRangeNotSatisfiable(stream, content, meta) {
31
22
  const { rangeDirective } = content
32
23
 
33
24
  /** @type {ContentRangeDirective} */
@@ -11,7 +11,6 @@ import { send } from '../send-util.js'
11
11
 
12
12
  /** @import { ServerHttp2Stream } from 'node:http2' */
13
13
  /** @import { SendInfo, Metadata } from '../../defs.js' */
14
- /** @import { RateLimitInfo, RateLimitPolicyInfo } from '../../headers/rate-limit.js' */
15
14
 
16
15
  const {
17
16
  HTTP2_HEADER_RETRY_AFTER
@@ -19,25 +18,12 @@ const {
19
18
 
20
19
  const { HTTP_STATUS_TOO_MANY_REQUESTS } = http2.constants
21
20
 
22
- /**
23
- * @param {ServerHttp2Stream} stream
24
- * @param {RateLimitInfo} limitInfo
25
- * @param {Array<RateLimitPolicyInfo>} policies
26
- * @param {Metadata} meta
27
- */
28
- export function sendTooManyRequests(stream, limitInfo, policies, meta) {
29
- _sendTooManyRequests(stream, {
30
- limitInfo,
31
- policies
32
- }, meta)
33
- }
34
-
35
21
  /**
36
22
  * @param {ServerHttp2Stream} stream
37
23
  * @param {Pick<SendInfo, 'limitInfo' | 'policies'>} info
38
24
  * @param {Metadata} meta
39
25
  */
40
- export function _sendTooManyRequests(stream, info, meta) {
26
+ export function sendTooManyRequests(stream, info, meta) {
41
27
  const {
42
28
  limitInfo,
43
29
  policies
@@ -10,25 +10,12 @@ const { HTTP2_METHOD_POST, HTTP2_METHOD_PATCH } = http2.constants
10
10
 
11
11
  const { HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE } = http2.constants
12
12
 
13
- /**
14
- * @param {ServerHttp2Stream} stream
15
- * @param {Array<string>|string} acceptableMediaType
16
- * @param {Array<string>|undefined} supportedQueryTypes
17
- * @param {Metadata} meta
18
- */
19
- export function sendUnsupportedMediaType(stream, acceptableMediaType, supportedQueryTypes, meta) {
20
- _sendUnsupportedMediaType(stream, {
21
- acceptableMediaType,
22
- supportedQueryTypes
23
- }, meta)
24
- }
25
-
26
13
  /**
27
14
  * @param {ServerHttp2Stream} stream
28
15
  * @param {Pick<SendInfo, 'acceptableMediaType' | 'supportedQueryTypes'>} info
29
16
  * @param {Metadata} meta
30
17
  */
31
- export function _sendUnsupportedMediaType(stream, info, meta) {
18
+ export function sendUnsupportedMediaType(stream, info, meta) {
32
19
  const {
33
20
  supportedQueryTypes,
34
21
  acceptableMediaType
@@ -1,7 +1,6 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
- import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
- import { send } from '../send-util.js'
3
+ import { send_error } from '../send-util.js'
5
4
 
6
5
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
6
  /** @import { Metadata } from '../../defs.js' */
@@ -14,5 +13,5 @@ const { HTTP_STATUS_INTERNAL_SERVER_ERROR } = http2.constants
14
13
  * @param {Metadata} meta
15
14
  */
16
15
  export function sendError(stream, message, meta) {
17
- send(stream, HTTP_STATUS_INTERNAL_SERVER_ERROR, {}, [], CONTENT_TYPE_TEXT, message, meta)
16
+ send_error(stream, HTTP_STATUS_INTERNAL_SERVER_ERROR, message, undefined, meta)
18
17
  }
@@ -1,6 +1,6 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
- import { send } from '../send-util.js'
3
+ import { send_error } from '../send-util.js'
4
4
 
5
5
  /** @import { ServerHttp2Stream } from 'node:http2' */
6
6
  /** @import { Metadata } from '../../defs.js' */
@@ -12,5 +12,5 @@ const { HTTP_STATUS_INSUFFICIENT_STORAGE } = http2.constants
12
12
  * @param {Metadata} meta
13
13
  */
14
14
  export function sendInsufficientStorage(stream, meta) {
15
- send(stream, HTTP_STATUS_INSUFFICIENT_STORAGE, {}, [], undefined, undefined, meta)
15
+ send_error(stream, HTTP_STATUS_INSUFFICIENT_STORAGE, 'Insufficient Storage', undefined, meta)
16
16
  }
@@ -1,7 +1,6 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
- import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
- import { send } from '../send-util.js'
3
+ import { send_error } from '../send-util.js'
5
4
 
6
5
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
6
  /** @import { Metadata } from '../../defs.js' */
@@ -14,5 +13,5 @@ const { HTTP_STATUS_NOT_IMPLEMENTED } = http2.constants
14
13
  * @param {Metadata} meta
15
14
  */
16
15
  export function sendNotImplemented(stream, message, meta) {
17
- send(stream, HTTP_STATUS_NOT_IMPLEMENTED, {}, [], CONTENT_TYPE_TEXT, message, meta)
16
+ send_error(stream, HTTP_STATUS_NOT_IMPLEMENTED, message, undefined, meta)
18
17
  }
@@ -1,37 +1,20 @@
1
1
  import http2 from 'node:http2'
2
2
 
3
- import { CONTENT_TYPE_TEXT } from '../../headers/content-type.js'
4
- import { send } from '../send-util.js'
3
+ import { send_error } from '../send-util.js'
5
4
 
6
5
  /** @import { ServerHttp2Stream } from 'node:http2' */
7
6
  /** @import { SendInfo, Metadata } from '../../defs.js' */
8
7
 
9
- const {
10
- HTTP2_HEADER_RETRY_AFTER
11
- } = http2.constants
12
-
13
8
  const { HTTP_STATUS_SERVICE_UNAVAILABLE } = http2.constants
14
9
 
15
- /**
16
- * @param {ServerHttp2Stream} stream
17
- * @param {string|undefined} message
18
- * @param {number|undefined} retryAfter
19
- * @param {Metadata} meta
20
- */
21
- export function sendUnavailable(stream, message, retryAfter, meta) {
22
- _sendUnavailable(stream, message, { retryAfter }, meta)
23
- }
24
-
25
10
  /**
26
11
  * @param {ServerHttp2Stream} stream
27
12
  * @param {string|undefined} message
28
13
  * @param {Pick<SendInfo, 'retryAfter'>} info
29
14
  * @param {Metadata} meta
30
15
  */
31
- export function _sendUnavailable(stream, message, info, meta) {
16
+ export function sendUnavailable(stream, message, info, meta) {
32
17
  const { retryAfter } = info
33
18
 
34
- send(stream, HTTP_STATUS_SERVICE_UNAVAILABLE, {
35
- [HTTP2_HEADER_RETRY_AFTER]: Number.isInteger(retryAfter) ? `${retryAfter}` : undefined
36
- }, [ HTTP2_HEADER_RETRY_AFTER ], CONTENT_TYPE_TEXT, message, meta)
19
+ send_error(stream, HTTP_STATUS_SERVICE_UNAVAILABLE, message, retryAfter, meta)
37
20
  }
@@ -27,7 +27,7 @@ const {
27
27
  * @returns {OutgoingHttpHeaders}
28
28
  */
29
29
  export function coreHeaders(status, contentType, exposedHeaders, meta) {
30
- const exposed = [ HTTP2_HEADER_ETAG, HTTP2_HEADER_SERVER, ...exposedHeaders ]
30
+ const exposed = [ HTTP2_HEADER_ETAG, HTTP2_HEADER_SERVER, ...exposedHeaders ] // todo include lastModified
31
31
 
32
32
  return {
33
33
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
@@ -1,7 +1,7 @@
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'
@@ -49,7 +49,7 @@ export const Response = {
49
49
  gone: sendGone,
50
50
  imATeapot: sendImATeapot,
51
51
  insufficientStorage: sendInsufficientStorage,
52
- json: sendJSON_Encoded,
52
+ json: sendJSON,
53
53
  movedPermanently: sendMovedPermanently,
54
54
  multipleChoices: sendMultipleChoices,
55
55
  noContent: sendNoContent,
@@ -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,
@@ -45,9 +45,29 @@ const {
45
45
  HTTP2_HEADER_CONTENT_LENGTH,
46
46
  HTTP2_HEADER_ACCEPT,
47
47
  HTTP2_HEADER_ACCEPT_ENCODING,
48
- HTTP2_HEADER_RANGE
48
+ HTTP2_HEADER_RANGE,
49
+ HTTP2_HEADER_RETRY_AFTER
49
50
  } = http2.constants
50
51
 
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
+
51
71
  /** @typedef { (data: InputType) => Buffer<ArrayBuffer> } EncoderFun */
52
72
 
53
73
  /** @type {Map<string, EncoderFun>} */
@@ -91,7 +111,6 @@ export function send_encoded(stream, status, contentType, body, encoding, etag,
91
111
  send_bytes(stream, status, contentType, encodedData, undefined, undefined, actualEncoding, etag, lastModified, age, cacheControl, acceptRanges, supportedQueryTypes, meta )
92
112
  }
93
113
 
94
-
95
114
  /**
96
115
  * @param {ServerHttp2Stream} stream
97
116
  * @param {number} status
@@ -127,7 +146,7 @@ export function send_bytes(stream, status, contentType, obj, range, contentLengt
127
146
  [HTTP2_HEADER_CACHE_CONTROL]: CacheControl.encode(cacheControl),
128
147
  [HTTP2_HEADER_ETAG]: Conditional.encodeEtag(etag),
129
148
  [HTTP2_HEADER_LAST_MODIFIED]: Conditional.encodeFixDate(lastModified),
130
- [HTTP2_HEADER_AGE]: age === undefined ? undefined : `${age}`,
149
+ [HTTP2_HEADER_AGE]: Number.isInteger(age) ? `${age}` : undefined,
131
150
  [HTTP2_HEADER_CONTENT_LENGTH]: contentLen,
132
151
  [HTTP2_HEADER_CONTENT_RANGE]: ContentRange.encode(range),
133
152
  [HTTP2_HEADER_ACCEPT_RANGES]: acceptRanges,
@@ -173,8 +192,8 @@ export function send(stream, status, headers, exposedHeaders, contentType, body,
173
192
  Readable.fromWeb(body, { signal }),
174
193
  stream,
175
194
  err => {
176
- if(err !== null) {
177
- console.warn('pipeline error')
195
+ if(err !== null && err !== undefined) {
196
+ console.warn('pipeline error', err)
178
197
  }
179
198
  })
180
199