braid-http 0.3.12 → 0.3.13
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 +74 -47
- package/braid-http-server.js +66 -31
- package/package.json +1 -1
package/braid-http-client.js
CHANGED
|
@@ -73,7 +73,7 @@ function braidify_http (http) {
|
|
|
73
73
|
|
|
74
74
|
// That will run each time we get new data
|
|
75
75
|
res.orig_on('data', (chunk) => {
|
|
76
|
-
parser.read(chunk
|
|
76
|
+
parser.read(chunk)
|
|
77
77
|
})
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -186,7 +186,7 @@ async function braid_fetch (url, params = {}) {
|
|
|
186
186
|
if (params.patches.length === 1) {
|
|
187
187
|
let patch = params.patches[0]
|
|
188
188
|
params.headers.set('Content-Range', `${patch.unit} ${patch.range}`)
|
|
189
|
-
params.headers.set('Content-Length', `${patch.content.length}`)
|
|
189
|
+
params.headers.set('Content-Length', `${(new TextEncoder().encode(patch.content)).length}`)
|
|
190
190
|
params.body = patch.content
|
|
191
191
|
}
|
|
192
192
|
|
|
@@ -194,7 +194,7 @@ async function braid_fetch (url, params = {}) {
|
|
|
194
194
|
else {
|
|
195
195
|
params.headers.set('Patches', params.patches.length)
|
|
196
196
|
params.body = (params.patches).map(patch => {
|
|
197
|
-
var length = `content-length: ${patch.content.length}`
|
|
197
|
+
var length = `content-length: ${(new TextEncoder().encode(patch.content)).length}`
|
|
198
198
|
var range = `content-range: ${patch.unit} ${patch.range}`
|
|
199
199
|
return `${length}\r\n${range}\r\n\r\n${patch.content}\r\n`
|
|
200
200
|
}).join('\r\n')
|
|
@@ -318,7 +318,6 @@ async function handle_fetch_stream (stream, cb) {
|
|
|
318
318
|
|
|
319
319
|
// Set up a reader
|
|
320
320
|
var reader = stream.getReader(),
|
|
321
|
-
decoder = new TextDecoder('utf-8'),
|
|
322
321
|
parser = subscription_parser(cb)
|
|
323
322
|
|
|
324
323
|
while (true) {
|
|
@@ -341,7 +340,7 @@ async function handle_fetch_stream (stream, cb) {
|
|
|
341
340
|
}
|
|
342
341
|
|
|
343
342
|
// Tell the parser to process some more stream
|
|
344
|
-
parser.read(
|
|
343
|
+
parser.read(value)
|
|
345
344
|
}
|
|
346
345
|
}
|
|
347
346
|
|
|
@@ -353,7 +352,7 @@ async function handle_fetch_stream (stream, cb) {
|
|
|
353
352
|
|
|
354
353
|
var subscription_parser = (cb) => ({
|
|
355
354
|
// A parser keeps some parse state
|
|
356
|
-
state: {input:
|
|
355
|
+
state: {input: []},
|
|
357
356
|
|
|
358
357
|
// And reports back new versions as soon as they are ready
|
|
359
358
|
cb: cb,
|
|
@@ -363,10 +362,10 @@ var subscription_parser = (cb) => ({
|
|
|
363
362
|
read (input) {
|
|
364
363
|
|
|
365
364
|
// Store the new input!
|
|
366
|
-
this.state.input
|
|
365
|
+
this.state.input.push(...input)
|
|
367
366
|
|
|
368
367
|
// Now loop through the input and parse until we hit a dead end
|
|
369
|
-
while (this.state.input.
|
|
368
|
+
while (this.state.input.length) {
|
|
370
369
|
|
|
371
370
|
// Try to parse an update
|
|
372
371
|
try {
|
|
@@ -445,29 +444,14 @@ function parse_update (state) {
|
|
|
445
444
|
return parse_body(state)
|
|
446
445
|
}
|
|
447
446
|
|
|
448
|
-
function swallow_blank_lines (input) {
|
|
449
|
-
var blank_lines = /(\r\n|\n)*/.exec(input)[0]
|
|
450
|
-
return input.substr(blank_lines.length)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
447
|
// Parsing helpers
|
|
454
448
|
function parse_headers (input) {
|
|
455
|
-
input = swallow_blank_lines(input)
|
|
456
449
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
// Look for the double-newline at the end of the headers
|
|
461
|
-
var headers_end = input.match(/(\r?\n)\r?\n/)
|
|
450
|
+
var h = extractHeader(input)
|
|
451
|
+
if (!h) return {result: 'waiting'}
|
|
462
452
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (!headers_end)
|
|
466
|
-
return {result: 'waiting'}
|
|
467
|
-
|
|
468
|
-
// We now know where the headers are to parse!
|
|
469
|
-
var headers_length = headers_end.index + headers_end[1].length,
|
|
470
|
-
headers_source = input.substring(0, headers_length)
|
|
453
|
+
var headers_source = h.header_string
|
|
454
|
+
var headers_length = headers_source.length
|
|
471
455
|
|
|
472
456
|
// Let's parse them! First define some variables:
|
|
473
457
|
var headers = {},
|
|
@@ -504,15 +488,7 @@ function parse_headers (input) {
|
|
|
504
488
|
headers.patches = JSON.parse(headers.patches)
|
|
505
489
|
|
|
506
490
|
// Update the input
|
|
507
|
-
input =
|
|
508
|
-
|
|
509
|
-
// Swallow the final blank line ending the headers
|
|
510
|
-
if (input.substr(0, 2) === '\r\n')
|
|
511
|
-
// Swallow \r\n
|
|
512
|
-
input = input.substr(2)
|
|
513
|
-
else
|
|
514
|
-
// Swallow \n
|
|
515
|
-
input = input.substr(1)
|
|
491
|
+
input = h.remaining_bytes
|
|
516
492
|
|
|
517
493
|
// And return the parsed result
|
|
518
494
|
return { result: 'success', headers, input }
|
|
@@ -538,7 +514,6 @@ function parse_body (state) {
|
|
|
538
514
|
}
|
|
539
515
|
|
|
540
516
|
// We have the whole block!
|
|
541
|
-
var consumed_length = content_length + 2
|
|
542
517
|
state.result = 'success'
|
|
543
518
|
|
|
544
519
|
// If we have a content-range, then this is a patch
|
|
@@ -553,7 +528,7 @@ function parse_body (state) {
|
|
|
553
528
|
state.patches = [{
|
|
554
529
|
unit: match.unit,
|
|
555
530
|
range: match.range,
|
|
556
|
-
content: state.input.
|
|
531
|
+
content: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length))),
|
|
557
532
|
|
|
558
533
|
// Question: Perhaps we should include headers here, like we do for
|
|
559
534
|
// the Patches: N headers below?
|
|
@@ -564,9 +539,9 @@ function parse_body (state) {
|
|
|
564
539
|
|
|
565
540
|
// Otherwise, this is a snapshot body
|
|
566
541
|
else
|
|
567
|
-
state.body = state.input.
|
|
542
|
+
state.body = (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length)))
|
|
568
543
|
|
|
569
|
-
state.input = state.input.
|
|
544
|
+
state.input = state.input.slice(content_length)
|
|
570
545
|
return state
|
|
571
546
|
}
|
|
572
547
|
|
|
@@ -581,8 +556,6 @@ function parse_body (state) {
|
|
|
581
556
|
while (!(state.patches.length === state.headers.patches
|
|
582
557
|
&& (state.patches.length === 0 || 'content' in last_patch))) {
|
|
583
558
|
|
|
584
|
-
state.input = state.input.trimStart()
|
|
585
|
-
|
|
586
559
|
// Are we starting a new patch?
|
|
587
560
|
if (!last_patch || 'content' in last_patch) {
|
|
588
561
|
last_patch = {}
|
|
@@ -614,14 +587,14 @@ function parse_body (state) {
|
|
|
614
587
|
return {
|
|
615
588
|
result: 'error',
|
|
616
589
|
message: 'no content-length in patch',
|
|
617
|
-
patch: last_patch, input: state.input
|
|
590
|
+
patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
|
|
618
591
|
}
|
|
619
592
|
|
|
620
593
|
if (!('content-range' in last_patch.headers))
|
|
621
594
|
return {
|
|
622
595
|
result: 'error',
|
|
623
596
|
message: 'no content-range in patch',
|
|
624
|
-
patch: last_patch, input: state.input
|
|
597
|
+
patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
|
|
625
598
|
}
|
|
626
599
|
|
|
627
600
|
var content_length = parseInt(last_patch.headers['content-length'])
|
|
@@ -637,17 +610,17 @@ function parse_body (state) {
|
|
|
637
610
|
return {
|
|
638
611
|
result: 'error',
|
|
639
612
|
message: 'cannot parse content-range in patch',
|
|
640
|
-
patch: last_patch, input: state.input
|
|
613
|
+
patch: last_patch, input: (new TextDecoder('utf-8')).decode(new Uint8Array(state.input))
|
|
641
614
|
}
|
|
642
615
|
|
|
643
616
|
last_patch.unit = match.unit
|
|
644
617
|
last_patch.range = match.range
|
|
645
|
-
last_patch.content = state.input.
|
|
618
|
+
last_patch.content = (new TextDecoder('utf-8')).decode(new Uint8Array(state.input.slice(0, content_length)))
|
|
646
619
|
last_patch.extra_headers = extra_headers(last_patch.headers)
|
|
647
620
|
delete last_patch.headers // We only keep the extra headers ^^
|
|
648
621
|
|
|
649
622
|
// Consume the parsed input
|
|
650
|
-
state.input = state.input.
|
|
623
|
+
state.input = state.input.slice(content_length)
|
|
651
624
|
}
|
|
652
625
|
}
|
|
653
626
|
|
|
@@ -684,6 +657,60 @@ function extra_headers (headers) {
|
|
|
684
657
|
return result
|
|
685
658
|
}
|
|
686
659
|
|
|
660
|
+
// a parsing utility function that will inspect a byte array of incoming data
|
|
661
|
+
// to see if there is header information at the beginning,
|
|
662
|
+
// namely some non-newline characters followed by two newlines
|
|
663
|
+
function extractHeader(input) {
|
|
664
|
+
// Find the start of the headers
|
|
665
|
+
let begin_headers_i = 0;
|
|
666
|
+
while (input[begin_headers_i] === 13 || input[begin_headers_i] === 10) {
|
|
667
|
+
begin_headers_i++;
|
|
668
|
+
}
|
|
669
|
+
if (begin_headers_i === input.length) {
|
|
670
|
+
return null; // Incomplete headers
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Look for the double-newline at the end of the headers
|
|
674
|
+
let end_headers_i = begin_headers_i;
|
|
675
|
+
let size_of_tail = 0;
|
|
676
|
+
while (end_headers_i < input.length) {
|
|
677
|
+
if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 10) {
|
|
678
|
+
size_of_tail = 2;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 13 && input[end_headers_i + 2] === 10) {
|
|
682
|
+
size_of_tail = 3;
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 10) {
|
|
686
|
+
size_of_tail = 3;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 13 && input[end_headers_i + 3] === 10) {
|
|
690
|
+
size_of_tail = 4;
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
end_headers_i++;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// If no double-newline is found, wait for more input
|
|
698
|
+
if (end_headers_i === input.length) {
|
|
699
|
+
return null; // Incomplete headers
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Extract the header string
|
|
703
|
+
const headerBytes = input.slice(begin_headers_i, end_headers_i);
|
|
704
|
+
const headerString = new TextDecoder('utf-8').decode(new Uint8Array(headerBytes));
|
|
705
|
+
|
|
706
|
+
// Return the remaining bytes and the header string
|
|
707
|
+
const remainingBytes = input.slice(end_headers_i + size_of_tail);
|
|
708
|
+
return {
|
|
709
|
+
remaining_bytes: remainingBytes,
|
|
710
|
+
header_string: headerString
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
687
714
|
// ****************************
|
|
688
715
|
// Exports
|
|
689
716
|
// ****************************
|
package/braid-http-server.js
CHANGED
|
@@ -54,7 +54,7 @@ function generate_patches(res, patches) {
|
|
|
54
54
|
|
|
55
55
|
let extra_headers = Object.fromEntries(Object.entries(patch).filter(([k, v]) => k != 'unit' && k != 'range' && k != 'content'))
|
|
56
56
|
|
|
57
|
-
result += `Content-Length: ${patch.content.length}\r
|
|
57
|
+
result += `Content-Length: ${(new TextEncoder().encode(patch.content)).length}\r
|
|
58
58
|
Content-Range: ${patch.unit} ${patch.range}\r
|
|
59
59
|
${Object.entries(extra_headers).map(([k, v]) => `${k}: ${v}\r\n`).join('')}\r
|
|
60
60
|
${patch.content}`
|
|
@@ -89,12 +89,12 @@ function parse_patches (req, cb) {
|
|
|
89
89
|
var [unit, range] = parse_content_range(req.headers['content-range'])
|
|
90
90
|
|
|
91
91
|
// The contents of the patch is in the request body
|
|
92
|
-
var buffer =
|
|
92
|
+
var buffer = []
|
|
93
93
|
// Read the body one chunk at a time
|
|
94
|
-
req.on('data', chunk => buffer
|
|
94
|
+
req.on('data', chunk => buffer.push(chunk))
|
|
95
95
|
// Then return it
|
|
96
96
|
req.on('end', () => {
|
|
97
|
-
patches = [{unit, range, content: buffer}]
|
|
97
|
+
patches = [{unit, range, content: Buffer.concat(buffer).toString('utf8')}]
|
|
98
98
|
cb(patches)
|
|
99
99
|
})
|
|
100
100
|
}
|
|
@@ -103,7 +103,7 @@ function parse_patches (req, cb) {
|
|
|
103
103
|
else {
|
|
104
104
|
num_patches = parseInt(num_patches)
|
|
105
105
|
let patches = []
|
|
106
|
-
let buffer =
|
|
106
|
+
let buffer = []
|
|
107
107
|
|
|
108
108
|
// We check to send send patches each time we parse one. But if there
|
|
109
109
|
// are zero to parse, we will never check to send them.
|
|
@@ -113,57 +113,39 @@ function parse_patches (req, cb) {
|
|
|
113
113
|
req.on('data', function parse (chunk) {
|
|
114
114
|
|
|
115
115
|
// Merge the latest chunk into our buffer
|
|
116
|
-
buffer
|
|
116
|
+
buffer.push(...chunk)
|
|
117
117
|
|
|
118
118
|
while (patches.length < num_patches) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
buffer = buffer.trimStart()
|
|
122
|
-
|
|
123
|
-
// First parse the patch headers. It ends with a double-newline.
|
|
124
|
-
// Let's see where that is.
|
|
125
|
-
var headers_end = buffer.match(/(\r?\n)(\r?\n)/)
|
|
126
|
-
|
|
127
|
-
// Give up if we don't have a set of headers yet.
|
|
128
|
-
if (!headers_end)
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
// Now we know where things end
|
|
132
|
-
var first_newline = headers_end[1],
|
|
133
|
-
headers_length = headers_end.index + first_newline.length,
|
|
134
|
-
blank_line = headers_end[2]
|
|
119
|
+
let h = extractHeader(buffer)
|
|
120
|
+
if (!h) return
|
|
135
121
|
|
|
136
122
|
// Now let's parse those headers.
|
|
137
|
-
var headers = require('parse-headers')(
|
|
138
|
-
buffer.substring(0, headers_length)
|
|
139
|
-
)
|
|
123
|
+
var headers = require('parse-headers')(h.header_string)
|
|
140
124
|
|
|
141
125
|
// We require `content-length` to declare the length of the patch.
|
|
142
126
|
if (!('content-length' in headers)) {
|
|
143
127
|
// Print a nice error if it's missing
|
|
144
128
|
console.error('No content-length in', JSON.stringify(headers),
|
|
145
|
-
'from', {buffer
|
|
129
|
+
'from', {buffer})
|
|
146
130
|
process.exit(1)
|
|
147
131
|
}
|
|
148
132
|
|
|
149
133
|
var body_length = parseInt(headers['content-length'])
|
|
150
134
|
|
|
151
135
|
// Give up if we don't have the full patch yet.
|
|
152
|
-
if (
|
|
136
|
+
if (h.remaining_bytes.length < body_length)
|
|
153
137
|
return
|
|
154
138
|
|
|
155
139
|
// XX Todo: support custom patch types beyond content-range.
|
|
156
140
|
|
|
157
141
|
// Content-range is of the form '<unit> <range>' e.g. 'json .index'
|
|
158
142
|
var [unit, range] = parse_content_range(headers['content-range'])
|
|
159
|
-
var patch_content =
|
|
160
|
-
buffer.substring(headers_length + blank_line.length,
|
|
161
|
-
headers_length + blank_line.length + body_length)
|
|
143
|
+
var patch_content = new TextDecoder('utf-8').decode(new Uint8Array(h.remaining_bytes.slice(0, body_length)))
|
|
162
144
|
|
|
163
145
|
// We've got our patch!
|
|
164
146
|
patches.push({unit, range, content: patch_content})
|
|
165
147
|
|
|
166
|
-
buffer =
|
|
148
|
+
buffer = h.remaining_bytes.slice(body_length)
|
|
167
149
|
}
|
|
168
150
|
|
|
169
151
|
// We got all the patches! Pause the stream and tell the callback!
|
|
@@ -375,5 +357,58 @@ function send_update(res, data, url, peer) {
|
|
|
375
357
|
}
|
|
376
358
|
}
|
|
377
359
|
|
|
360
|
+
// a parsing utility function that will inspect a byte array of incoming data
|
|
361
|
+
// to see if there is header information at the beginning,
|
|
362
|
+
// namely some non-newline characters followed by two newlines
|
|
363
|
+
function extractHeader(input) {
|
|
364
|
+
// Find the start of the headers
|
|
365
|
+
let begin_headers_i = 0;
|
|
366
|
+
while (input[begin_headers_i] === 13 || input[begin_headers_i] === 10) {
|
|
367
|
+
begin_headers_i++;
|
|
368
|
+
}
|
|
369
|
+
if (begin_headers_i === input.length) {
|
|
370
|
+
return null; // Incomplete headers
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Look for the double-newline at the end of the headers
|
|
374
|
+
let end_headers_i = begin_headers_i;
|
|
375
|
+
let size_of_tail = 0;
|
|
376
|
+
while (end_headers_i < input.length) {
|
|
377
|
+
if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 10) {
|
|
378
|
+
size_of_tail = 2;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
if (input[end_headers_i] === 10 && input[end_headers_i + 1] === 13 && input[end_headers_i + 2] === 10) {
|
|
382
|
+
size_of_tail = 3;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 10) {
|
|
386
|
+
size_of_tail = 3;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
if (input[end_headers_i] === 13 && input[end_headers_i + 1] === 10 && input[end_headers_i + 2] === 13 && input[end_headers_i + 3] === 10) {
|
|
390
|
+
size_of_tail = 4;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
end_headers_i++;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// If no double-newline is found, wait for more input
|
|
398
|
+
if (end_headers_i === input.length) {
|
|
399
|
+
return null; // Incomplete headers
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Extract the header string
|
|
403
|
+
const headerBytes = input.slice(begin_headers_i, end_headers_i);
|
|
404
|
+
const headerString = new TextDecoder('utf-8').decode(new Uint8Array(headerBytes));
|
|
405
|
+
|
|
406
|
+
// Return the remaining bytes and the header string
|
|
407
|
+
const remainingBytes = input.slice(end_headers_i + size_of_tail);
|
|
408
|
+
return {
|
|
409
|
+
remaining_bytes: remainingBytes,
|
|
410
|
+
header_string: headerString
|
|
411
|
+
};
|
|
412
|
+
}
|
|
378
413
|
|
|
379
414
|
module.exports = braidify
|