Web relay now stream HTTP responces from device to browser, #4172
Ylian Saint-Hilaire committed
Jun 27, 2022 at 13:02 UTC
087b33649269e855c0a2cbdf44f3be8d599e5bf5
1 file changed
+103
-5
apprelays.js
+103
-5
@@ -97,13 +97,13 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
97
// Close any old non-websocket tunnels
98
const tunnelToRemove = [];
99
for (var i in tunnels) { if ((tunnels[i].lastOperation < limit) && (tunnels[i].isWebSocket !== true)) { tunnelToRemove.push(tunnels[i]); } }
100
- for (var i in tunnelToRemove) { console.log('session-close-tunnel'); tunnelToRemove[i].close(); }
100
+ for (var i in tunnelToRemove) { tunnelToRemove[i].close(); }
101
102
// Close this session if no longer used
103
if (obj.lastOperation < limit) {
104
var count = 0;
105
for (var i in tunnels) { count++; }
106
- if (count == 0) { console.log('session-close-self'); close(); } // Time limit reached and no tunnels, clean up.
106
+ if (count == 0) { close(); } // Time limit reached and no tunnels, clean up.
107
}
108
}
109
@@ -137,7 +137,13 @@ module.exports.CreateWebRelaySession = function (parent, db, req, args, domain,
137
var count = 0;
138
for (var i in tunnels) { count += (tunnels[i].isWebSocket ? 0 : 1); }
139
// If there are none, discard all pending HTTP requests
140
- if (count == 0) { for (var i in pendingRequests) { const x = pendingRequests[i]; x[1].end(); pendingRequests = []; } }
140
+ if (count == 0) {
141
+ for (var i in pendingRequests) {
142
+ const x = pendingRequests[i];
143
+ if (x != null) { x[1].end(); }
144
+ pendingRequests = [];
145
+ }
146
+ }
147
}
148
tunnel.onconnect = function (tunnelId) { if (pendingRequests.length > 0) { const x = pendingRequests.shift(); tunnels[tunnelId].processRequest(x[0], x[1]); } }
149
tunnel.oncompleted = function (tunnelId) { if (pendingRequests.length > 0) { const x = pendingRequests.shift(); tunnels[tunnelId].processRequest(x[0], x[1]); } }
@@ -327,6 +333,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
333
}
334
}
335
336
+ /*
337
// Process incoming HTTP data
338
obj.socketAccumulator = '';
339
obj.socketParseState = 0;
@@ -352,10 +359,10 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
359
}
360
if (obj.socketParseState == 1) {
361
var csize = -1;
355
- if ((obj.socketXHeader['connection'] != undefined) && (obj.socketXHeader['connection'].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == undefined) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) {
362
+ if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == null) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) {
363
// The body ends with a close, in this case, we will only process the header
364
csize = 0;
358
- } else if (obj.socketXHeader['content-length'] != undefined) {
365
+ } else if (obj.socketXHeader['content-length'] != null) {
366
// The body length is specified by the content-length
367
csize = parseInt(obj.socketXHeader['content-length']);
368
if (obj.socketAccumulator.length < csize) return;
@@ -402,6 +409,97 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
409
// Event completion
410
if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
411
}
412
+ */
413
+
414
+ // Process incoming HTTP data
415
+ obj.socketAccumulator = '';
416
+ obj.socketParseState = 0;
417
+ obj.socketContentLengthRemaining = 0;
418
+ function processHttpData(data) {
419
+ obj.socketAccumulator += data;
420
+ while (true) {
421
+ //console.log('ACC(' + obj.socketAccumulator + '): ' + obj.socketAccumulator);
422
+ if (obj.socketParseState == 0) {
423
+ var headersize = obj.socketAccumulator.indexOf('\r\n\r\n');
424
+ if (headersize < 0) return;
425
+ //obj.Debug("Header: "+obj.socketAccumulator.substring(0, headersize)); // Display received HTTP header
426
+ obj.socketHeader = obj.socketAccumulator.substring(0, headersize).split('\r\n');
427
+ obj.socketAccumulator = obj.socketAccumulator.substring(headersize + 4);
428
+ obj.socketParseState = 1;
429
+ obj.socketXHeader = { Directive: obj.socketHeader[0].split(' ') };
430
+ for (var i in obj.socketHeader) {
431
+ if (i != 0) {
432
+ var x2 = obj.socketHeader[i].indexOf(':');
433
+ obj.socketXHeader[obj.socketHeader[i].substring(0, x2).toLowerCase()] = obj.socketHeader[i].substring(x2 + 2);
434
+ }
435
+ }
436
+ processHttpResponse(obj.socketXHeader, null, false);
437
+ }
438
+ if (obj.socketParseState == 1) {
439
+ var csize = -1;
440
+ if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'close') && ((obj.socketXHeader["transfer-encoding"] == null) || (obj.socketXHeader["transfer-encoding"].toLowerCase() != 'chunked'))) {
441
+ // The body ends with a close, in this case, we will only process the header
442
+ processHttpResponse(null, null, true);
443
+ csize = 0;
444
+ } else if (obj.socketXHeader['content-length'] != null) {
445
+ // The body length is specified by the content-length
446
+ if (obj.socketContentLengthRemaining == 0) { obj.socketContentLengthRemaining = parseInt(obj.socketXHeader['content-length']); } // Set the remaining content-length if not set
447
+ var data = obj.socketAccumulator.substring(0, obj.socketContentLengthRemaining); // Grab the available data, not passed the expected content-length
448
+ obj.socketAccumulator = obj.socketAccumulator.substring(data.length); // Remove the data from the accumulator
449
+ obj.socketContentLengthRemaining -= data.length; // Substract the obtained data from the expected size
450
+ processHttpResponse(null, data, (obj.socketContentLengthRemaining == 0)); // Send any data we have, if we are done, signal the end of the response
451
+ if (obj.socketContentLengthRemaining > 0) return; // If more data is needed, return now so we exit the while() loop.
452
+ csize = 0; // We are done
453
+ } else {
454
+ // The body is chunked
455
+ var clen = obj.socketAccumulator.indexOf('\r\n');
456
+ if (clen < 0) return; // Chunk length not found, exit now and get more data.
457
+ // Chunk length if found, lets see if we can get the data.
458
+ csize = parseInt(obj.socketAccumulator.substring(0, clen), 16);
459
+ if (obj.socketAccumulator.length < clen + 2 + csize + 2) return;
460
+ // We got a chunk with all of the data, handle the chunck now.
461
+ var data = obj.socketAccumulator.substring(clen + 2, clen + 2 + csize);
462
+ obj.socketAccumulator = obj.socketAccumulator.substring(clen + 2 + csize + 2);
463
+ processHttpResponse(null, data, (csize == 0));
464
+ }
465
+ if (csize == 0) {
466
+ //obj.Debug("xxOnSocketData DONE: (" + obj.socketData.length + "): " + obj.socketData);
467
+ obj.socketParseState = 0;
468
+ obj.socketHeader = null;
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ // This is a fully parsed HTTP response from the remote device
475
+ function processHttpResponse(header, data, done) {
476
+ if (obj.res == null) return;
477
+ parent.lastOperation = obj.lastOperation = Date.now(); // Update time of last opertion performed
478
+
479
+ // If there is a header, send it
480
+ if (header != null) {
481
+ obj.res.status(parseInt(header.Directive[1])); // Set the status
482
+ const blockHeaders = ['Directive']; // These are headers we do not forward
483
+ for (var i in header) {
484
+ if (i == 'set-cookie') { parent.webCookie = header[i]; } // Keep the cookie, don't forward it
485
+ else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i, header[i]); } // Set the headers if not blocked
486
+ }
487
+ obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future
488
+ }
489
+
490
+ // If there is data, send it
491
+ if (data != null) { obj.res.write(data, 'binary'); }
492
+
493
+ // If we are done, close the response
494
+ if (done == true) {
495
+ // Close the response
496
+ obj.res.end();
497
+ delete obj.res;
498
+
499
+ // Event completion
500
+ if (obj.oncompleted) { obj.oncompleted(obj.tunnelId); }
501
+ }
502
+ }
503
504
// Send data thru the relay tunnel. Written to use TLS if needed.
505
function send(data) { try { if (obj.tls) { obj.tls.write(data); } else { obj.wsClient.send(data); } } catch (ex) { } }