Skip to content

Commit bc03c1f

Browse files
committed
Implemented support for connecting via WebSockets and proxies
1 parent eb947dc commit bc03c1f

15 files changed

+491
-62
lines changed

.travis.yml

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313

1414
before_install:
1515
- docker pull opendxl/opendxl-broker
16-
- docker run -d -p 127.0.0.1:8883:8883 -p 127.0.0.1:8443:8443 opendxl/opendxl-broker
16+
- docker run -d -p 127.0.0.1:8883:8883 -p 127.0.0.1:8443:8443 -p 127.0.0.1:443:443 opendxl/opendxl-broker
1717
- docker ps -a
1818

1919
env:
@@ -32,4 +32,10 @@ before_script:
3232
- cat test/integration/client_config.cfg
3333

3434
script:
35+
- echo Running tests with MQTT
3536
- npm run ci
37+
- sed -i -e "s/#UseWebSockets=false/UseWebSockets=true/g" test/integration/client_config.cfg
38+
- cat test/integration/client_config.cfg
39+
- echo Running tests with WebSockets
40+
- npm run test
41+

doc/sdk/basic-cli-provisioning.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ Here is an example usage of `provisionconfig` operation for a Mac or Linux-based
1313
operating system:
1414

1515
```sh
16-
node_modules/.bin/dxlclient config myserver client1
16+
node_modules/.bin/dxlclient provisionconfig config myserver client1
1717
```
1818

1919
For Windows, the equivalent command would be:
2020

2121
```sh
22-
node_modules\.bin\dxlclient config myserver client1
22+
node_modules\.bin\dxlclient provisionconfig config myserver client1
2323
```
2424

2525
The parameters are as follows:
@@ -46,7 +46,7 @@ environment variable, you can also provide the path to the openssl executable
4646
as a separate parameter. For example:
4747

4848
```sh
49-
node_modules\.bin\dxlclient --opensslbin D:\custom\openssl.exe config myserver client1
49+
node_modules\.bin\dxlclient --opensslbin D:\custom\openssl.exe provisionconfig config myserver client1
5050
```
5151

5252
When prompted, provide credentials for the OpenDXL Broker Management Console

doc/sdk/samples.md

+3
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ Prior to running any of the examples, you must complete the following:
55

66
The `dxlclient.config` file located in the `sample` sub-directory of the
77
JavaScript DXL SDK must be populated.
8+
9+
NOTE: If {@tutorial basic-cli-provisioning} Command Line Interface (CLI) provisioning is being performed, specify the
10+
`sample` directory as the output location for the provisioning operation.

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports.RequestError = require('./lib/request-error')
1313
exports.Response = require('./lib/response')
1414
exports.ResponseErrorCode = require('./lib/response-error-code')
1515
exports.ServiceRegistrationInfo = require('./lib/service-registration-info')
16+
exports.Proxy = require('./lib/proxy')
1617

1718
// Leaving this for backward compatibility for now
1819
exports.MessageError = exports.RequestError

lib/_provisioning/management-service.js

+16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var https = require('https')
55
var querystring = require('querystring')
66
var url = require('url')
77
var provisionUtil = require('./provision-util')
8+
var HttpsProxyAgent = require('https-proxy-agent')
89

910
var HTTP_MAX_REDIRECTS = 5
1011

@@ -230,6 +231,21 @@ ManagementService.prototype.request = function (description, path, queryString,
230231
requestOptions.requestCert = false
231232
}
232233

234+
// use proxy specified in the env
235+
var proxy
236+
if (process.env.http_proxy != null) { proxy = process.env.http_proxy }
237+
if (process.env.HTTP_PROXY != null) { proxy = process.env.HTTP_PROXY }
238+
239+
if (proxy) {
240+
// if there is no protocol set in the env variable add the http://
241+
proxy = (proxy.indexOf('://') === -1) ? 'http://' + proxy : proxy
242+
var proxyOptions = url.parse(proxy)
243+
proxyOptions.secureEndpoint = true
244+
console.log('Connecting via Proxy:', proxy)
245+
// set proxy agent in requestOptions
246+
requestOptions.agent = new HttpsProxyAgent(proxyOptions)
247+
}
248+
233249
var requestUrl = toUrlString(requestOptions)
234250

