• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

telefonicaid / iotagent-json / 11363611528

27 Sep 2024 11:24AM UTC coverage: 79.595% (-0.02%) from 79.614%
11363611528

push

github

web-flow
Merge pull request #845 from telefonicaid/task/add_response_header_procesing_time

add response time procesing header

494 of 699 branches covered (70.67%)

Branch coverage included in aggregate %.

6 of 8 new or added lines in 1 file covered. (75.0%)

1 existing line in 1 file now uncovered.

1078 of 1276 relevant lines covered (84.48%)

130.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.11
/lib/bindings/HTTPBinding.js
1
/*
2
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
3
 *
4
 * This file is part of iotagent-json
5
 *
6
 * iotagent-json is free software: you can redistribute it and/or
7
 * modify it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the License,
9
 * or (at your option) any later version.
10
 *
11
 * iotagent-json is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
 * See the GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public
17
 * License along with iotagent-json.
18
 * If not, seehttp://www.gnu.org/licenses/.
19
 *
20
 * For those usages not covered by the GNU Affero General Public License
21
 * please contact with::[contacto@tid.es]
22
 */
23

24
/* eslint-disable no-unused-vars */
25

26
const fs = require('fs');
1✔
27
const iotAgentLib = require('iotagent-node-lib');
1✔
28
const regenerateTransid = iotAgentLib.regenerateTransid;
1✔
29
const finishSouthBoundTransaction = iotAgentLib.finishSouthBoundTransaction;
1✔
30
const fillService = iotAgentLib.fillService;
1✔
31
const intoTrans = iotAgentLib.intoTrans;
1✔
32
const _ = require('underscore');
1✔
33
const commandHandler = require('../commandHandler');
1✔
34
const async = require('async');
1✔
35
const apply = async.apply;
1✔
36
const request = iotAgentLib.request;
1✔
37
const errors = require('../errors');
1✔
38
const express = require('express');
1✔
39
const iotaUtils = require('../iotaUtils');
1✔
40
const http = require('http');
1✔
41
const https = require('https');
1✔
42
const commonBindings = require('../commonBindings');
1✔
43
const bodyParser = require('body-parser');
1✔
44
require('body-parser-xml')(bodyParser);
1✔
45
const constants = require('../constants');
1✔
46
let context = {
1✔
47
    op: 'IOTAJSON.HTTP.Binding'
48
};
49

50
const config = require('../configService');
1✔
51
let httpBindingServer;
52
const transport = 'HTTP';
1✔
53

54
const { promisify } = require('util');
1✔
55
const json = promisify(bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit })); // accept anything JSON.parse accepts.
1✔
56
const text = promisify(bodyParser.text({ limit: config.getConfig().iota.expressLimit }));
1✔
57
const raw = promisify(bodyParser.raw({ limit: config.getConfig().iota.expressLimit }));
1✔
58
const xml2js = require('xml2js');
1✔
59
const xmlStripPrefix = xml2js.processors.stripPrefix;
1✔
60
const xml = promisify(
1✔
61
    bodyParser.xml({
62
        xmlParseOptions: {
63
            // XML namespaces might change from one request to the next.
64
            // It is useful to remove them from the document,
65
            // to be able to refer to tags later in JEXL transformations.
66
            // See https://github.com/Leonidas-from-XIV/node-xml2js/issues/87
67
            tagNameProcessors: [xmlStripPrefix],
68
            attrNameProcessors: [xmlStripPrefix]
69
        }
70
    })
71
);
72

73
function parserBody() {
74
    // generic bodyParser
75
    return function (req, res, next) {
248✔
76
        if (req.is('text/plain')) {
8✔
77
            text(req, res).then(() => next(), next);
2✔
78
        } else if (req.is('application/octet-stream')) {
6✔
79
            raw(req, res).then(() => next(), next);
2✔
80
        } else if (req.is('application/soap+xml')) {
4✔
81
            xml(req, res).then(() => next(), next);
2✔
82
        } else {
83
            // req.is('json')
84
            json(req, res).then(() => next(), next);
2✔
85
        }
86
    };
87
}
88

