mqtt-ws
Version:
a Node.js MQTT/WebSocket bridge
167 lines (146 loc) • 4.98 kB
JavaScript
/*!
* mqtt-ws: a node.js MQTT/WebSocket bridge
* Copyright(c) 2013 M2M Connections, Inc <info@m2mconnectionsinc.com>
* MIT Licensed
*/
var optimist = require('optimist'),
myArgs = optimist
.usage('MQTT/WebSocket Bridge\nUsage: $0')
.alias({
'p': 'port',
'h': 'host',
'l': 'listen',
'c': 'configFile',
})
.describe({
'p': 'MQTT port to connect to',
'h': 'Hostname of MQTT server',
'l': 'WebSocket port to listen on',
'c': 'Configuration file',
'help': 'Show this help'
})
.argv,
url = require('url'),
util = require('util'),
errno = require('errno'),
log4js = require('log4js'),
logger = log4js.getLogger(),
mqttws = require('../lib/mqtt-ws');
if (myArgs.help) {
optimist.showHelp();
process.exit(0);
}
// If we are given a config file, parse that,
// otherwise just parse command line
if (myArgs.c || myArgs.configFile) {
var configFile = myArgs.c || myArgs.configFile;
logger.info("Reading configuration from %s", configFile);
require('fs').readFile(configFile, 'utf8', function(err, data) {
if (err) {
logger.info("Error reading config file %s: %s", configFile, err);
process.exit(-1);
}
config = JSON.parse(data);
run(parseCommandLine(myArgs, config));
});
} else {
run(parseCommandLine(myArgs, {mqtt: {}, websocket: {}}));
}
// Parse the command line
function parseCommandLine(args, config) {
if (args.p || args.port) {
config.mqtt.port = args.p || args.port;
}
if (args.h || args.host) {
config.mqtt.host = args.h || args.host;
}
if (args.l || args.listen) {
config.websocket.port = args.l || args.listen;
}
return config;
}
function getErrnoDescription(err) {
if (!err.errno) return undefined;
if (typeof err.errno == 'number') {
var e = errno.errno[err.errno];
if (e) {
return e.description;
} else {
return undefined;
}
} else if (typeof err.errno == 'string') {
for (var e in errno.errno) {
if (errno.errno[e].code == err.code) {
return errno.errno[e].description;
}
}
return undefined;
}
}
function logError(err, message) {
if (err.syscall != undefined) {
var description = getErrnoDescription(err) || err.code;
logger.error("%s on %s: %s", message, err.syscall, description);
} else {
logger.error("%s: %s", message, err);
}
}
// Start the bridge
function run(config) {
if (config.log4js) {
log4js.configure(config.log4js);
}
// Create our bridge
bridge = mqttws.createBridge(config);
logger.info("Listening for incoming WebSocket connections on port %d",
bridge.port);
// Set up error handling
bridge.on('error', function(err) {
logError(err, "WebSocket Error");
});
// Handle incoming WS connection
bridge.on('connection', function(ws) {
// URL-decode the URL, and use the URI part as the subscription topic
logger.info("WebSocket connection from %s received", ws.connectString);
var self = this;
ws.on('error', function(err) {
logError(err, util.format("WebSocket error in client %s", ws.connectString));
});
// Parse the URL
var parsed = url.parse(ws.upgradeReq.url, true);
// Connect to the MQTT server using the URL query as options
var mqtt = bridge.connectMqtt(parsed.query);
mqtt.topic = decodeURIComponent(parsed.pathname.substring(1));
mqtt.isWildcardTopic = (mqtt.topic.match(/[\+#]/) != null);
ws.on('close', function() {
logger.info("WebSocket client %s closed", ws.connectString);
mqtt.end();
});
ws.on('message', function(message) {
logger.info("WebSocket client %s publishing '%s' to %s",
ws.connectString, message, mqtt.topic);
mqtt.publish(mqtt.topic, message, mqtt.options);
});
mqtt.on('error', function(err) {
logError(err, "MQTT error");
});
mqtt.on('connect', function() {
logger.info("Connected to MQTT server at %s:%d", mqtt.host, mqtt.port);
logger.info("WebSocket client %s subscribing to '%s'", ws.connectString, mqtt.topic);
mqtt.subscribe(mqtt.topic);
});
mqtt.on('close', function() {
logger.info("MQTT connection for client %s closed",
ws.connectString);
ws.terminate();
});
mqtt.on('message', function(topic, message, packet) {
if (mqtt.isWildcardTopic) {
ws.send(util.format("%s: %s", topic, message), self.options);
} else {
ws.send(message, self.options);
}
});
});
};