@johntalton/http-util 1.0.0 → 2.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": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,6 +12,9 @@
12
12
  "files": [
13
13
  "src/*.js"
14
14
  ],
15
+ "repository": {
16
+ "url": "https://github.com/johntalton/http-util"
17
+ },
15
18
  "dependencies": {
16
19
  "@johntalton/sse-util": "^1.0.0"
17
20
  }
@@ -6,6 +6,7 @@ export const MIME_TYPE_XML = 'application/xml'
6
6
  export const MIME_TYPE_URL_FORM_DATA = 'application/x-www-form-urlencoded'
7
7
  export const MIME_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data'
8
8
  export const MIME_TYPE_OCTET_STREAM = 'application/octet-stream'
9
+ export const MIME_TYPE_MESSAGE_HTTP = 'message/http'
9
10
 
10
11
  export const KNOWN_CONTENT_TYPES = [
11
12
  'application', 'audio', 'image', 'message',
@@ -8,7 +8,14 @@ import {
8
8
  ENDING,
9
9
  } from '@johntalton/sse-util'
10
10
 
11
- import { CHARSET_UTF8, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT } from './content-type.js'
11
+ /** @import { IncomingHttpHeaders } from 'node:http2' */
12
+
13
+ import {
14
+ CHARSET_UTF8,
15
+ CONTENT_TYPE_JSON,
16
+ CONTENT_TYPE_TEXT,
17
+ MIME_TYPE_MESSAGE_HTTP
18
+ } from './content-type.js'
12
19
  import { ServerTiming, HTTP_HEADER_SERVER_TIMING, HTTP_HEADER_TIMING_ALLOW_ORIGIN } from './server-timing.js'
13
20
  import { HTTP_HEADER_RATE_LIMIT, HTTP_HEADER_RATE_LIMIT_POLICY, RateLimit, RateLimitPolicy } from './rate-limit.js'
14
21
 
@@ -23,7 +30,9 @@ const {
23
30
  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
24
31
  HTTP2_HEADER_SERVER,
25
32
  HTTP2_HEADER_RETRY_AFTER,
26
- HTTP2_HEADER_CACHE_CONTROL
33
+ HTTP2_HEADER_CACHE_CONTROL,
34
+ HTTP2_HEADER_ETAG,
35
+ HTTP2_HEADER_ALLOW
27
36
  } = http2.constants
28
37
 
29
38
  const {
@@ -32,7 +41,8 @@ const {
32
41
  HTTP_STATUS_UNAUTHORIZED,
33
42
  HTTP_STATUS_NO_CONTENT,
34
43
  HTTP_STATUS_INTERNAL_SERVER_ERROR,
35
- HTTP_STATUS_TOO_MANY_REQUESTS
44
+ HTTP_STATUS_TOO_MANY_REQUESTS,
45
+ HTTP_STATUS_METHOD_NOT_ALLOWED
36
46
  } = http2.constants
37
47
 
38
48
  export const HTTP_HEADER_ORIGIN = 'origin'
@@ -63,6 +73,7 @@ export const PREFLIGHT_AGE_SECONDS = '500'
63
73
  * @property {Array<TimingsInfo>} performance
64
74
  * @property {string|undefined} servername
65
75
  * @property {string|undefined} origin
76
+ * @property {string|undefined} etag
66
77
  */
67
78
 
68
79
  /**
@@ -102,14 +113,13 @@ export function sendError(stream, message, meta) {
102
113
 
103
114
  /**
104
115
  * @param {ServerHttp2Stream} stream
105
- * @param {string|undefined} allowedOrigin
106
116
  * @param {Array<string>} methods
107
117
  * @param {Metadata} meta
108
118
  */
109
- export function sendPreflight(stream, allowedOrigin, methods, meta) {
119
+ export function sendPreflight(stream, methods, meta) {
110
120
  stream.respond({
111
121
  [HTTP2_HEADER_STATUS]: HTTP_STATUS_OK,
112
- [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: allowedOrigin,
122
+ [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
113
123
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS]: methods.join(','),
114
124
  [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS]: ['Authorization', HTTP2_HEADER_CONTENT_TYPE].join(','),
115
125
  [HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE]: PREFLIGHT_AGE_SECONDS,
@@ -118,6 +128,20 @@ export function sendPreflight(stream, allowedOrigin, methods, meta) {
118
128
  stream.end()
119
129
  }
120
130
 
131
+ /**
132
+ * @param {ServerHttp2Stream} stream
133
+ * @param {Array<string>} methods
134
+ * @param {Metadata} meta
135
+ */
136
+ export function sendNowAllowed(stream, methods, meta) {
137
+ stream.respond({
138
+ [HTTP2_HEADER_STATUS]: HTTP_STATUS_METHOD_NOT_ALLOWED,
139
+ [HTTP2_HEADER_ALLOW]: methods.join(','),
140
+ [HTTP2_HEADER_SERVER]: meta.servername
141
+ })
142
+ stream.end()
143
+ }
144
+
121
145
  /**
122
146
  * @param {ServerHttp2Stream} stream
123
147
  * @param {Metadata} meta
@@ -188,10 +212,9 @@ export const ENCODER_MAP = new Map([
188
212
  * @param {ServerHttp2Stream} stream
189
213
  * @param {Object} obj
190
214
  * @param {string|undefined} encoding
191
- * @param {string|undefined} allowedOrigin
192
215
  * @param {Metadata} meta
193
216
  */
194
- export function sendJSON_Encoded(stream, obj, encoding, allowedOrigin, meta) {
217
+ export function sendJSON_Encoded(stream, obj, encoding, meta) {
195
218
  if(stream.closed) { return }
196
219
 
197
220
  const json = JSON.stringify(obj)
@@ -210,15 +233,16 @@ export function sendJSON_Encoded(stream, obj, encoding, allowedOrigin, meta) {
210
233
  )
211
234
 
212
235
  stream.respond({
213
- [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: allowedOrigin,
236
+ [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
214
237
  [HTTP2_HEADER_CONTENT_TYPE]: CONTENT_TYPE_JSON,
215
238
  [HTTP2_HEADER_CONTENT_ENCODING]: actualEncoding,
216
239
  [HTTP2_HEADER_VARY]: 'Accept, Accept-Encoding',
217
240
  [HTTP2_HEADER_CACHE_CONTROL]: 'private',
218
241
  [HTTP2_HEADER_STATUS]: HTTP_STATUS_OK,
219
242
  [HTTP2_HEADER_SERVER]: meta.servername,
220
- [HTTP_HEADER_TIMING_ALLOW_ORIGIN]: allowedOrigin,
221
- [HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance)
243
+ [HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
244
+ [HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
245
+ [HTTP2_HEADER_ETAG]: meta.etag
222
246
  })
223
247
 
224
248
  // stream.write(encodedData)
@@ -227,10 +251,48 @@ export function sendJSON_Encoded(stream, obj, encoding, allowedOrigin, meta) {
227
251
 
228
252
  /**
229
253
  * @param {ServerHttp2Stream} stream
230
- * @param {string|undefined} allowedOrigin
254
+ * @param {string} method
255
+ * @param {URL} url
256
+ * @param {IncomingHttpHeaders} headers
257
+ * @param {Metadata} meta
258
+ */
259
+ export function sendTrace(stream, method, url, headers, meta) {
260
+ const FILTER_KEYS = [ 'authorization', 'cookie' ]
261
+ const HTTP_VERSION = new Map([
262
+ [ 'h2', 'HTTP/2' ],
263
+ [ 'h2c', 'HTTP/2'],
264
+ [ 'http/1.1', 'HTTP/1.1']
265
+ ])
266
+
267
+ const version = HTTP_VERSION.get(stream.session?.alpnProtocol ?? 'h2')
268
+
269
+ stream.respond({
270
+ [HTTP2_HEADER_CONTENT_TYPE]: MIME_TYPE_MESSAGE_HTTP,
271
+ [HTTP2_HEADER_STATUS]: HTTP_STATUS_OK,
272
+ [HTTP2_HEADER_SERVER]: meta.servername,
273
+ [HTTP_HEADER_TIMING_ALLOW_ORIGIN]: meta.origin,
274
+ [HTTP_HEADER_SERVER_TIMING]: ServerTiming.encode(meta.performance),
275
+ })
276
+
277
+ const reconstructed = [
278
+ `${method} ${url.pathname}${url.search} ${version}`,
279
+ Object.entries(headers)
280
+ .filter(([ key ]) => !key.startsWith(':'))
281
+ .filter(([ key ]) => !FILTER_KEYS.includes(key))
282
+ .map(([ key, value ]) => `${key}: ${value}`)
283
+ .join('\n'),
284
+ '\n'
285
+ ]
286
+ .join('\n')
287
+
288
+ stream.end(reconstructed)
289
+ }
290
+
291
+ /**
292
+ * @param {ServerHttp2Stream} stream
231
293
  * @param {SSEOptions & Metadata} meta
232
294
  */
233
- export function sendSSE(stream, allowedOrigin, meta) {
295
+ export function sendSSE(stream, meta) {
234
296
  // stream.setTimeout(0)
235
297
  // stream.session?.setTimeout(0)
236
298
  // stream.session?.socket.setTimeout(0)
@@ -244,7 +306,7 @@ export function sendSSE(stream, allowedOrigin, meta) {
244
306
  const sendBOM = meta.bom ?? true
245
307
 
246
308
  stream.respond({
247
- [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: allowedOrigin,
309
+ [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN]: meta.origin,
248
310
  [HTTP2_HEADER_CONTENT_TYPE]: SSE_MIME,
249
311
  [HTTP2_HEADER_STATUS]: activeStream ? HTTP_STATUS_OK : HTTP_STATUS_NO_CONTENT, // SSE_INACTIVE_STATUS_CODE
250
312
  // [HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true'