89
function checkMandatoryParams(queryPayload) {
90
    return function (req, res, next) {
1,240✔
91
        const notFoundParams = [];
147✔
92
        let error;
93

94
        req.apiKey = req.query.k;
147✔
95
        req.deviceId = req.query.i;
147✔
96
        req.attr = req.params ? req.params.attrValue : undefined;
147!
97

98
        if (!req.apiKey) {
147!
99
            notFoundParams.push('API Key');
×
100
        }
101

102
        if (!req.deviceId) {
147!
103
            notFoundParams.push('Device Id');
×
104
        }
105

106
        // Check if retrievingParam
107
        if (queryPayload && !req.query.d && req.query.getCmd !== '1') {
147!
108
            notFoundParams.push('Payload');
×
109
        }
110
        if (
147!
111
            req.method === 'POST' &&
305✔
112
            !req.is('json') &&
113
            !req.is('text/plain') &&
114
            !req.is('application/octet-stream') &&
115
            !req.is('application/soap+xml')
116
        ) {
117
            error = new errors.UnsupportedType(
×
118
                'application/json, text/plain, application/octet-stream, application/soap+xml'
119
            );
120
        }
121

122
        if (notFoundParams.length !== 0) {
147!
123
            next(new errors.MandatoryParamsNotFound(notFoundParams));
×
124
        } else {
125
            next(error);
147✔
126
        }
127
    };
128
}
129

130
function parseData(req, res, next) {
131
    let data;
132
    let error;
133
    let payload;
134

135
    if (req.body) {
15✔
136
        config.getLogger().debug(context, 'Using body %s', req.body);
14✔
137
        data = req.body;
14✔
138
        regenerateTransid(data);
14✔
139
    } else {
140
        payload = req.query.d;
1✔
141
        regenerateTransid(payload);
1✔
142
        config.getLogger().debug(context, 'Parsing payload %s', payload);
1✔
143

144
        try {
1✔
145
            if (payload) {
1!
146
                data = JSON.parse(payload);
×
147
            }
148
        } catch (e) {
149
            error = e;
×
150
        }
151
    }
152

153
    if (error) {
15!
154
        next(error);
×
155
    } else {
156
        req.jsonPayload = data;
15✔
157
        if (req.body !== undefined) {
15✔
158
            try {
14✔
159
                // This is just for log data
160
                data = data.toString('hex');
14✔
161
            } catch (e) {
162
                // no error should be reported
163
            }
164
        }
165
        config.getLogger().debug(context, 'Parsed data: %j', data);
15✔
166
        next();
15✔
167
    }
168
}
169

170
function parseDataMultipleMeasure(req, res, next) {
171
    let data;
172
    let error;
173
    let payload;
174
    let dataArray;
175
    dataArray = [];
132✔
176
    if (req.body) {
132!
177
        config.getLogger().debug(context, 'Using body %s', req.body);
132✔
178
        if (!Array.isArray(req.body)) {
132✔
179
            dataArray.push(req.body);
116✔
180
        } else {
181
            dataArray = req.body;
16✔
182
        }
183
        regenerateTransid(dataArray);
132✔
184
    } else {
185
        payload = req.query.d;
×
186
        regenerateTransid(payload);
×
187
        config.getLogger().debug(context, 'Parsing payload %s', payload);
×
188
        try {
×
189
            if (payload) {
×
190
                data = JSON.parse(payload);
×
191
                dataArray.push(data);
×
192
            }
193
        } catch (e) {
194
            error = e;
×
195
        }
196
    }
197
    if (error) {
132!
198
        next(error);
×
199
    } else {
200
        req.jsonPayload = dataArray;
132✔
201
        config.getLogger().debug(context, 'Parsed data array: %j', dataArray);
132✔
202
        next();
132✔
203
    }
204
}
205

206
function executeCommand(apiKey, group, device, cmdName, serializedPayload, contentType, callback) {
207
    const options = {
7✔
208
        url: device.endpoint || (group && group.endpoint ? group.endpoint : undefined),
11!
209
        method: 'POST',
210
        body: serializedPayload,
211
        headers: {
212
            'fiware-service': device.service,
213
            'fiware-servicepath': device.subservice,
214
            'content-type': contentType
215
        }
216
    };
217
    if (options.endpoint) {
7!
218
        // endpoint could be an expression
219
        const parser = iotAgentLib.dataPlugins.expressionTransformation;
×
220
        let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device);
×
221
        attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]);
