braid-http 1.3.108 → 1.3.109

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.
@@ -197,6 +197,7 @@ async function braid_fetch (url, params = {}) {
197
197
  // Multiple patches get sent within a Patches: N block
198
198
  else {
199
199
  params.headers.set('Patches', params.patches.length)
200
+ params.headers.set('Content-Type', 'message/http-patches')
200
201
  let bufs = []
201
202
  let te = new TextEncoder()
202
203
  for (let patch of params.patches) {
@@ -801,6 +802,12 @@ function parse_headers (input, check_for_encoding_blocks, dont_parse_special_hea
801
802
  headers.patches = JSON.parse(headers.patches)
802
803
  }
803
804
 
805
+ // If we have Patches: N, verify that we have the right content-type set
806
+ if ('patches' in headers
807
+ && headers['content-type'] !== 'message/http-patches')
808
+ console.warn('braid-http: update with Patches: ' + headers.patches
809
+ + ' is missing Content-Type: message/http-patches. Has Content-Type: ' + JSON.stringify(headers['content-type']))
810
+
804
811
  // Update the input
805
812
  input = input.slice(end)
806
813
 
@@ -964,7 +971,7 @@ function extra_headers (headers) {
964
971
 
965
972
  // Remove the non-extra parts
966
973
  var known_headers = ['version', 'parents', 'patches',
967
- 'content-length', 'content-range', ':status']
974
+ 'content-length', 'content-range', 'content-type', ':status']
968
975
  for (var i = 0; i < known_headers.length; i++)
969
976
  delete result[known_headers[i]]
970
977
 
@@ -38,8 +38,8 @@ function write_patches (res, patches) {
38
38
 
39
39
  // An array of one patch behaves like a single patch
40
40
  if (Array.isArray(patches)) {
41
-
42
- // Add `Patches: N` header if array
41
+ // Add `Patches: N` and `Content-Type: message/http-patches' if array
42
+ res.write('Content-Type: message/http-patches\r\n')
43
43
  res.write(`Patches: ${patches.length}\r\n\r\n`)
44
44
  } else
45
45
  // Else, we'll out put a single patch
@@ -94,123 +94,128 @@ function parse_patches (req, cb) {
94
94
 
95
95
  // This function reads an update (either a set of patches, or a body) from a
96
96
  // ReadableStream and then fires a callback when finished.
97
+ //
98
+ // If req.already_buffered_body is set (Buffer, Uint8Array, or string), it
99
+ // will be used instead of reading from the request stream. This supports
100
+ // HTTP frameworks (like Fastify, Express with body-parser) that consume the
101
+ // request body before the handler runs.
97
102
  function parse_update (req, cb) {
98
- var num_patches = req.headers.patches
99
-
100
- if (!num_patches && !req.headers['content-range']) {
101
- var buffer = []
102
- req.on('data', chunk => buffer.push(chunk))
103
- req.on('end', () => {
104
- let body = new Uint8Array(Buffer.concat(buffer))
105
- let update = { body, patches: undefined }
106
- Object.defineProperty(update, 'body_text', {
107
- get: () => new TextDecoder('utf-8').decode(update.body)
108
- })
109
- cb(update)
103
+ if (req.already_buffered_body != null) {
104
+ var buf = req.already_buffered_body
105
+ if (typeof buf === 'string') buf = new TextEncoder().encode(buf)
106
+ parse_update_from_bytes(new Uint8Array(buf), req.headers, cb)
107
+ } else {
108
+ var chunks = []
109
+ req.on('data', chunk => chunks.push(chunk))
110
+ req.on('end', () =>
111
+ parse_update_from_bytes(new Uint8Array(Buffer.concat(chunks)), req.headers, cb))
112
+ }
113
+ }
114
+
115
+ // Parse a complete body buffer into an update (body snapshot or patches).
116
+ function parse_update_from_bytes (bytes, headers, cb) {
117
+ var num_patches = headers.patches
118
+
119
+ // Full body snapshot (no patches, no content-range)
120
+ if (!num_patches && !headers['content-range']) {
121
+ let update = { body: bytes, patches: undefined }
122
+ Object.defineProperty(update, 'body_text', {
123
+ get: () => new TextDecoder('utf-8').decode(update.body)
110
124
  })
125
+ return cb(update)
111
126
  }
112
127
 
113
128
  // Parse a single patch, lacking Patches: N
114
- else if (num_patches === undefined && req.headers['content-range']) {
115
- // We only support range patches right now, so there must be a
116
- // Content-Range header.
117
- assert(req.headers['content-range'], 'No patches to parse: need `Patches: N` or `Content-Range:` header in ' + JSON.stringify(req.headers))
129
+ // We only support range patches right now, so there must be a
130
+ // Content-Range header.
131
+ if (num_patches === undefined && headers['content-range']) {
132
+ assert(headers['content-range'], 'No patches to parse: need `Patches: N` or `Content-Range:` header in ' + JSON.stringify(headers))
118
133
 
119
134
  // Parse the Content-Range header
120
135
  // Content-range is of the form '<unit> <range>' e.g. 'json .index'
121
- var [unit, range] = parse_content_range(req.headers['content-range'])
122
-
123
- // The contents of the patch is in the request body
124
- var buffer = []
125
- // Read the body one chunk at a time
126
- req.on('data', chunk => buffer.push(chunk))
127
- // Then return it
128
- req.on('end', () => {
129
- let patch = {unit, range, content: new Uint8Array(Buffer.concat(buffer))}
130
- Object.defineProperty(patch, 'content_text', {
131
- get: () => new TextDecoder('utf-8').decode(patch.content)
132
- })
133
- cb({ patches: [patch], body: undefined })
136
+ var [unit, range] = parse_content_range(headers['content-range'])
137
+ let patch = {unit, range, content: bytes}
138
+ Object.defineProperty(patch, 'content_text', {
139
+ get: () => new TextDecoder('utf-8').decode(patch.content)
134
140
  })
141
+ return cb({ patches: [patch], body: undefined })
135
142
  }
136
143
 
137
144
  // Parse multiple patches within a Patches: N block
138
- else {
139
- num_patches = parseInt(num_patches)
140
- let patches = []
141
- let buffer = []
142
-
143
- // We check to send send patches each time we parse one. But if there
144
- // are zero to parse, we will never check to send them.
145
- if (num_patches === 0)
146
- return cb({ patches: [], body: undefined })
147
-
148
- req.on('data', function parse (chunk) {
149
-
150
- // Merge the latest chunk into our buffer
151
- for (let x of chunk) buffer.push(x)
152
-
153
- while (patches.length < num_patches) {
154
- // Find the start of the headers
155
- let s = 0;
156
- while (buffer[s] === 13 || buffer[s] === 10) s++
157
- if (s === buffer.length) return {result: 'waiting'}
158
-
159
- // Look for the double-newline at the end of the headers.
160
- let e = s;
161
- while (++e) {
162
- if (e > buffer.length) return {result: 'waiting'}
163
- if (buffer[e - 1] === 10 && (buffer[e - 2] === 10 || (buffer[e - 2] === 13 && buffer[e - 3] === 10))) break
164
- }
165
-
166
- // Extract the header string
167
- let headers_source = buffer.slice(s, e).map(x => String.fromCharCode(x)).join('')
168
-
169
- // Now let's parse those headers.
170
- var headers = require('parse-headers')(headers_source)
171
-
172
- // We require `content-length` to declare the length of the patch.
173
- if (!('content-length' in headers)) {
174
- // Print a nice error if it's missing
175
- console.error('No content-length in', JSON.stringify(headers),
176
- 'from', new TextDecoder().decode(new Uint8Array(buffer)), {buffer})
177
- process.exit(1)
178
- }
145
+ num_patches = parseInt(num_patches)
146
+
147
+ // We check to send patches each time we parse one. But if there
148
+ // are zero to parse, we will never check to send them.
149
+ if (num_patches === 0)
150
+ return cb({ patches: [], body: undefined })
151
+
152
+ var patches = []
153
+ var buffer = Array.from(bytes)
154
+
155
+ while (patches.length < num_patches) {
156
+ // Find the start of the headers (skip leading CR/LF)
157
+ let headers_start = 0
158
+ while (buffer[headers_start] === 13 || buffer[headers_start] === 10)
159
+ headers_start++
160
+ if (headers_start === buffer.length)
161
+ break
162
+
163
+ // Look for the double-newline at the end of the headers.
164
+ let headers_end = headers_start
165
+ while (++headers_end) {
166
+ if (headers_end > buffer.length)
167
+ break
168
+ if (buffer[headers_end - 1] === 10
169
+ && (buffer[headers_end - 2] === 10
170
+ || (buffer[headers_end - 2] === 13
171
+ && buffer[headers_end - 3] === 10)))
172
+ break
173
+ }
174
+ if (headers_end > buffer.length)
175
+ break
176
+
177
+ // Extract the header string
178
+ var headers_source = buffer.slice(headers_start, headers_end)
179
+ .map(x => String.fromCharCode(x)).join('')
180
+
181
+ // Now let's parse those headers.
182
+ var patch_headers = require('parse-headers')(headers_source)
183
+
184
+ // We require `content-length` to declare the length of the patch.
185
+ if (!('content-length' in patch_headers)) {
186
+ // Print a nice error if it's missing
187
+ console.error('No content-length in', JSON.stringify(patch_headers),
188
+ 'from', new TextDecoder().decode(new Uint8Array(buffer)),
189
+ {buffer})
190
+ process.exit(1)
191
+ }
179
192
 
180
- var body_length = parseInt(headers['content-length'])
193
+ var body_length = parseInt(patch_headers['content-length'])
181
194
 
182
- // Give up if we don't have the full patch yet.
183
- if (buffer.length - e < body_length)
184
- return
195
+ // Give up if we don't have the full patch yet.
196
+ if (buffer.length - headers_end < body_length) break
185
197
 
186
- // XX Todo: support custom patch types beyond content-range.
198
+ // XX Todo: support custom patch types beyond content-range "Range Patches".
187
199
 
188
- // Content-range is of the form '<unit> <range>' e.g. 'json .index'
189
- var [unit, range] = parse_content_range(headers['content-range'])
190
- var patch_content = new Uint8Array(buffer.slice(e, e + body_length))
200
+ // Content-range is of the form '<unit> <range>' e.g. 'json .index'
201
+ var [unit, range] = parse_content_range(patch_headers['content-range'])
202
+ var patch_content = new Uint8Array(buffer.slice(headers_end,
203
+ headers_end + body_length))
204
+
205
+ // We've got our patch!
206
+ let patch = {unit, range, content: patch_content}
207
+ Object.defineProperty(patch, 'content_text', {
208
+ get: () => new TextDecoder('utf-8').decode(patch.content)
209
+ })
210
+ patches.push(patch)
191
211
 
192
- // We've got our patch!
193
- let patch = {unit, range, content: patch_content}
194
- Object.defineProperty(patch, 'content_text', {
195
- get: () => new TextDecoder('utf-8').decode(patch.content)
196
- })
197
- patches.push(patch)
212
+ buffer = buffer.slice(headers_end + body_length)
213
+ }
198
214
 
199
- buffer = buffer.slice(e + body_length)
200
- }
215
+ if (patches.length !== num_patches)
216
+ console.error(`Got an incomplete PUT: ${patches.length}/${num_patches} patches were received`)
201
217
 
202
- // We got all the patches! Pause the stream and tell the callback!
203
- req.pause()
204
- cb({ patches, body: undefined })
205
- })
206
- req.on('end', () => {
207
- // If the stream ends before we get everything, then return what we
208
- // did receive
209
- console.error('Request stream ended!')
210
- if (patches.length !== num_patches)
211
- console.error(`Got an incomplete PUT: ${patches.length}/${num_patches} patches were received`)
212
- })
213
- }
218
+ cb({ patches, body: undefined })
214
219
  }
215
220
 
216
221
  function parse_content_range (range_string) {
@@ -321,6 +326,11 @@ function braidify (req, res, next) {
321
326
  res.setHeader('Range-Request-Allow-Methods', 'PATCH, PUT')
322
327
  res.setHeader('Range-Request-Allow-Units', 'json')
323
328
 
329
+ // All requests explicitly Vary on Version, Parents, and Subscribe
330
+ res.appendHeader('Vary', 'Version')
331
+ res.appendHeader('Vary', 'Parents')
332
+ res.appendHeader('Vary', 'Subscribe')
333
+
324
334
  // Extract braid info from headers
325
335
  var version = ('version' in req.headers) && JSON.parse('['+req.headers.version+']'),
326
336
  parents = ('parents' in req.headers) && JSON.parse('['+req.headers.parents+']'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-http",
3
- "version": "1.3.108",
3
+ "version": "1.3.109",
4
4
  "description": "An implementation of Braid-HTTP for Node.js and Browsers",
5
5
  "scripts": {
6
6
  "test": "node test/test.js",