@johntalton/http-util 2.0.4 → 3.0.0

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