×
222
        const ctxt = parser.extractContext(attrList, device);
×
223
        config.getLogger().debug(context, 'attrList %j for device %j', attrList, device);
×
224
        // expression result will be the full command payload
225
        let endpointRes = null;
×
226
        try {
×
227
            endpointRes = parser.applyExpression(options.endpoint, ctxt, device);
×
228
        } catch (e) {
229
            // no error should be reported
230
        }
231
        options.url = endpointRes ? endpointRes : options.endpoint;
×
232
    }
233
    if (config.getConfig().http.timeout) {
7!
234
        options.timeout = config.getConfig().http.timeout;
×
235
    }
236
    config
7✔
237
        .getLogger()
238
        .debug(
239
            context,
240
            'Sending command %s with payload %s and with http options %j',
241
            cmdName,
242
            serializedPayload,
243
            options
244
        );
245
    request(options, function (error, response, body) {
7✔
246
        if (error || !response || (response.statusCode !== 200 && response.statusCode !== 201)) {
7!
247
            callback(new errors.HTTPCommandResponseError(response ? response.statusCode : 400, error));
×
248
        } else if (apiKey) {
7!
249
            config
7✔
250
                .getLogger()
251
                .info(
252
                    context,
253
                    'Cmd: %j was sent to the device %s with http options %j',
254
                    serializedPayload,
255
                    cmdName,
256
                    options
257
                );
258
            process.nextTick(commandHandler.updateCommand.bind(null, apiKey, device.id, device, body));
7✔
259
            callback();
7✔
260
        } else {
261
            config
×
262
                .getLogger()
263
                .info(
264
                    context,
265
                    'Cmd: %j was sent to the device %s with http options %j',
266
                    serializedPayload,
267
                    cmdName,
268
                    options
269
                );
270
            callback();
×
271
        }
272
    });
273
}
274

275
function addTimestamp(req, res, next) {
276
    const arr = req.jsonPayload;
142✔
277
    let timeStampData;
278
    for (const i in arr) {
142✔
279
        if (req.query.t && arr[i]) {
371✔
280
            timeStampData = arr[i];
4✔
281
            timeStampData[constants.TIMESTAMP_ATTRIBUTE] = req.query.t;
4✔
282
        }
283
    }
284
    next();
142✔
285
}
286

