This commit is contained in:
Clemens Vasters 2016-09-20 20:00:49 +02:00
Коммит 157fea3a5f
41 изменённых файлов: 2164 добавлений и 0 удалений

14
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
.vscode
.vscode/*
# Build and package folders
###################
build
dist
packages
typings
.tmp
# Node
###################
node_modules

90
README.md Normal file
Просмотреть файл

@ -0,0 +1,90 @@
# Azure Relay Hybrid Connections for Node.JS
This repository contains Node modules and samples for the Hybrid Connections feature of the
Microsoft Azure Relay, a capability pilar of the Azure Service Bus platform.
Azure Relay is one of the key capability pillars of the Azure Service Bus platform. Hybrid
Connections is a secure, open-protocol evolution of the existing Relay service that has been
available in Azure since the beginning. Hybrid Connections is based on HTTP and WebSockets.
Hybrid Connections allows establishing bi-directional, binary stream communication between
two networked applications, whereby either or both of the parties can reside behind NATs or
Firewalls.
## Functional Principles
For Node, the code in the repository allows a **publicly discoverable and reachable** WebSocket
server to be hosted on any machine that has outbound access to the Internet, and
specifically to the Microsoft Azure Relay service in the chosen region, via HTTPS port 443.
The WebSocket server code will look instantly familiar as it is directly based on and integrated
with the most most popular existing WebSocket modules in the Node universe: "ws" and "websocket".
``` JS
require('ws') ==> require('hyco-ws')
require('websocket') ==> require('hyco-websocket')
```
As you create a WebSocket server using either of the alternate "hyco-ws" and "hyco-websocket"
modules from this repository, the server will not listen on a TCP port on the local network,
but rather delegate listening to a configured Hybrid Connection path the Azure Relay service
in Service Bus. That listener connection is automatically TLS/SSL protected without you having
to juggle any certificates.
The example below shows the "ws"/"hyco-ws" variant of creating a server. The API is usage is
completely "normal" except for using the "hyco-ws" module and creating an instance of the
*RelayedServer* instead of *Server*. The default underlying *Server* class remains fully available
when using "hyco-ws" instead of "ws", meaning you can host a relayed and a local WebSocket
server side-by-side from within the same application. The "websocket"/"hyco-websocket"
experience is analogous and explained in the module's README.
``` JS
var WebSocket = require('hyco-ws')
var wss = WebSocket.RelayedServer(
{
// create the
server : WebSocket.createRelayListenUri(ns, path),
token: WebSocket.createRelayToken('http://'+ns, keyrule, key)
});
wss.on('connection', function (ws) {
console.log('connection accepted');
ws.onmessage = function (event) {
console.log(JSON.parse(event.data));
};
ws.on('close', function () {
console.log('connection closed');
});
});
wss.on('error', function(err) {
console.log('error' + err);
});
```
Up to 25 WebSocket listeners can listen concurrently on the same Hybrid Connection path on the
Relay; if two or more listeners are connected, the service will automatically balance incoming
connection requests across the connected listeners. which also provides an easy failover capability.
You don't have to do anything to enable this, just have multiple listeners share the same path.
Clients connect to the server through the Relay service on the same path the listener is listening
on. The client uses the regular WebSocket protocol.
three
## Modules
This repository hosts two different modules for Node that integrate with the Hybrid Connections
feature. The modules are designed to act, as much as possible, as contract-compatible drop-in
replacements for two of the most popular existing WebSocket modules in the Node universe:
"ws" and "websocket". "Contract-compatible" means that you can take nearly any existing module or
app that uses either library and convert it to work through the Hybrid Connections relay with
minimal changes.
### Functional principles
The functional principle

2
hyco-websocket-tunnel/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
node_modules/
node_modules/*

Просмотреть файл

@ -0,0 +1,74 @@
var https = require('https');
var tunnel = require('./lib/tunnel');
var WebSocket = require('../hyco-websocket');
var argv = require('optimist').argv;
if ( argv._.length < 4) {
console.log("connect.js [namespace] [path] [key-rule] [key]")
process.exit(1);
}
var ns = argv._[0];
var path = argv._[1];
var keyrule = argv._[2];
var key = argv._[3];
var server = WebSocket.createRelaySendUri(ns, path);
var token = WebSocket.createRelayToken('http://'+ns, keyrule, key);
var credentials, tunnels = [];
var shell = global.shell = require('./lib/shell');
shell.on('command', function(cmd, args) {
if (cmd == 'help') {
shell.echo('Commands:');
shell.echo('tunnel [localhost:]port [remotehost:]port');
shell.echo('close [tunnel-id]');
shell.echo('exit');
shell.prompt();
} else
if (cmd == 'tunnel') {
tunnel.createTunnel(server, token, credentials, args[0], args[1], function(err, server) {
if (err) {
shell.echo(String(err));
} else {
var id = tunnels.push(server);
shell.echo('Tunnel created with id: ' + id);
}
shell.prompt();
});
} else
if (cmd == 'close') {
var id = parseInt(args[0], 10) - 1;
if (tunnels[id]) {
tunnels[id].close();
tunnels[id] = null;
shell.echo('Tunnel ' + (id + 1) + ' closed.');
} else {
shell.echo('Invalid tunnel id.');
}
shell.prompt();
} else
if (cmd == 'exit') {
shell.exit();
} else {
shell.echo('Invalid command. Type `help` for more information.');
shell.prompt();
}
});
shell.echo('WebSocket Tunnel Console v0.1');
shell.echo('Remote Host: ' + ns);
authenticate(function() {
shell.prompt();
});
function authenticate(callback) {
shell.prompt('Username: ', function(user) {
shell.prompt('Password: ', function(pw) {
credentials = user + ':' + pw;
callback();
}, {passwordMode: true});
});
}

Просмотреть файл

@ -0,0 +1,106 @@
var readline = require('readline');
var EventEmitter = require('events').EventEmitter;
var rl = readline.createInterface(process.stdin, process.stdout);
var shell = module.exports = new EventEmitter();
var paused, passwordMode;
//Hack to hide mask characters from output
var normalWrite = process.stdout.write;
var dummyWrite = function (data) {
var args = Array.prototype.slice.call(arguments);
if (typeof data == 'string') {
//todo: deal with control characters like backspace
args[0] = new Array(data.length + 1).join('*');
}
normalWrite.apply(process.stdout, args);
};
rl.on('SIGINT', function () {
shell.exit();
});
shell.pause = function (s) {
process.stdin.pause();
rl.pause();
paused = true;
};
shell.resume = function (s) {
process.stdin.resume();
rl.resume();
paused = false;
};
shell.echo = function (s) {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < args.length; i++) {
shell.writeLine(args[i]);
}
};
shell.setPasswordMode = function (enabled) {
passwordMode = !!enabled;
process.stdout.write = (enabled) ? dummyWrite : normalWrite;
};
shell.writeLine = function (s) {
if (paused) {
rl.output.write(s);
rl.output.write('\n');
} else {
try {
rl.output.cursorTo(0);
} catch (e) {
// nop
}
rl.output.write(s);
try {
rl.output.clearLine(1);
} catch (e) {
// nop
}
rl.output.write('\n');
try {
rl._refreshLine();
} catch (e) {
// nop
}
}
};
var lineHandler;
rl.on('line', function (line) {
shell.pause();
if (passwordMode) {
shell.setPasswordMode(false);
process.stdout.write('\n');
}
if (lineHandler) {
lineHandler(line);
} else {
var parts = line.trim().split(/\s+/);
shell.emit('command', parts[0], parts.slice(1));
}
});
shell.prompt = function (query, callback, opts) {
opts = opts || {};
lineHandler = callback;
rl.setPrompt(query || '> ');
shell.resume();
rl.prompt();
if (opts.passwordMode) {
shell.setPasswordMode(true);
}
};
shell.exit = function () {
rl.close();
process.stdin.destroy();
process.exit();
};
//start initially paused
rl.pause();

Просмотреть файл

@ -0,0 +1,124 @@
var net = require('net');
var WebSocketClient = require('../../hyco-websocket').client;
exports.createTunnel = function(wsServerAddr, token, credentials, listen, forward, callback) {
listen = parseAddr(listen, {host: '0.0.0.0', port: '8080'});
forward = parseAddr(forward, {host: '127.0.0.1', port: listen.port});
var server = net.createServer(function (tcpSock) {
var wsClient = new WebSocketClient();
var webSock, buffer = [];
tcpSock.on('data', function(data) {
if (!webSock || buffer.length) {
buffer.push(data);
} else {
webSock.send(data);
}
});
tcpSock.on('close', function() {
log('TCP socket closed');
if (webSock) {
webSock.close();
} else {
webSock = null;
}
});
tcpSock.on('error', function(err) {
log('TCP socket closed');
if (webSock) {
webSock.close();
} else {
webSock = null;
}
});
wsClient.on('connect', function(connection) {
log('WebSocket connected');
//flush buffer
while (buffer.length) {
connection.send(buffer.shift());
}
//check if tcpSock is already closed
if (webSock === null) {
connection.close();
return;
}
webSock = connection;
webSock.on('message', function (msg) {
if (msg.type == 'utf8') {
//log('Received UTF8 message');
var data = JSON.parse(msg.utf8Data);
if (data.status == 'error') {
log(data.details);
webSock.close();
}
} else {
//log('Received binary message');
tcpSock.write(msg.binaryData);
}
});
webSock.on('close', function (reasonCode, description) {
log('WebSocket closed; ' + reasonCode + '; ' + description);
tcpSock.destroy();
});
});
wsClient.on('connectFailed', function(err) {
log('WebSocket connection failed: ' + err);
tcpSock.destroy();
});
var url = wsServerAddr; //+ '&port=' + forward.port + '&host=' + forward.host;
wsClient.connect(url, null, null,
{ 'ServiceBusAuthorization' : token,
'Authorization': 'Basic ' + new Buffer(credentials).toString('base64')});
});
server.on('error', function (err) {
callback(err);
});
server.listen(listen.port, listen.host, function() {
var addr = server.address();
log('listening on ' + addr.address + ':' + addr.port);
if (callback) callback(null, server);
});
};
function parseAddr(str, addr) {
if (str) {
var parts = str.split(':');
if (parts.length == 1) {
if (parts[0] == parseInt(parts[0], 10).toString()) {
addr.port = parts[0];
} else {
addr.host = parts[0];
}
} else
if (parts.length == 2) {
addr = {host: parts[0], port: parts[1]};
}
}
return addr;
}
function log(s) {
if (global.shell) {
global.shell.echo(s);
} else {
console.log(s);
}
}

Просмотреть файл

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2016, Simon Sturmer <sstur@me.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Просмотреть файл

@ -0,0 +1,13 @@
{
"name": "node-websocket-tunnel",
"version": "1.0.0",
"repository" : {
"type" : "git",
"url" : "git://github.com/clemensv/node-websocket-tunnel.git"
},
"dependencies": {
"optimist": "^0.3.1",
"websocket": "^1.0.4",
"wordwrap": "^0.0.2"
}
}

Просмотреть файл

@ -0,0 +1,33 @@
#TCP over WebSocket
This tool allows you to tunnel TCP connections over WebSocket protocol using SSL. It consists of a server agent and
a client console. If the server agent is running on a remote machine you can use it as a middle-man to route connections
securely to any network host even through firewalls and proxies.
##Example use cases:
- You are on a restricted network that only allows traffic on ports 80 and 443
- You wish to connect securely to a service from a public access point, and cannot use SSH
Due to WebSocket connections starting out as normal HTTPS, this can be used to tunnel connections through certain
restrictive firewalls that do not even allow SSH or OpenVPN over port 443.
##Usage
On a server, run `server.js` specifying optional port and address to bind to (defaults to 0.0.0.0:443):
`node server.js 74.125.227.148:443`
On a client, run `connect.js` specifying remote host and optional port (defaults to 443):
`node connect.js 74.125.227.148`
You will be prompted for username/password which the server will verify against users.txt and then you are presented
with a command shell where you can create and destroy tunnels.
`> tunnel 3306 8.12.44.238:3306`
This will listen on port 3306 on the client (localhost) and forward connections to remote host 8.12.44.238 via the
WebSocket server. Destination port, if omitted, will default to source port.
The server uses SSL key files present in `keys/` and users listed in `users.txt` (in which passwords are md5 hashed).

Просмотреть файл

@ -0,0 +1,149 @@
var fs = require('fs');
var net = require('net');
var crypto = require('crypto');
var urlParse = require('url').parse;
var WebSocket = require('../hyco-websocket');
var WebSocketServer = require('../hyco-websocket').server;
process.chdir(__dirname);
var argv = require('optimist').argv;
var pidfile;
//kill an already running instance
if (argv.kill) {
pidfile = argv.kill;
if (!pidfile.match(/\.pid$/i))
pidfile += '.pid';
try {
var pid = fs.readFileSync(pidfile, 'utf8');
fs.unlinkSync(pidfile);
process.kill(parseInt(pid, 10));
console.log('Killed process ' + pid);
} catch (e) {
console.log('Error killing process ' + (pid || argv.kill));
}
process.exit();
}
//write pid to file so it can be killed with --kill
if (argv.pidfile) {
pidfile = argv.pidfile;
if (!pidfile.match(/\.pid$/i))
pidfile += '.pid';
fs.writeFileSync(pidfile, process.pid);
}
var users = loadUsers();
if (argv._.length < 4) {
console.log("server.js [namespace] [path] [key-rule] [key]")
process.exit(1);
}
var ns = argv._[0];
var path = argv._[1];
var keyrule = argv._[2];
var key = argv._[3];
var wsServer = new WebSocketServer(
{
server: WebSocket.createRelayListenUri(ns, path),
token: WebSocket.createRelayToken('http://' + ns, keyrule, key),
});
wsServer.on('request', function (request) {
createTunnel(request, 3389);
});
function authenticate(request) {
var encoded = request.headers['authorization'] || '', credentials;
encoded = encoded.replace(/Basic /i, '');
try {
credentials = new Buffer(encoded, 'base64').toString('utf8').split(':');
} catch (e) {
credentials = [];
}
var user = credentials[0], hash = md5(credentials[1]);
return (users[user] == hash);
}
function createTunnel(request, port, host) {
if (!authenticate(request.httpRequest)) {
request.reject(403);
return;
}
request.accept(null, null, null, function (webSock) {
console.log(webSock.remoteAddress + ' connected - Protocol Version ' + webSock.webSocketVersion);
var tcpSock = new net.Socket();
tcpSock.on('error', function (err) {
webSock.send(JSON.stringify({ status: 'error', details: 'Upstream socket error; ' + err }));
});
tcpSock.on('data', function (data) {
webSock.send(data);
});
tcpSock.on('close', function () {
webSock.close();
});
tcpSock.connect(port, host || '127.0.0.1', function () {
webSock.on('message', function (msg) {
if (msg.type === 'utf8') {
//console.log('received utf message: ' + msg.utf8Data);
} else {
//console.log('received binary message of length ' + msg.binaryData.length);
tcpSock.write(msg.binaryData);
}
});
webSock.send(JSON.stringify({ status: 'ready', details: 'Upstream socket connected' }));
});
webSock.on('close', function () {
tcpSock.destroy();
console.log(webSock.remoteAddress + ' disconnected');
});
});
}
function loadUsers() {
var lines = fs.readFileSync('./users.txt', 'utf8');
var users = {};
lines.split(/[\r\n]+/g).forEach(function (line) {
var parts = line.split(':');
if (parts.length == 2) {
users[parts[0]] = parts[1];
}
});
return users;
}
function md5(s) {
var hash = crypto.createHash('md5');
hash.update(new Buffer(String(s)));
return hash.digest('hex');
}
function parseAddr(str, addr) {
if (str) {
var parts = str.split(':');
if (parts.length == 1) {
if (parts[0] == parseInt(parts[0], 10).toString()) {
addr.port = parts[0];
} else {
addr.host = parts[0];
}
} else
if (parts.length == 2) {
addr = { host: parts[0], port: parts[1] };
}
}
return addr;
}

Просмотреть файл

@ -0,0 +1,2 @@
simon:bf787577ff656cde5b5d1f8236a75d2a
foo:37b51d194a7513e45b56f6524f2d51f2

7
hyco-websocket/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
node_modules
.DS_Store
.lock-*
build
build/*
builderror.log
npm-debug.log

88
hyco-websocket/.jshintrc Normal file
Просмотреть файл

@ -0,0 +1,88 @@
{
// JSHint Default Configuration File (as on JSHint website)
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
"forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"latedef" : "nofunc", // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : "vars", // vars: Require all defined variables be used, ignore function params
"strict" : false, // true: Requires all functions run in ES5 Strict Mode
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
"notypeof" : false, // true: Tolerate invalid typeof operator values
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"browserify" : true, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jasmine" : false, // Jasmine
"jquery" : false, // jQuery
"mocha" : false, // Mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"qunit" : false, // QUnit
"rhino" : false, // Rhino
"shelljs" : false, // ShellJS
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : { // additional predefined global variables
"WebSocket": true
}
}

Просмотреть файл

@ -0,0 +1,7 @@
.npmignore
.gitignore
example/
build/
test/
node_modules/

Просмотреть файл

@ -0,0 +1,3 @@
Changelog
=========

14
hyco-websocket/README.md Normal file
Просмотреть файл

@ -0,0 +1,14 @@
WebSocket Client & Server Implementation for Node
=================================================
Overview
--------
Documentation
=============
Changelog
---------

Просмотреть файл

@ -0,0 +1,41 @@
if ( process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('../../');
var WebSocketServer = require('../../').server;
var wss = new WebSocketServer(
{
server : WebSocket.createRelayListenUri(ns, path),
token: WebSocket.createRelayToken('http://'+ns, keyrule, key),
autoAcceptConnections : true
});
wss.on('connect',
function (ws) {
console.log('connection accepted');
ws.on('message', function (message) {
if (message.type === 'utf8') {
try {
console.log(JSON.parse(message.utf8Data));
}
catch(e) {
// do nothing if there's an error.
}
}
});
ws.on('close', function () {
console.log('connection closed');
});
});
wss.on('error', function(err) {
console.log('error' + err);
});
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"author": "",
"name": "simple",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git://github.com/clemensv/azure-hybridconnections.git"
},
"devDependencies": {},
"optionalDependencies": {}
}

Просмотреть файл

@ -0,0 +1,38 @@
if (process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('../../')
var WebSocketClient = WebSocket.client
var address = WebSocket.createRelaySendUri(ns, path);
var token = WebSocket.createRelayToken('http://'+ns, keyrule, key);
var client = new WebSocketClient({tlsOptions: { rejectUnauthorized: false }});
client.connect(address, null, null, { 'ServiceBusAuthorization' : token});
client.on('connect', function(connection){
var id = setInterval(function () {
connection.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ });
}, 100);
console.log('Started client interval. Press any key to stop.');
connection.on('close', function () {
console.log('stopping client interval');
clearInterval(id);
process.exit();
});
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', function () {
connection.close();
});
});
}

Просмотреть файл

@ -0,0 +1,14 @@
/**
* Dependencies.
*/
var gulp = require('gulp');
var jshint = require('gulp-jshint');
gulp.task('lint', function() {
return gulp.src(['gulpfile.js', 'lib/**/*.js', 'test/**/*.js'])
.pipe(jshint('.jshintrc'))
.pipe(jshint.reporter('jshint-stylish', {verbose: true}))
.pipe(jshint.reporter('fail'));
});
gulp.task('default', gulp.series('lint'));

