braid-http 1.3.108 → 1.3.110
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/braid-http-client.js +8 -1
- package/braid-http-server.js +110 -100
- package/package.json +3 -1
package/braid-http-client.js
CHANGED
|
@@ -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
|
|
package/braid-http-server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
assert(
|
|
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(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
193
|
+
var body_length = parseInt(patch_headers['content-length'])
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
198
|
+
// XX Todo: support custom patch types beyond content-range "Range Patches".
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
200
|
-
|
|
215
|
+
if (patches.length !== num_patches)
|
|
216
|
+
console.error(`Got an incomplete PUT: ${patches.length}/${num_patches} patches were received`)
|
|
201
217
|
|
|
202
|
-
|
|
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.
|
|
3
|
+
"version": "1.3.110",
|
|
4
4
|
"description": "An implementation of Braid-HTTP for Node.js and Browsers",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node test/test.js",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"index.mjs"
|
|
17
17
|
],
|
|
18
18
|
"main": "./index.js",
|
|
19
|
+
"types": "./index.d.ts",
|
|
19
20
|
"exports": {
|
|
21
|
+
"types": "./index.d.ts",
|
|
20
22
|
"require": "./index.js",
|
|
21
23
|
"import": "./index.mjs"
|
|
22
24
|
},
|