287
function handleIncomingMeasure(req, res, next) {
288
    let values;
289
    context = fillService(context, { service: 'n/a', subservice: 'n/a' });
142✔
290
    config
142✔
291
        .getLogger()
292
        .debug(context, 'Processing multiple HTTP measures for device %s with apiKey %s', req.deviceId, req.apiKey);
293

294
    function updateCommandHandler(error) {
295
        if (error) {
141!
296
            next(error);
×
297
            config.getLogger().error(
×
298
                context,
299
                /*jshint quotmark: double */
300
                "MEASURES-002: Couldn't send the updated values to the Context Broker due to an error: %j",
301
                /*jshint quotmark: single */
302
                error
303
            );
304
        } else {
305
            config
141✔
306
                .getLogger()
307
                .info(
308
                    context,
309
                    'Multiple measures for device %s with apiKey %s successfully updated',
310
                    req.deviceId,
311
                    req.apiKey
312
                );
313

314
            finishSouthBoundTransaction(next);
141✔
315
        }
316
    }
317

318
    function processHTTPWithDevice(device) {
319
        let payloadDataArr;
320
        let attributeArr;
321
        let attributeValues;
322
        attributeArr = [];
142✔
323
        payloadDataArr = [];
142✔
324

325
        if (req.attr && req.jsonPayload) {
142✔
326
            config.getLogger().debug(context, 'Parsing attr %s with value %s', req.attr, req.jsonPayload);
8✔
327
            try {
8✔
328
                req.jsonPayload = req.jsonPayload.toString('hex');
8✔
329
            } catch (e) {
330
                // no error should be reported
331
            }
332
            const theAttr = [{ name: req.attr, value: req.jsonPayload, type: 'None' }];
8✔
333
            attributeArr.push(theAttr);
8✔
334
        } else {
335
            if (!Array.isArray(req.jsonPayload)) {
134✔
336
                payloadDataArr.push(req.jsonPayload);
2✔
337
            } else {
338
                payloadDataArr = req.jsonPayload;
132✔
339
            }
340

341
            if (req.jsonPayload) {
134✔
342
                config.getLogger().debug(context, 'Parsing payloadDataArr %j for device %j', payloadDataArr, device);
133✔
343
                for (const i in payloadDataArr) {
133✔
344
                    values = commonBindings.extractAttributes(device, payloadDataArr[i], device.payloadType);
167✔
345
                    if (values && values[0] && values[0][0]) {
167✔
346
                        // Check multimeasure from a ngsiv2/ngsild entities array
347
                        attributeArr = attributeArr.concat(values);
2✔
348
                    } else {
349
                        attributeArr.push(values);
165✔
350
                    }
351
                }
352
            } else {
353
                attributeArr = [];
1✔
354
            }
355
        }
356
        if (attributeArr.length === 0) {
142✔
357
            finishSouthBoundTransaction(next);
1✔
358
        } else {
359
            config
141✔
360
                .getLogger()
361
                .debug(context, 'Processing measure device %s with attributeArr %j', device.name, attributeArr);
362
            if (req.isCommand) {
141✔
363
                const executions = [];
1✔
364
                for (const j in attributeArr) {
1✔
365
                    attributeValues = attributeArr[j];
1✔
366
                    for (const k in attributeValues) {
1✔
367
                        executions.push(
1✔
368
                            iotAgentLib.setCommandResult.bind(
369
                                null,
370
                                device.name,
371
                                config.getConfig().iota.defaultResource,
372
                                req.apiKey,
373
                                attributeValues[k].name,
374
                                attributeValues[k].value,
375
                                constants.COMMAND_STATUS_COMPLETED,
376
                                device
377
                            )
378
                        );
379
                    }
380
                }
381
                async.parallel(executions, updateCommandHandler);
1✔
382
            } else {
383
                iotAgentLib.update(device.name, device.type, '', attributeArr, device, updateCommandHandler);
140✔
384
            }
385
        }
386
    }
387

388
    function processDeviceMeasure(error, device) {
389
        if (error) {
142!
390
            next(error);
×
391
        } else {
392
            const localContext = _.clone(context);
142✔
393
            req.device = device;
142✔
394
            localContext.service = device.service;
142✔
395
            localContext.subservice = device.subservice;
142✔
396
            intoTrans(localContext, processHTTPWithDevice)(device);
142✔
397
        }
398
    }
399

400
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, processDeviceMeasure);
142✔
401
}
402

403
function isCommand(req, res, next) {
404
    if (
1!
405
        req.path ===
406
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH
1!
407
    ) {
408
        req.isCommand = true;
1✔
409
    }
410

411
    next();
1✔
412
}
413

414
function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) {
415
    function handleDeviceResponse(innerCallback) {
416
        return function (error, response, body) {
4✔
417
            if (error) {
4!
418
                innerCallback(error);
×
419
            } else if (response && response.statusCode !== 200) {
4!
420
                innerCallback(new errors.DeviceEndpointError(response.statusCode, body));
×
421
            } else {
422
                innerCallback();
4✔
423
            }
424
        };
425
    }
426

427
    function sendRequest(device, group, results, innerCallback) {
428
        const resultRequest = {
4✔
429
            url:
430
                (device.endpoint || (group && group.endpoint ? group.endpoint : undefined)) +
4!
431
                constants.HTTP_CONFIGURATION_PATH,
432

433
            method: 'POST',
434
            json: iotaUtils.createConfigurationNotification(results),
435
            headers: {
436
                'fiware-service': device.service,
437
                'fiware-servicepath': device.subservice,
438
                'content-type': 'application/json'
439
            }
440
        };
441

442
        request(resultRequest, handleDeviceResponse(innerCallback));
4✔
443
    }
444
    iotaUtils.retrieveDevice(deviceId, apiKey, function (error, device) {
4✔
445
        if (error) {
4!
446
            callback(error);
×
447
        } else if (!device.endpoint) {
4!
448
            callback(new errors.EndpointNotFound(device.id));
×
449
        } else {
450
            sendRequest(device, group, results, callback);
4✔
451
        }
452
    });
453
}
454