38
hyco-websocket/index.js Normal file
Просмотреть файл

@ -0,0 +1,38 @@
'use strict';
var crypto = require('crypto')
var moment = require('moment')
var WS = module.exports = require('./lib/hybridconnectionswebsocket');
WS.createRelayToken = function createRelayToken(uri, key_name, key) {
// Token expires in one hour
var expiry = moment().add(1, 'hours').unix();
var string_to_sign = encodeURIComponent(uri) + '\n' + expiry;
var hmac = crypto.createHmac('sha256', key);
hmac.update(string_to_sign);
var signature = hmac.digest('base64');
var token = 'SharedAccessSignature sr=' + encodeURIComponent(uri) + '&sig=' + encodeURIComponent(signature) + '&se=' + expiry + '&skn=' + key_name;
return token;
};
WS.createRelaySendUri = function createRelaySendUri(serviceBusNamespace, path, token)
{
var uri = 'wss://' + serviceBusNamespace + ':443/$servicebus/hybridconnection?action=connect&path=' + path;
if ( token != null ) {
uri = uri + '&token=' + encodeURIComponent(token);
}
return uri;
}
WS.createRelayListenUri = function createRelayListenUri(serviceBusNamespace, path, token)
{
var uri = 'wss://' + serviceBusNamespace + ':443/$servicebus/hybridconnection?action=listen&path=' + path;
if ( token != null ) {
uri = uri + '&token=' + encodeURIComponent(token);
}
return uri;
}

