-
Notifications
You must be signed in to change notification settings - Fork 184
Cloud on Node.js
We'd like to run uProxy cloud servers on Node.js. This would eliminate the need to spin up the cloud server inside Firefox inside of a headless X server, (hopefully) allowing us run on much cheaper VMs. It's also just easier to understand and reason about and opens up a lot of possibilities for automated testing - basically, less crazy is good.
freedom-for-node supports core.rtcpeerconnection via wrtc - this has enabled uProxy to include two sample apps that run in Node.js:
Run them like this:
git clone https://github.com/uProxy/uproxy.git
cd uproxy
npm install
grunt simpleSocks
node build/src/lib/samples/simple-socks-node
Zork is what powers cloud servers - cool, we built a Docker image and now you can easily run a Node.js-based uProxy cloud server: (add -d localhost
for testing on your workstation)
git clone https://github.com/uProxy/uproxy-docker.git
cd uproxy-docker/testing/run-scripts
./run_cloud.sh -z uproxy/node-stable
TODO: add -z to our installer
We're done, right? Nope - it's slow as hell: ~25% of Firefox's throughput and is affected much more by high latency between each peer than either Chrome or Firefox - >66% slowdown when that latency rises to ~150ms.
That latency issue is doubly puzzling because uProxy made changes to Chrome in 2015 to improve throughput in the presence of high latency between peers - and wrtc is built from the exact same code.
Furthermore, we plan to test any improvements with the help of Docker - but recent versions of wrtc aren't running in Docker.
So, two priorities:
- figure out why
node-webrtc
is broken in Docker, so we can run tests - Docker-ise the distributed stress sample (see below), so we can verify fixes
- actually fix it - best theory right now is Node.js' threadpool https://github.com/uProxy/uproxy/issues/2612#issuecomment-244219541
wrtc relies on pre-built WebRTC libraries: it uses build-webrtc, as do a few other WebRTC-on-Node.js-type NPMs. build-webrtc
is a little crazy: it takes >10GB of disk space and frequently has issues (right now, it builds a busted library on Linux - this comment describes a workaround). wrtc
uploads build-webrtc
builds for several architectures to Amazon and, on npm install
first attempts to download from there - only if none is found will it proceed to build build-webrtc
.
Here's how to npm install
wrtc
against your own build of build-webrtc
:
##
## build-webrtc
##
cd ~/src
git clone https://github.com/markandrus/build-webrtc.git
cd build-webrtc
# apply this patch to jakelib/environment.js:
# https://github.com/js-platform/node-webrtc/issues/281#issuecomment-242173952
##
## node-webrtc
##
cd ~/src
git clone https://github.com/js-platform/node-webrtc.git
cd node-webrtc
export SKIP_DOWNLOAD=true
export BUILD_WEBRTC_DEPENDENCY=~/src/build-webrtc/
# apply https://github.com/js-platform/node-webrtc/pull/287:
# git fetch origin pull/287/head:fake_audio_device
# git checkout fake_audio_device
# git merge develop
# on docker you need to run this:
# echo 'unsafe-perm = true' > /root/.npmrc
npm install
node examples/ping-pong-test.js
(find more advice on custom builds at https://github.com/js-platform/node-webrtc/wiki/Building)
This pumps 32MB of data through a datachannel. Copy it into examples/
in your node-webrtc
clone.
'use strict';
const webrtc = require('..');
const MB = 32;
const pc1 = new webrtc.RTCPeerConnection();
const pc2 = new webrtc.RTCPeerConnection();
pc1.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log('pc1 candidate: ' + JSON.stringify(candidate));
pc2.addIceCandidate(candidate.candidate);
}
};
pc2.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log('pc2 candidate: ' + JSON.stringify(candidate));
pc1.addIceCandidate(candidate.candidate);
}
};
pc2.ondatachannel = (event) => {
console.log('pc2: data channel open');
const dc = event.channel;
let r = 0;
dc.onmessage = (event) => {
r++;
if (r >= 1024 * MB) {
console.log('all received!');
// TODO: signal that we're done
pc1.close();
pc2.close();
}
};
};
// NOTE: must create the datachannel before the offer
const dc = pc1.createDataChannel('test');
dc.onopen = () => {
console.log('pc1: data channel open');
for (let i = 0; i < 1024 * MB; i++) {
const ab = new ArrayBuffer(1500);
dc.send(ab);
}
console.log('all sent!');
};
pc1.createOffer((offer) => {
console.log('offer: ' + JSON.stringify(offer));
pc1.setLocalDescription(offer);
pc2.setRemoteDescription(offer);
pc2.createAnswer((answer) => {
console.log('answer: ' + JSON.stringify(answer));
pc2.setLocalDescription(answer);
pc1.setRemoteDescription(answer);
}, console.error);
}, console.error);
Again, copy these two files into examples/
in your node-webrtc
clone. Run it like so:
mkfifo fifo
node stress-send.js < fifo | /usr/bin/time -f %e node stress-receive.js > fifo
On Docker:
docker run --rm -i --name=stress-send -v ~/wrtc/node-webrtc:/work -w /work node:6 node examples/stress-send.js < fifo | /usr/bin/time -f %e docker run --rm -i --name=stress-receive -v ~/wrtc/node-webrtc:/work -w /work node:6 node examples/stress-receive.js > fifo
TODO: add latency - but tc with Docker is a real pain to do in a one-liner
'use strict';
const readline = require('readline');
const webrtc = require('.././');
const MB = 32;
const pc = new webrtc.RTCPeerConnection();
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
pc.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log(JSON.stringify(candidate));
}
};
// NOTE: must create the datachannel before the offer
const dc = pc.createDataChannel('test');
dc.onopen = () => {
console.error('sender: data channel open');
for (let i = 0; i < 1024 * MB; i++) {
const ab = new ArrayBuffer(1500);
dc.send(ab);
}
console.error('all sent!');
pc.close();
};
pc.createOffer((offer) => {
console.log(JSON.stringify(offer));
pc.setLocalDescription(offer);
}, console.error);
reader.on('line', function(line) {
try {
const yo = JSON.parse(line);
if (yo.type === 'icecandidate') {
pc.addIceCandidate(yo.candidate);
} else {
pc.setRemoteDescription(yo);
}
} catch (e) {
console.error('could not parse line: ' + line);
}
});
'use strict';
const readline = require('readline');
const webrtc = require('.././');
const MB = 32;
const pc = new webrtc.RTCPeerConnection();
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
pc.onicecandidate = (candidate) => {
if (candidate.candidate) {
console.log(JSON.stringify(candidate));
}
};
pc.ondatachannel = (event) => {
console.error('receiver: data channel open');
const dc = event.channel;
let r = 0;
dc.onmessage = (event) => {
r++;
if (r >= 1024 * MB) {
console.error('all received!');
pc.close();
process.exit();
}
};
};
reader.on('line', function(line) {
try {
const yo = JSON.parse(line);
if (yo.type === 'icecandidate') {
pc.addIceCandidate(yo.candidate);
} else {
pc.setRemoteDescription(yo);
pc.createAnswer((answer) => {
console.log(JSON.stringify(answer));
pc.setLocalDescription(answer);
}, console.error);
}
} catch (e) {
console.error('could not parse line: ' + line);
}
});