455
function handleConfigurationRequest(req, res, next) {
456
    function replyToDevice(error) {
457
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
5✔
458
        if (error) {
5!
459
            res.status(error.code).json(error);
×
460
        } else {
461
            res.status(200).json({});
5✔
462
        }
463
    }
464
    iotaUtils.retrieveDevice(req.deviceId, req.apiKey, function (error, device) {
5✔
465
        if (error) {
5!
466
            next(error);
×
467
        } else {
468
            iotaUtils.manageConfiguration(
5✔
469
                req.apiKey,
470
                req.deviceId,
471
                device,
472
                req.jsonPayload,
473
                sendConfigurationToDevice,
474
                replyToDevice
475
            );
476
        }
477
    });
478
}
479

480
function reqTiming(req, res, next) {
481
    req.startTime = Date.now();
147✔
482
    next();
147✔
483
}
484

485
function handleError(error, req, res, next) {
486
    let code = 500;
×
487

488
    config.getLogger().debug(context, 'Error %s handling request: %s', error.name, error.message);
×
489

490
    if (error.code && String(error.code).match(/^[2345]\d\d$/)) {
×
491
        code = error.code;
×
492
    }
NEW
493
    res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
494
    res.status(code).json({
×
495
        name: error.name,
496
        message: error.message
497
    });
498
}
499

500
/**
501
 * Just fills in the transport protocol in case there is none and polling if endpoint.
502
 *
503
 * @param {Object} device           Device object containing all the information about the device.
504
 * @param {Object} group            Group object containing all the information about the device.
505
 */
506
function setPollingAndDefaultTransport(device, group, callback) {
507
    config.getLogger().debug(context, 'httpbinding.setPollingAndDefaultTransport device %j group %j', device, group);
155✔
508
    if (!device.transport) {
155✔
509
        device.transport = group && group.transport ? group.transport : 'HTTP';
83!
510
    }
511

512
    if (device.transport === 'HTTP') {
155✔
513
        if (device.endpoint) {
105✔
514
            device.polling = false;
69✔
515
        } else {
516
            device.polling = !(group && group.endpoint);
36✔
517
        }
518
    }
519

520
    callback(null, device);
155✔
521
}
522

523
/**
524
 * Device provisioning handler.
525
 *
526
 * @param {Object} device           Device object containing all the information about the provisioned device.
527
 */
528
function deviceProvisioningHandler(device, callback) {
529
    config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler device %j', device);
151✔
530
    let group = {};
151✔
531
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function (
151!
532
        error,
533
        foundGroup
534
    ) {
535
        if (!error) {
151!
536
            group = foundGroup;
×
537
        }
538
        config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler group %j', group);
151✔
539
        setPollingAndDefaultTransport(device, group, callback);
151✔
540
    });
541
}
542

543
/**
544
 * Device updating handler. This handler just fills in the transport protocol in case there is none.
545
 *
546
 * @param {Object} device           Device object containing all the information about the updated device.
547
 */
548
function deviceUpdatingHandler(newDevice, oldDevice, callback) {
549
    config
4✔
550
        .getLogger()
551
        .debug(context, 'httpbinding.deviceUpdatingHandler newDevice %j oldDevice %j', newDevice, oldDevice);
552
    let group = {};
4✔
553
    iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', oldDevice.apikey, function (
4!
554
        error,
555
        foundGroup
556
    ) {
557
        if (!error) {
4!
558
            group = foundGroup;
×
559
        }
560
        config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler group %j', group);
4✔
561
        setPollingAndDefaultTransport(newDevice, group, callback);
4✔
562
    });
563
}
564

565
/**
566
 * This middleware checks whether there is any polling command pending to be sent to the device. If there is some,
567
 * add the command information to the return payload. Otherwise it returns an empty payload.
568
 */