Просмотреть файл

@ -0,0 +1,240 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var crypto = require('crypto');
var util = require('util');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var WebSocketClient = require('websocket').client;
var WebSocketConnection = require('websocket').connection;
var wait = require('wait.for');
var headerValueSplitRegExp = /,\s*/;
var headerParamSplitRegExp = /;\s*/;
var headerSanitizeRegExp = /[\r\n]/g;
var xForwardedForSeparatorRegExp = /,\s*/;
var separators = [
'(', ')', '<', '>', '@',
',', ';', ':', '\\', '\"',
'/', '[', ']', '?', '=',
'{', '}', ' ', String.fromCharCode(9)
];
var controlChars = [String.fromCharCode(127) /* DEL */];
for (var i=0; i < 31; i ++) {
/* US-ASCII Control Characters */
controlChars.push(String.fromCharCode(i));
}
var cookieNameValidateRegEx = /([\x00-\x20\x22\x28\x29\x2c\x2f\x3a-\x3f\x40\x5b-\x5e\x7b\x7d\x7f])/;
var cookieValueValidateRegEx = /[^\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]/;
var cookieValueDQuoteValidateRegEx = /^"[^"]*"$/;
var controlCharsAndSemicolonRegEx = /[\x00-\x20\x3b]/g;
var cookieSeparatorRegEx = /[;,] */;
function HybridConnectionsWebSocketRequest(resource, address, id, httpHeaders, serverConfig) {
// Superclass Constructor
EventEmitter.call(this);
this.resource = resource;
this.address = address;
this.id = id;
this.httpRequest = {
headers : {}
};
for(var keys = Object.keys(httpHeaders), l = keys.length; l; --l)
{
this.httpRequest.headers[ keys[l-1].toLowerCase() ] = httpHeaders[ keys[l-1] ];
}
this.serverConfig = serverConfig;
this._resolved = false;
}
util.inherits(HybridConnectionsWebSocketRequest, EventEmitter);
HybridConnectionsWebSocketRequest.prototype.readHandshake = function() {
var self = this;
var request = this.httpRequest;
// Decode URL
this.resourceURL = url.parse(this.resource, true);
this.host = request.headers['host'];
if (!this.host) {
throw new Error('Client must provide a Host header.');
}
this.key = request.headers['sec-websocket-key'];
if (!this.key) {
throw new Error('Client must provide a value for Sec-WebSocket-Key.');
}
this.webSocketVersion = parseInt(request.headers['sec-websocket-version'], 10);
if (!this.webSocketVersion || isNaN(this.webSocketVersion)) {
throw new Error('Client must provide a value for Sec-WebSocket-Version.');
}
// Protocol is optional.
var protocolString = request.headers['sec-websocket-protocol'];
this.protocolFullCaseMap = {};
this.requestedProtocols = [];
if (protocolString) {
var requestedProtocolsFullCase = protocolString.split(headerValueSplitRegExp);
requestedProtocolsFullCase.forEach(function(protocol) {
var lcProtocol = protocol.toLocaleLowerCase();
self.requestedProtocols.push(lcProtocol);
self.protocolFullCaseMap[lcProtocol] = protocol;
});
}
if (!this.serverConfig.ignoreXForwardedFor &&
request.headers['x-forwarded-for']) {
var immediatePeerIP = this.remoteAddress;
this.remoteAddresses = request.headers['x-forwarded-for']
.split(xForwardedForSeparatorRegExp);
this.remoteAddresses.push(immediatePeerIP);
this.remoteAddress = this.remoteAddresses[0];
}
// Extensions are optional.
var extensionsString = request.headers['sec-websocket-extensions'];
this.requestedExtensions = this.parseExtensions(extensionsString);
// Cookies are optional
var cookieString = request.headers['cookie'];
this.cookies = this.parseCookies(cookieString);
};
HybridConnectionsWebSocketRequest.prototype.parseExtensions = function(extensionsString) {
if (!extensionsString || extensionsString.length === 0) {
return [];
}
var extensions = extensionsString.toLocaleLowerCase().split(headerValueSplitRegExp);
extensions.forEach(function(extension, index, array) {
var params = extension.split(headerParamSplitRegExp);
var extensionName = params[0];
var extensionParams = params.slice(1);
extensionParams.forEach(function(rawParam, index, array) {
var arr = rawParam.split('=');
var obj = {
name: arr[0],
value: arr[1]
};
array.splice(index, 1, obj);
});
var obj = {
name: extensionName,
params: extensionParams
};
array.splice(index, 1, obj);
});
return extensions;
};
// This function adapted from node-cookie
// https://github.com/shtylman/node-cookie
HybridConnectionsWebSocketRequest.prototype.parseCookies = function(str) {
// Sanity Check
if (!str || typeof(str) !== 'string') {
return [];
}
var cookies = [];
var pairs = str.split(cookieSeparatorRegEx);
pairs.forEach(function(pair) {
var eq_idx = pair.indexOf('=');
if (eq_idx === -1) {
cookies.push({
name: pair,
value: null
});
return;
}
var key = pair.substr(0, eq_idx).trim();
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' === val[0]) {
val = val.slice(1, -1);
}
cookies.push({
name: key,
value: decodeURIComponent(val)
});
});
return cookies;
};
HybridConnectionsWebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies, callback) {
var req = this;
var client = new WebSocketClient({tlsOptions: { rejectUnauthorized: false }});
client.connect(this.address, null, null);
client.on('connect', function(connection) {
req.emit('requestAccepted', connection);
callback(connection);
});
};
HybridConnectionsWebSocketRequest.prototype.reject = function(status, reason, extraHeaders, callback) {
var req = this;
var client = new WebSocketClient({tlsOptions: { rejectUnauthorized: false }});
var rejectUri = this.address + "&statusCode=" + status + "&statusDescription" + encodeURIComponent(reason);
client.connect(rejectUri, null, null);
client.on('error', function(event){
this.emit('requestRejected', this);
callback(event);
});
};
HybridConnectionsWebSocketRequest.prototype._handleSocketCloseBeforeAccept = function() {
this._socketIsClosing = true;
this._removeSocketCloseListeners();
};
HybridConnectionsWebSocketRequest.prototype._removeSocketCloseListeners = function() {
this.socket.removeListener('end', this._socketCloseHandler);
this.socket.removeListener('close', this._socketCloseHandler);
};
HybridConnectionsWebSocketRequest.prototype._verifyResolution = function() {
if (this._resolved) {
throw new Error('HybridConnectionsWebSocketRequest may only be accepted or rejected one time.');
}
};
function cleanupFailedConnection(connection) {
// Since we have to return a connection object even if the socket is
// already dead in order not to break the API, we schedule a 'close'
// event on the connection object to occur immediately.
process.nextTick(function() {
// WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006
// Third param: Skip sending the close frame to a dead socket
connection.drop(1006, 'TCP connection lost before handshake completed.', true);
});
}
module.exports = HybridConnectionsWebSocketRequest;