235251
httpGet(description, 0, requestOptions, verbosity,

lib/_provisioning/provision-config.js

+40-4
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,13 @@ function brokersForConfig (brokerLines) {
6868
* include in the stored configuration file.
6969
* @param {Number} verbosity - Level of verbosity at which to log any error
7070
* or trace messages.
71+
* @param {Boolean} useWebSockets Whether or not the client will use WebSockets.
72+
* If false MQTT over tcp will be used.
73+
* If only WebSocket brokers are specified this will default to true.
7174
* @private
7275
*/
7376
function storeProvisionConfig (config, configDir, filePrefix,
74-
privateKeyFileName, verbosity) {
77+
privateKeyFileName, verbosity, useWebSockets) {
7578
if (typeof config !== 'string') {
7679
throw new DxlError('Unexpected data type for response: ' +
7780
typeof config)
@@ -91,16 +94,41 @@ function storeProvisionConfig (config, configDir, filePrefix,
9194

9295
var brokers = brokersForConfig(brokerLines)
9396

97+
var brokerWSLines = []
98+
if (configElements[3]) {
99+
brokerWSLines = configElements[3].split(
100+
/[\r\n]+/).filter(function (brokerWSLine) {
101+
return brokerWSLine.length > 0
102+
}
103+
)
104+
}
105+
106+
var brokersWS = brokersForConfig(brokerWSLines)
107+
var webSocketsEnabled = typeof useWebSockets !== 'undefined' ? useWebSockets : false
94108
var certFileName = filePrefix + '.crt'
95109
var configString = ini.encode({
110+
General: {
111+
UseWebSockets: webSocketsEnabled
112+
},
96113
Certs: {
97114
BrokerCertChain: DEFAULT_BROKER_CERT_CHAIN_FILE_NAME,
98115
CertFile: path.basename(certFileName),
99116
PrivateKey: path.basename(privateKeyFileName)
100117
},
101-
Brokers: brokers
118+
Brokers: brokerLines.length ? brokers : { marker: 1 },
119+
BrokersWebSockets: brokerWSLines.length ? brokersWS : { marker: 1 }
102120
}).replace(/\\;/g, ';')
103121

122+
// ini.js does not support empty sections , using marker and replacing it
123+
if (configString.includes('marker=1')) {
124+
configString = configString.replace(/marker=1(\r\n|\n)/g, '')
125+
}
126+
127+
if (!webSocketsEnabled) {
128+
// UseWebSockets property is commented out by default
129+
configString = configString.replace('UseWebSockets', '#UseWebSockets')
130+
}
131+
104132
var configFileName = path.join(configDir,
105133
provisionUtil.DEFAULT_CONFIG_FILE_NAME)
106134
if (verbosity) {
@@ -203,10 +231,13 @@ function storeProvisionConfig (config, configDir, filePrefix,
203231
* provisioned configuration has been stored. If an error occurs, the first
204232
* parameter supplied to the `doneCallback` is an `Error` instance
205233
* containing failure details.
234+
* @param {Boolean} [useWebSockets] Whether or not the client will use WebSockets.
235+
* If false MQTT over tcp will be used.
236+
* If only WebSocket brokers are specified this will default to true.
206237
* @private
207238
*/
208239
module.exports = function provisionConfig (configDir, commonOrCsrFileName,
209-
hostInfo, options) {
240+
hostInfo, options, useWebSockets) {
210241
if (!configDir) {
211242
throw new TypeError('configDir is required for provisioning')
212243
}
@@ -239,10 +270,15 @@ module.exports = function provisionConfig (configDir, commonOrCsrFileName,
239270
try {
240271
storeProvisionConfig(config, configDir,
241272
options.filePrefix || pki.DEFAULT_PKI_FILE_PREFIX,
242-
privateKeyFileName, verbosity)
273+
privateKeyFileName, verbosity, useWebSockets)
243274
} catch (err) {
244275
error = err
245276
}
277+
} else {
278+
if (error) {
279+
console.log('Error from server' + error)
280+
}
281+
console.log('No data from Server.Check Server Configuration')
246282
}
247283
provisionUtil.invokeCallback(error, doneCallback, verbosity)
248284
}

lib/_provisioning/update-config.js

+44-11
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,41 @@ function requestBrokerList (managementService, verbosity, callback) {
7575
*/
7676
function brokerConfigLines (brokerListResponse) {
7777
var brokers = JSON.parse(brokerListResponse).brokers
78-
return brokers.map(function (broker) {
79-
return broker.guid + '=' + [broker.guid, broker.port,
80-
broker.hostName, broker.ipAddress].join(';')
81-
})
78+
return createMap(brokers)
79+
}
80+
81+
/**
82+
* Converts the list of WebSocket Brokers received from a call to the getBrokerList
83+
* endpoint on the management server (JSON objects) into the broker format
84+
* used in a configuration ini file (key=value pairs).
85+
* @param {String} brokerListResponse - Response received from the management
86+
* service for a request to the getBrokerList endpoint.
87+
* @returns {Array<String>} A list of WebSocket broker lines to include in the client
88+
* configuration file.
89+
* @private
90+
*/
91+
function brokerWsConfigLines (brokerListResponse) {
92+
var brokersWs = JSON.parse(brokerListResponse).webSocketBrokers
93+
return createMap(brokersWs)
94+
}
95+
96+
/**
97+
* Helper function that converts the list of broker info received from a call to the getBrokerList
98+
* endpoint on the management server (JSON objects) into the broker format
99+
* used in a configuration ini file (key=value pairs).
100+
* @param {String} brokers - ParsedResponse received from the management
101+
* service for a request to the getBrokerList endpoint.
102+
* @returns {Array<String>} A list of broker lines to include in the client
103+
* configuration file.
104+
* @private
105+
*/
106+
function createMap (brokers) {
107+
if (brokers) {
108+
return brokers.map(function (broker) {
109+
return broker.guid + '=' + [broker.guid, broker.port,
110+
broker.hostName, broker.ipAddress].join(';')
111+
})
112+
}
82113
}
83114

84115
/**
@@ -89,13 +120,10 @@ function brokerConfigLines (brokerListResponse) {
89120
* into the configuration file.
90121
* @param {Number} verbosity - Level of verbosity at which to log any error
91122
* or trace messages.
123+
* @param {String} section - Section like Brokers or websockets to process
92124
* @private
93125
*/
94-
function updateBrokerListInConfigFile (configFileName, brokerLines, verbosity) {
95-
if (verbosity) {
96-
console.log('Updating DXL config file at ' + configFileName)
97-
}
98-
126+
function updateBrokerListInConfigFile (configFileName, brokerLines, verbosity, section) {
99127
var inBrokerSection = false
100128
var linesAfterBrokerSection = []
101129
var configFileData = util.getConfigFileData(configFileName)
@@ -105,7 +133,7 @@ function updateBrokerListInConfigFile (configFileName, brokerLines, verbosity) {
105133
// sections - for example, the 'Certs' section.
106134
var newConfigLines = configFileData.lines.reduce(
107135
function (acc, line) {
108-
if (line === '[Brokers]') {
136+
if (line === section) {
109137
inBrokerSection = true
110138
acc.push(line)
111139
} else {
@@ -273,7 +301,12 @@ module.exports = function updateConfig (configDir, hostInfo, options) {
273301
if (response) {
274302
try {
275303
updateBrokerListInConfigFile(configFileName,
276-
brokerConfigLines(response), verbosity)
304+
brokerConfigLines(response), verbosity, '[Brokers]')
305+
updateBrokerListInConfigFile(configFileName,
306+
brokerWsConfigLines(response), verbosity, '[BrokersWebSockets]')
307+
if (verbosity) {
308+
console.log('Updating DXL config file at ' + configFileName)
309+
}
277310
} catch (err) {
278311
error = err
279312
}

lib/client.js

+44-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ var DxlError = require('./dxl-error')
1010
var message = require('./message')
1111
var RequestManager = require('./request-manager')
1212
var ServiceManager = require('./service-manager')
13+
var HttpsProxyAgent = require('https-proxy-agent')
14+
var url = require('url')
15+
var nodeUtil = require('util')
16+
var debug = nodeUtil.debuglog('dxlclient')
1317

1418
var REPLY_TO_PREFIX = '/mcafee/client/'
1519

@@ -132,6 +136,7 @@ function Client (config) {
132136
return result
133137
}, [])
134138

139+
this._iswebSocketEnabled = this.config.useWebSockets
135140
events.EventEmitter.call(this)
136141
}
137142

@@ -305,10 +310,20 @@ Client.prototype.connect = function (callback) {
305310

306311
var that = this
307312
var firstConnection = true
308-
313+
// server list
314+
var brokerList = this._servers
315+
// default protocol is MQTT
316+
var protocolToUse = 'mqtts'
317+
var rejectUnauthorized = true
318+
// if UseWebSockets property is enabled or mqtt broker list is empty, connect via WebSockets
319+
if (this._iswebSocketEnabled) {
320+
protocolToUse = 'wss'
321+
rejectUnauthorized = false
322+
console.log('Setting \'UseWebSockets\' is enabled. Client will connect via WebSockets')
323+
}
309324
var connectOptions = {
310-
servers: this._servers,
311-
protocol: 'mqtts',
325+
servers: brokerList,
326+
protocol: protocolToUse,
312327
protocolId: 'MQIsdp',
313328
protocolVersion: 3,
314329
clientId: this._clientId,
@@ -320,10 +335,35 @@ Client.prototype.connect = function (callback) {
320335
},
321336
keepalive: this.config.keepAliveInterval,
322337
reconnectPeriod: this.config.reconnectDelay * 1000,
323-
rejectUnauthorized: true,
338+
rejectUnauthorized: rejectUnauthorized,
324339
requestCert: true
325340
}
341+
// check proxy configuration (Optional)
342+
var proxy = that.config.proxy
343+
if (protocolToUse === 'wss' && proxy) {
344+
var port = proxy.port
345+
var hostname = proxy.address
346+
var user = proxy.user
347+
var pwd = proxy.password
348+
// http proxy support Only
349+
var proxyUrl = nodeUtil.format('http://%s:%d', hostname, port)
350+
var proxyOptions = url.parse(proxyUrl)
351+
// true for wss
352+
proxyOptions.secureEndpoint = true
353+
console.log('Connecting via Proxy:', proxyUrl)
354+
if (user && pwd) {
355+
proxyOptions.auth = nodeUtil.format('%s:%s', user, pwd)
356+
console.log('Proxy connection user:', user)
357+
}
358+
// Set up a proxy agent
359+
var wsagent = new HttpsProxyAgent(proxyOptions)
360+
// Set Agent for wsOption in MQTT
361+
connectOptions.wsOptions = {
362+
agent: wsagent
363+
}
364+
}
326365

366+
debug('Connect options', connectOptions)
327367
var mqttClient = mqtt.connect(connectOptions)
328368

329369
Object.keys(this._subscriptionsByMessageType).forEach(function (topic) {

0 commit comments

Comments
 (0)