569
function returnCommands(req, res, next) {
570
    function updateCommandStatus(device, commandList) {
571
        context = fillService(context, device);
6✔
572
        function createCommandUpdate(command) {
573
            return apply(
6✔
574
                iotAgentLib.setCommandResult,
575
                device.name,
576
                device.resource,
577
                req.query.k,
578
                command.name,
579
                ' ',
580
                'DELIVERED',
581
                device
582
            );
583
        }
584

585
        function cleanCommand(command) {
586
            return apply(iotAgentLib.removeCommand, device.service, device.subservice, device.id, command.name);
6✔
587
        }
588

589
        const updates = commandList.map(createCommandUpdate);
6✔
590
        const cleanCommands = commandList.map(cleanCommand);
6✔
591
        if (updates) {
6!
592
            async.parallel(updates.concat(cleanCommands), function (error, results) {
6✔
593
                if (error) {
6!
594
                    config
×
595
                        .getLogger()
596
                        .error(
597
                            context,
598
                            'Error updating command status after delivering commands for device %s',
599
                            device.id
600
                        );
601
                } else {
602
                    config
6✔
603
                        .getLogger()
604
                        .debug(
605
                            context,
606
                            'Command status updated successfully after delivering command list to device %s',
607
                            device.id
608
                        );
609
                }
610
            });
611
        }
612
    }
613

614
    function parseCommand(item) {
615
        const result = {};
6✔
616
        const cleanedValue = String(item.value).trim();
6✔
617

618
        if (cleanedValue !== '') {
6!
619
            result[item.name] = item.value;
6✔
620
        }
621
        return result;
6✔
622
    }
623

624
    function concatCommand(previous, current) {
625
        if (previous === {}) {
6!
626
            return current;
×
627
        }
628
        return _.extend(previous, current);
6✔
629
    }
630
    if (req.query && req.query.getCmd === '1') {
142✔
631
        iotAgentLib.commandQueue(req.device.service, req.device.subservice, req.deviceId, function (error, list) {
6✔
632
            res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
6✔
633
            if (error || !list || list.count === 0) {
6!
634
                if (req.accepts('json')) {
×
635
                    res.status(200).send({});
×
636
                } else {
637
                    res.status(200).send('');
×
638
                }
639
            } else {
640
                if (req.accepts('json')) {
6!
641
                    res.status(200).send(list.commands.map(parseCommand).reduce(concatCommand, {}));
6✔
642
                } else {
643
                    res.status(200).send(JSON.stringify(list.commands.map(parseCommand).reduce(concatCommand, {})));
×
644
                }
645
                process.nextTick(updateCommandStatus.bind(null, req.device, list.commands));
6✔
646
            }
647
        });
648
    } else if (req.accepts('json')) {
136!
649
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
136✔
650
        res.status(200).send({});
136✔
651
    } else {
NEW
652
        res.set(constants.X_PROCESSING_TIME, Date.now() - req.startTime);
×
UNCOV
653
        res.status(200).send('');
×
654
    }
655
}
656

657
function start(callback) {
658
    const baseRoot = '/';
248✔
659

660
    httpBindingServer = {
248✔
661
        server: null,
662
        app: express(),
663
        router: express.Router()
664
    };
665

666
    if (!config.getConfig().http) {
248!
667
        config
×
668
            .getLogger()
669
            .fatal(context, 'GLOBAL-002: Configuration error. Configuration object [config.http] is missing');
670
        callback(new errors.ConfigurationError('config.http'));
×
671
        return;
×
672
    }
673

674
    httpBindingServer.app.set('port', config.getConfig().http.port);
248✔
675
    httpBindingServer.app.set('host', config.getConfig().http.host || '0.0.0.0');
248!
676
    httpBindingServer.app.use(reqTiming);
248✔
677

678
    httpBindingServer.router.get(
248✔
679
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
248!
680
        checkMandatoryParams(true),
681
        parseData,
682
        addTimestamp,
683
        handleIncomingMeasure,
684
        returnCommands
685
    );
686

687
    httpBindingServer.router.post(
248✔
688
        config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH,
248!
689
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts
690
        checkMandatoryParams(false),
691
        parseDataMultipleMeasure,
692
        addTimestamp,
693
        handleIncomingMeasure,
694
        returnCommands
695
    );
696

697
    httpBindingServer.router.post(
248✔
698
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) +
248!
699
            '/' +
700
            constants.MEASURES_SUFIX +
701
            '/:attrValue',
702
        parserBody(),
703
        checkMandatoryParams(false),
704
        parseData, // non multiple measures are expected in this route
705
        addTimestamp,
706
        handleIncomingMeasure,
707
        returnCommands
708
    );