Просмотреть файл

@ -0,0 +1,269 @@
/************************************************************************
* Copyright 2010-2015 Brian McKelvey.
* Derivative Copyright Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
var extend = require('./utils').extend;
var utils = require('./utils');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var WebSocketClient = require('websocket').client;
var WebSocketRequest = require('./HybridConnectionsWebSocketRequest');
var isDefinedAndNonNull = function (options, key) {
return typeof options[key] != 'undefined' && options[key] !== null;
};
var HybridConnectionsWebSocketServer = function HybridConnectionsWebSocketServer(config) {
// Superclass Constructor
EventEmitter.call(this);
this._handlers = {
requestAccepted: this.handleRequestAccepted.bind(this),
requestResolved: this.handleRequestResolved.bind(this)
};
this.pendingRequests = [];
this.connections = [];
if (config) {
this.open(config);
}
};
util.inherits(HybridConnectionsWebSocketServer, EventEmitter);
HybridConnectionsWebSocketServer.prototype.open = function(config) {
this.config = {
// hybrid connection endpoint address
server: null,
// listen token
token: null,
// identifier
id: null,
// If true, the server will automatically send a ping to all
// connections every 'keepaliveInterval' milliseconds. The timer is
// reset on any received data from the client.
keepalive: true,
// The interval to send keepalive pings to connected clients if the
// connection is idle. Any received data will reset the counter.
keepaliveInterval: 20000,
// If true, the server will consider any connection that has not
// received any data within the amount of time specified by
// 'keepaliveGracePeriod' after a keepalive ping has been sent to
// be dead, and will drop the connection.
// Ignored if keepalive is false.
dropConnectionOnKeepaliveTimeout: true,
// The amount of time to wait after sending a keepalive ping before
// closing the connection if the connected peer does not respond.
// Ignored if keepalive is false.
keepaliveGracePeriod: 10000,
// Whether to use native TCP keep-alive instead of WebSockets ping
// and pong packets. Native TCP keep-alive sends smaller packets
// on the wire and so uses bandwidth more efficiently. This may
// be more important when talking to mobile devices.
// If this value is set to true, then these values will be ignored:
// keepaliveGracePeriod
// dropConnectionOnKeepaliveTimeout
useNativeKeepalive: false,
// If true, fragmented messages will be automatically assembled
// and the full message will be emitted via a 'message' event.
// If false, each frame will be emitted via a 'frame' event and
// the application will be responsible for aggregating multiple
// fragmented frames. Single-frame messages will emit a 'message'
// event in addition to the 'frame' event.
// Most users will want to leave this set to 'true'
assembleFragments: true,
// If this is true, websocket connections will be accepted
// regardless of the path and protocol specified by the client.
// The protocol accepted will be the first that was requested
// by the client. Clients from any origin will be accepted.
// This should only be used in the simplest of cases. You should
// probably leave this set to 'false' and inspect the request
// object to make sure it's acceptable before accepting it.
autoAcceptConnections: false,
// Whether or not the X-Forwarded-For header should be respected.
// It's important to set this to 'true' when accepting connections
// from untrusted connections, as a malicious client could spoof its
// IP address by simply setting this header. It's meant to be added
// by a trusted proxy or other intermediary within your own
// infrastructure.
// See: http://en.wikipedia.org/wiki/X-Forwarded-For
ignoreXForwardedFor: false,
// The Nagle Algorithm makes more efficient use of network resources
// by introducing a small delay before sending small packets so that
// multiple messages can be batched together before going onto the
// wire. This however comes at the cost of latency, so the default
// is to disable it. If you don't need low latency and are streaming
// lots of small messages, you can change this to 'false'
disableNagleAlgorithm: true,
// The number of milliseconds to wait after sending a close frame
// for an acknowledgement to come back before giving up and just
// closing the socket.
closeTimeout: 5000
};
extend(this.config, config);
if (this.config.server) {
// connect
this.listenUri = config.server;
if (isDefinedAndNonNull(config, 'id')) {
this.listenUri = listenUri + '&id=' + config.id;
}
connectControlChannel(this);
}
else {
throw new Error('You must specify an hybrid connections server address on which to open the WebSocket server.');
}
};
HybridConnectionsWebSocketServer.prototype.closeAllConnections = function() {
this.connections.forEach(function(connection) {
connection.close();
});
};
HybridConnectionsWebSocketServer.prototype.broadcast = function(data) {
if (Buffer.isBuffer(data)) {
this.broadcastBytes(data);
}
else if (typeof(data.toString) === 'function') {
this.broadcastUTF(data);
}
};
HybridConnectionsWebSocketServer.prototype.broadcastUTF = function(utfData) {
this.connections.forEach(function(connection) {
connection.sendUTF(utfData);
});
};
HybridConnectionsWebSocketServer.prototype.broadcastBytes = function(binaryData) {
this.connections.forEach(function(connection) {
connection.sendBytes(binaryData);
});
};
HybridConnectionsWebSocketServer.prototype.shutDown = function() {
this.closeAllConnections();
};
HybridConnectionsWebSocketServer.prototype.handleRequestAccepted = function(connection) {
var self = this;
connection.once('close', function(closeReason, description) {
self.handleConnectionClose(connection, closeReason, description);
});
this.connections.push(connection);
this.emit('connect', connection);
};
HybridConnectionsWebSocketServer.prototype.handleRequestResolved = function(request) {
var index = this.pendingRequests.indexOf(request);
if (index !== -1) { this.pendingRequests.splice(index, 1); }
};
HybridConnectionsWebSocketServer.prototype.handleConnectionClose = function(closeReason, description) {
console.log(description);
}
function connectControlChannel(server) {
/* create the control connection */
var headers = null;
if ( server.config.token != null) {
headers = { 'ServiceBusAuthorization' : server.config.token};
};
var client = new WebSocketClient();
client.connect(server.listenUri, null, null, headers);
client.on('connect', function(connection) {
server.controlChannel = connection;
server.controlChannel.on('error', function (event) {
server.emit('error', event);
if (!closeRequested) {
connectControlChannel(server);
}
});
server.controlChannel.on('close', function(event) {
// reconnect
if (!closeRequested)
{
connectControlChannel(server);
} else {
server.emit('close', server);
}
});
server.controlChannel.on('message', function (message) {
if (message.type === 'utf8') {
try {
handleControl(server, JSON.parse(message.utf8Data));
}
catch(e) {
// do nothing if there's an error.
}
}
});
});
client.on('connectFailed', function(event){});
}
function handleControl(server,message) {
if ( isDefinedAndNonNull(message, 'accept') ) {
var address = message.accept.address;
var wsRequest = new WebSocketRequest(server.listenUri, message.accept.address, message.accept.id, message.accept.connectHeaders, server.config);
try {
wsRequest.readHandshake();
}
catch(e) {
wsRequest.reject(
e.httpCode ? e.httpCode : 400,
e.message,
e.headers
);
debug('Invalid handshake: %s', e.message);
return;
}
server.pendingRequests.push(wsRequest);
wsRequest.once('requestAccepted', server._handlers.requestAccepted);
wsRequest.once('requestResolved', server._handlers.requestResolved);
if (!server.config.autoAcceptConnections && utils.eventEmitterListenerCount(server, 'request') > 0) {
server.emit('request', wsRequest);
}
else if (server.config.autoAcceptConnections) {
wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
}
else {
wsRequest.reject(404, 'No handler is configured to accept the connection.');
}
}
}
module.exports = HybridConnectionsWebSocketServer;

Просмотреть файл

@ -0,0 +1,13 @@
var websocket = require('websocket')
module.exports = {
'server' : require('./HybridConnectionsWebSocketServer'),
'client' : websocket.client,
'router' : websocket.router,
'frame' : websocket.frame,
'request' : require('./HybridConnectionsWebSocketRequest'),
'connection' : websocket.connection,
'w3cwebsocket' : websocket.w3cwebsocket,
'deprecation' : websocket.deprecation,
'version' : require('./version')
};

Просмотреть файл

@ -0,0 +1,60 @@
var noop = exports.noop = function(){};
exports.extend = function extend(dest, source) {
for (var prop in source) {
dest[prop] = source[prop];
}
};
exports.eventEmitterListenerCount =
require('events').EventEmitter.listenerCount ||
function(emitter, type) { return emitter.listeners(type).length; };
exports.BufferingLogger = function createBufferingLogger(identifier, uniqueID) {
var logFunction = require('debug')(identifier);
if (logFunction.enabled) {
var logger = new BufferingLogger(identifier, uniqueID, logFunction);
var debug = logger.log.bind(logger);
debug.printOutput = logger.printOutput.bind(logger);
debug.enabled = logFunction.enabled;
return debug;
}
logFunction.printOutput = noop;
return logFunction;
};
function BufferingLogger(identifier, uniqueID, logFunction) {
this.logFunction = logFunction;
this.identifier = identifier;
this.uniqueID = uniqueID;
this.buffer = [];
}
BufferingLogger.prototype.log = function() {
this.buffer.push([ new Date(), Array.prototype.slice.call(arguments) ]);
return this;
};
BufferingLogger.prototype.clear = function() {
this.buffer = [];
return this;
};
BufferingLogger.prototype.printOutput = function(logFunction) {
if (!logFunction) { logFunction = this.logFunction; }
var uniqueID = this.uniqueID;
this.buffer.forEach(function(entry) {
var date = entry[0].toLocaleString();
var args = entry[1].slice();
var formatString = args[0];
if (formatString !== (void 0) && formatString !== null) {
formatString = '%s - %s - ' + formatString.toString();
args.splice(0, 1, formatString, date, uniqueID);
logFunction.apply(global, args);
}
});
};

Просмотреть файл

@ -0,0 +1 @@
module.exports = require('../package.json').version;

Просмотреть файл

@ -0,0 +1,45 @@
{
"name": "hyco-websocket",
"description": "",
"keywords": [
"websocket",
"websockets",
"socket",
"networking",
"comet",
"push",
"RFC-6455",
"realtime",
"server",
"client"
],
"author": "",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/clemensv/azure-relay-hybridconnections.git"
},
"homepage": "https://azure.com",
"engines": {
"node": ">=0.8.0"
},
"dependencies": {
"wait.for" : "latest",
"websocket": "latest",
"crypto": "latest",
"moment": "latest"
},
"devDependencies": {
},
"config": {
"verbose": false
},
"scripts": {
},
"main": "index",
"directories": {
"lib": "./lib"
},
"license": "Apache-2.0"
}

8
hyco-ws/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,8 @@
npm-debug.log
node_modules
.*.swp
.lock-*
build
coverage
builderror.log

7
hyco-ws/.npmignore Normal file
Просмотреть файл

@ -0,0 +1,7 @@
.npmignore
.gitignore
example/
build/
test/
node_modules/

Просмотреть файл

@ -0,0 +1,19 @@
{
"author": "",
"name": "serverstats",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git://github.com/clemensv/azure-hybridconnections.git"
},
"engines": {
"node": ">0.4.0"
},
"dependencies": {
"hyco-ws": "latest",
"express": "4.x",
"mustache-express" : "latest"
},
"devDependencies": {},
"optionalDependencies": {}
}

Просмотреть файл

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Tahoma, Geneva, sans-serif;
}
div {
display: inline;
}
</style>
<script>
function updateStats(memuse) {
document.getElementById('rss').innerHTML = memuse.rss;
document.getElementById('heapTotal').innerHTML = memuse.heapTotal;
document.getElementById('heapUsed').innerHTML = memuse.heapUsed;
}
var host = window.document.location.host.replace(/:.*/, '');
var ws = new WebSocket('wss://{{ns}}:443/$servicebus/hybridconnection?action=connect&path={{path}}&token={{token}}');
ws.onmessage = function (event) {
updateStats(JSON.parse(event.data));
};
</script>
</head>
<body>
<strong>Server Stats</strong><br>
RSS: <div id='rss'></div><br>
Heap total: <div id='heapTotal'></div><br>
Heap used: <div id='heapUsed'></div><br>
</body>
</html>

Просмотреть файл

@ -0,0 +1,37 @@
if ( process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var WebSocket = require('hyco-ws')
, http = require('http')
, express = require('express')
, mustache = require('mustache-express')
, app = express();
var _ns = process.argv[2];
var _path = process.argv[3];
var _keyrule = process.argv[4];
var _key = process.argv[5];
app.engine('html', mustache());
app.set('view engine', 'mustache');
app.set('views', __dirname + '/public');
app.get('/', function (req, res) {
res.render('index.html', { ns : _ns, path : _path, token : encodeURIComponent(WebSocket.createRelayToken('http://'+_ns, _keyrule, _key)) });
});
app.listen(8080);
WebSocket.createRelayedServer(
{
server : WebSocket.createRelayListenUri(_ns, _path),
token: WebSocket.createRelayToken('http://'+_ns, _keyrule, _key)
}, function(ws) {
var id = setInterval(function() {
ws.send(JSON.stringify(process.memoryUsage()), function() { /* ignore errors */ });
}, 100);
console.log('started client interval');
ws.on('close', function() {
console.log('stopping client interval');
clearInterval(id);
});
});
}

Просмотреть файл

@ -0,0 +1,31 @@
if ( process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('hyco-ws')
var wss = WebSocket.createRelayedServer(
{
server : WebSocket.createRelayListenUri(ns, path),
token: WebSocket.createRelayToken('http://'+ns, keyrule, key)
},
function (ws) {
console.log('connection accepted');
ws.onmessage = function (event) {
console.log(JSON.parse(event.data));
};
ws.on('close', function () {
console.log('connection closed');
});
});
wss.on('error', function(err) {
console.log('error' + err);
});
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"author": "",
"name": "simple-externalpkg",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git://github.com/clemensv/azure-hybridconnections.git"
},
"dependencies": {
"hyco-ws": "latest"
},
"devDependencies": {},
"optionalDependencies": {}
}

Просмотреть файл

@ -0,0 +1,34 @@
if (process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('hyco-ws')
WebSocket.relayedConnect(
WebSocket.createRelaySendUri(ns, path),
WebSocket.createRelayToken('http://'+ns, keyrule, key),
function (wss) {
var id = setInterval(function () {
wss.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ });
}, 100);
console.log('Started client interval. Press any key to stop.');
wss.on('close', function () {
console.log('stopping client interval');
clearInterval(id);
process.exit();
});
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', function () {
wss.close();
});
}
);
}

Просмотреть файл

@ -0,0 +1,31 @@
if ( process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('../../')
var wss = WebSocket.createRelayedServer(
{
server : WebSocket.createRelayListenUri(ns, path),
token: WebSocket.createRelayToken('http://'+ns, keyrule, key)
},
function (ws) {
console.log('connection accepted');
ws.onmessage = function (event) {
console.log(JSON.parse(event.data));
};
ws.on('close', function () {
console.log('connection closed');
});
});
wss.on('error', function(err) {
console.log('error' + err);
});
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"author": "",
"name": "simple",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "git://github.com/clemensv/azure-hybridconnections.git"
},
"devDependencies": {},
"optionalDependencies": {}
}

Просмотреть файл

@ -0,0 +1,34 @@
if (process.argv.length < 6) {
console.log("listener.js [namespace] [path] [key-rule] [key]");
} else {
var ns = process.argv[2];
var path = process.argv[3];
var keyrule = process.argv[4];
var key = process.argv[5];
var WebSocket = require('../../')
WebSocket.relayedConnect(
WebSocket.createRelaySendUri(ns, path),
WebSocket.createRelayToken('http://'+ns, keyrule, key),
function (wss) {
var id = setInterval(function () {
wss.send(JSON.stringify(process.memoryUsage()), function () { /* ignore errors */ });
}, 100);
console.log('Started client interval. Press any key to stop.');
wss.on('close', function () {
console.log('stopping client interval');
clearInterval(id);
process.exit();
});
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.on('data', function () {
wss.close();
});
}
);
}

88
hyco-ws/index.js Normal file
Просмотреть файл

@ -0,0 +1,88 @@
'use strict';
var crypto = require('crypto')
var moment = require('moment')
/*!
* Adapted from
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*
*/
var WS = module.exports = require('ws');
WS.RelayedServer = require('./lib/HybridConnectionWebSocketServer');
/**
* Create a new WebSocket server.
*
* @param {Object} options Server options
* @param {Function} fn Optional connection listener.
* @returns {WS.Server}
* @api public
*/
WS.createRelayedServer = function createRelayedServer(options, fn) {
var server = new WS.RelayedServer(options);
if (typeof fn === 'function') {
server.on('connection', fn);
}
return server;
};
/**
* Create a new WebSocket connection.
*
* @param {String} address The URL/address we need to connect to.
* @param {Function} fn Open listener.
* @returns {WS}
* @api public
*/
WS.relayedConnect = WS.createRelayedConnection = function relayedConnect(address, token, fn) {
var opt = null;
if ( token != null) {
opt = { headers : { 'ServiceBusAuthorization' : token}};
};
var client = new WS(address, null, opt);
if (typeof fn === 'function') {
client.on('open', function() { fn(client) });
}
return client;
};
WS.createRelayToken = function createRelayToken(uri, key_name, key) {
// Token expires in one hour
var expiry = moment().add(1, 'hours').unix();
var string_to_sign = encodeURIComponent(uri) + '\n' + expiry;
var hmac = crypto.createHmac('sha256', key);
hmac.update(string_to_sign);
var signature = hmac.digest('base64');
var token = 'SharedAccessSignature sr=' + encodeURIComponent(uri) + '&sig=' + encodeURIComponent(signature) + '&se=' + expiry + '&skn=' + key_name;
return token;
};
WS.createRelaySendUri = function createRelaySendUri(serviceBusNamespace, path, token)
{
var uri = 'wss://' + serviceBusNamespace + ':443/$servicebus/hybridconnection?action=connect&path=' + path;
if ( token != null ) {
uri = uri + '&token=' + encodeURIComponent(token);
}
return uri;
}
WS.createRelayListenUri = function createRelayListenUri(serviceBusNamespace, path, token)
{
var uri = 'wss://' + serviceBusNamespace + ':443/$servicebus/hybridconnection?action=listen&path=' + path;
if ( token != null ) {
uri = uri + '&token=' + encodeURIComponent(token);
}
return uri;
}

Просмотреть файл

@ -0,0 +1,267 @@
'use strict';
const util = require('util');
const EventEmitter = require('events');
const http = require('http');
const crypto = require('crypto');
const WebSocket = require('ws');
const url = require('url');
// slightly awful workaround to pull submodules
var wsc = require.cache[require.resolve('ws')]
const Extensions = wsc.require('./lib/Extensions');
const PerMessageDeflate = wsc.require('./lib/PerMessageDeflate');
var isDefinedAndNonNull = function (options, key) {
return typeof options[key] != 'undefined' && options[key] !== null;
};
/**
* WebSocket Server implementation
*/
function HybridConnectionsWebSocketServer(options, callback) {
if (this instanceof HybridConnectionsWebSocketServer === false) {
return new HybridConnectionsWebSocketServer(options, callback);
}
EventEmitter.call(this);
options = Object.assign({
server: null,
token: null,
id: null,
verifyClient: null,
handleProtocols: null,
disableHixie: false,
clientTracking: true,
perMessageDeflate: true,
maxPayload: 100 * 1024 * 1024,
backlog: null // use default (511 as implemented in net.js)
}, options);
if (!isDefinedAndNonNull(options, 'server')) {
throw new TypeError('\'server\' must be provided');
}
var self = this;
this.listenUri = options.server;
if (isDefinedAndNonNull(options, 'id')) {
this.listenUri = listenUri + '&id=' + options.id;
}
this.closeRequested = false;
this.options = options;
this.path = options.path;
this.clients = [];
connectControlChannel(this);
}
/**
* Inherits from EventEmitter.
*/
util.inherits(HybridConnectionsWebSocketServer, EventEmitter);
/**
* Immediately shuts down the connection.
*
* @api public
*/
HybridConnectionsWebSocketServer.prototype.close = function (callback) {
this.closeRequested = true;
// terminate all associated clients
var error = null;
try {
for (var i = 0, l = this.clients.length; i < l; ++i) {
this.clients[i].close();
}
this.controlChannel.close();
}
catch (e) {
error = e;
}
if (callback)
callback(error);
else if (error)
throw error;
}
function connectControlChannel(server) {
/* create the control connection */
var opt = null;
if (server.options.token != null) {
opt = { headers: { 'ServiceBusAuthorization': server.options.token } };
};
server.controlChannel = new WebSocket(server.listenUri, null, opt);
server.controlChannel.onerror = function (event) {
server.emit('error', event);
if (!server.closeRequested) {
connectControlChannel(server);
}
}
server.controlChannel.onopen = function (event) {
server.emit('listening');
}
server.controlChannel.onclose = function (event) {
// reconnect
if (!server.closeRequested) {
connectControlChannel(server);
} else {
server.emit('close', server);
}
}
server.controlChannel.onmessage = function (event) {
var message = JSON.parse(event.data);
if (isDefinedAndNonNull(message, 'accept')) {
accept(server, message);
}
};
}
function accept(server, message) {
var address = message.accept.address;
var req = { headers: {} };
var headers = [];
for (var keys = Object.keys(message.accept.connectHeaders), l = keys.length; l; --l) {
req.headers[keys[l - 1].toLowerCase()] = message.accept.connectHeaders[keys[l - 1]];
}
// verify key presence
if (!req.headers['sec-websocket-key']) {
abortConnection(message, 400, 'Bad Request');
return;
}
// verify version
var version = parseInt(req.headers['sec-websocket-version']);
// verify protocol
var protocols = req.headers['sec-websocket-protocol'];
// verify client
var origin = version < 13 ?
req.headers['sec-websocket-origin'] :
req.headers['origin'];
// handle extensions offer
var extensionsOffer = Extensions.parse(req.headers['sec-websocket-extensions']);
// handler to call when the connection sequence completes
var self = server;
var completeHybiUpgrade2 = function (protocol) {
if (typeof protocol != 'undefined') {
headers.push('Sec-WebSocket-Protocol: ' + protocol);
}
var extensions = {};
try {
extensions = acceptExtensions.call(self, extensionsOffer);
} catch (err) {
abortConnection(message, 400, 'Bad Request');
return;
}
if (Object.keys(extensions).length) {
var serverExtensions = {};
Object.keys(extensions).forEach(function (token) {
serverExtensions[token] = [extensions[token].params]
});
headers.push('Sec-WebSocket-Extensions: ' + Extensions.format(serverExtensions));
}
// allows external modification/inspection of handshake headers
self.emit('headers', headers);
var client = new WebSocket(address, null, { rejectUnauthorized: false, headers: headers });
server.clients.push(client);
client.on('close', function () {
var index = server.clients.indexOf(client);
if (index != -1) {
server.clients.splice(index, 1);
}
});
server.emit('connection', client);
if (self.options.clientTracking) {
self.clients.push(client);
client.on('close', function () {
var index = self.clients.indexOf(client);
if (index != -1) {
self.clients.splice(index, 1);
}
});
}
}
// optionally call external protocol selection handler before
// calling completeHybiUpgrade2
var completeHybiUpgrade1 = function () {
// choose from the sub-protocols
if (typeof self.options.handleProtocols == 'function') {
var protList = (protocols || '').split(/, */);
var callbackCalled = false;
self.options.handleProtocols(protList, function (result, protocol) {
callbackCalled = true;
if (!result) abortConnection(socket, 401, 'Unauthorized');
else completeHybiUpgrade2(protocol);
});
if (!callbackCalled) {
// the handleProtocols handler never called our callback
abortConnection(socket, 501, 'Could not process protocols');
}
return;
} else {
if (typeof protocols !== 'undefined') {
completeHybiUpgrade2(protocols.split(/, */)[0]);
}
else {
completeHybiUpgrade2();
}
}
}
completeHybiUpgrade1();
}
function acceptExtensions(offer) {
var extensions = {};
var options = this.options.perMessageDeflate;
var maxPayload = this.options.maxPayload;
if (options && offer[PerMessageDeflate.extensionName]) {
var perMessageDeflate = new PerMessageDeflate(options !== true ? options : {}, true, maxPayload);
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
return extensions;
}
function abortConnection(message, status, reason) {
var client = new WebSocketClient({ tlsOptions: { rejectUnauthorized: false } });
var rejectUri = message.address + "&statusCode=" + status + "&statusDescription" + encodeURIComponent(reason);
client.connect(rejectUri, null, null);
client.on('error', function (connection) {
this.emit('requestRejected', this);
});
}
module.exports = HybridConnectionsWebSocketServer

29
hyco-ws/package.json Normal file
Просмотреть файл

@ -0,0 +1,29 @@
{
"author": "xyz",
"name": "hyco-ws",
"description": "xyz",
"version": "1.0.0",
"license": "MIT",
"main": "index.js",
"keywords": [
"Hixie",
"HyBi",
"Push",
"RFC-6455",
"WebSocket",
"WebSockets",
"real-time"
],
"repository" :
{
"type" : "git",
"url" : "https://github.com/clemensv/azure-relay-hybridconnections.git"
},
"dependencies": {
"ws": "1.1.x",
"crypto": "latest",
"moment": "latest"
},
"gypfile": true,
"private" : false
}