709

710
    httpBindingServer.router.post(
248✔
711
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_COMMANDS_PATH,
248!
712
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
713
        checkMandatoryParams(false),
714
        parseData,
715
        addTimestamp,
716
        isCommand,
717
        handleIncomingMeasure,
718
        returnCommands
719
    );
720

721
    httpBindingServer.router.post(
248✔
722
        (config.getConfig().iota.defaultResource || constants.HTTP_MEASURE_PATH) + constants.HTTP_CONFIGURATION_PATH,
248!
723
        bodyParser.json({ strict: false, limit: config.getConfig().iota.expressLimit }), // accept anything JSON.parse accepts.
724
        checkMandatoryParams(false),
725
        parseData,
726
        handleConfigurationRequest
727
    );
728

729
    httpBindingServer.app.use(baseRoot, httpBindingServer.router);
248✔
730
    httpBindingServer.app.use(handleError);
248✔
731

732
    if (config.getConfig().http && config.getConfig().http.key && config.getConfig().http.cert) {
248!
733
        const privateKey = fs.readFileSync(config.getConfig().http.key, 'utf8');
×
734
        const certificate = fs.readFileSync(config.getConfig().http.cert, 'utf8');
×
735
        const credentials = { key: privateKey, cert: certificate };
×
736

737
        config.getLogger().info(context, 'HTTPS Binding listening on port %s', config.getConfig().http.port);
×
738
        httpBindingServer.server = https.createServer(credentials, httpBindingServer.app);
×
739
    } else {
740
        config.getLogger().info(context, 'HTTP Binding listening on port %s', config.getConfig().http.port);
248✔
741
        httpBindingServer.server = http.createServer(httpBindingServer.app);
248✔
742
    }
743

744
    httpBindingServer.server.listen(httpBindingServer.app.get('port'), httpBindingServer.app.get('host'), callback);
248✔
745
}
746

747
function stop(callback) {
748
    config.getLogger().info(context, 'Stopping JSON HTTP Binding: ');
248✔
749

750
    if (httpBindingServer) {
248!
751
        httpBindingServer.server.close(function () {
248✔
752
            config.getLogger().info(context, 'HTTP Binding Stopped');
248✔
753
            callback();
248✔
754
        });
755
    } else {
756
        callback();
×
757
    }
758
}
759

760
function sendPushNotifications(device, values, callback) {
761
    const executions = _.flatten(values.map(commandHandler.generateCommandExecution.bind(null, null, device)));
×
762

763
    async.series(executions, function (error) {
×
764
        callback(error);
×
765
    });
766
}
767

768
function storePollNotifications(device, values, callback) {
769
    function addPollNotification(item, innerCallback) {
770
        iotAgentLib.addCommand(device.service, device.subservice, device.id, item, innerCallback);
×
771
    }
772

773
    async.map(values, addPollNotification, callback);
×
774
}
775

776
function notificationHandler(device, values, callback) {
777
    if (device.endpoint) {
×
778
        sendPushNotifications(device, values, callback);
×
779
    } else {
780
        storePollNotifications(device, values, callback);
×
781
    }
782
}
783

784
exports.start = start;
1✔
785
exports.stop = stop;
1✔
786
exports.sendConfigurationToDevice = sendConfigurationToDevice;
1✔
787
exports.deviceProvisioningHandler = deviceProvisioningHandler;
1✔
788
exports.deviceUpdatingHandler = deviceUpdatingHandler;
1✔
789
exports.notificationHandler = notificationHandler;
1✔
790
exports.executeCommand = executeCommand;
1✔
791
exports.protocol = 'HTTP';
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc