diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6afb03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Snapcraft build artefacts +parts/ +prime/ +stage/ +*.snap + +# Also ignore node modules installed +node_modules/ + +# Ignore vim files +*.swp diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ea21ec1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:8.1.2 + +ARG http_proxy +ARG https_proxy + +ENV http_proxy ${http_proxy} +ENV https_proxy ${https_proxy} + +# Create app directory +RUN mkdir -p /opt/iot-rest-api-server +WORKDIR /opt/iot-rest-api-server + +# Install IoTivity build dependencies +RUN apt-get update +RUN apt-get install -y \ + libboost-dev libboost-program-options-dev libboost-thread-dev \ + uuid-dev libexpat1-dev libglib2.0-dev libsqlite3-dev \ + libcurl4-gnutls-dev scons + +COPY package.json /opt/iot-rest-api-server +RUN npm --production install + +# Unset proxy +ENV http_proxy "" +ENV https_proxy "" + +# Bundle app source +COPY . /opt/iot-rest-api-server + +EXPOSE 8000 + +CMD ["npm", "start"] diff --git a/README.md b/README.md index 385dcd4..562425d 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,148 @@ # IoT REST API Server + ## Description -This project provides node.js based REST API server accoring to the OIC (Open Interconnect) core specification. +This project provides node.js based REST API server according to the OIC (Open +Interconnect) core specification. -The project is experimental at the moment and APIs provided are work in progress and subject to changes. +The project is experimental at the moment and APIs provided are work in +progress and subject to changes. ![Overview](img/iot-rest-api-server.png) -## Install +## Global Install ``` -npm install node-gyp -g -git clone https://github.com/01org/iot-rest-api-server.git -cd iot-rest-api-server -npm install +npm install -g iot-rest-api-server +``` + +Run with: `$ iot-rest-api-server`. + +## Local Install + ``` +npm install iot-rest-api-server +``` + +> Must have a `package.json` file in your current directory. Also you may want +> to add the `--save` flag to `npm install` so that `package.json` knows your +> project requires `iot-rest-api-server`. +Run with: `$ ./node_modules/iot-rest-api-server/bin/iot-rest-api-server.js`. ## Usage Start the API server -```node index.js``` +`$ iot-rest-api-server` ### Command Line Options -The command line options +The command line options ``` -node index.js -h +$ iot-rest-api-server -h Options - -h, --help - -v, --verbose - -p, --port number - -s, --https - ``` + -h, --help + -v, --verbose + -p, --port number + -s, --https +``` + #### verbose More verbose logging -#### port -Listen to specifc port. Default is 8000 + +#### port +Listen to specific port. Default is 8000 + #### https -Use https with TLS instead of plain http. In order to use https the `config` directory needs to contain the following certificate and private key files (in PEM format) +Use https with TLS instead of plain http. In order to use https the `config` +directory needs to contain the following certificate and private key files (in +PEM format) ``` certificate.pem private.key ``` -You can use the `config/generate-key-and-cert.sh` to generate the files for testing only purposes. The certificate is self signed and browsers do not regognise it so you will get warnings. +You can use the `config/generate-key-and-cert.sh` to generate the files for +testing only purposes. The certificate is self signed and browsers do not +recognise it so you will get warnings. -The recomemnded way to use the https is to get proper certificate from know certificate authority and corresponding private key and place those to the `config` directory. +The recommended way to use the https is to get proper certificate from know +certificate authority and corresponding private key and place those to the +`config` directory. ## API End Points -/api/system +- `/api/system` +- `/api/oic` -/api/oic +# Hacking + +## Install Dev Dependencies + +``` +npm install node-gyp -g +``` ## API documentation -The REST APIs are documented in the /doc folder using the [RAML](http://raml.org/) modeling language. You also need the ```raml2html```node module to produce the documentation: +The REST APIs are documented in the [doc](./doc/) folder using the +[RAML](http://raml.org/) modeling language. You also need the `raml2html` node +module to produce the documentation: -```npm install -g raml2html``` +``` +npm install -g raml2html +``` -The API documentation can be generated +The API documentation can be generated with -```raml2html doc/name-of-the-raml-file > api.html``` +``` +raml2html doc/name-of-the-raml-file > api.html +``` For example - ```raml2html doc/oic.wk.res.raml > oic-res.html``` +``` +raml2html doc/oic.wk.res.raml > oic-res.html +``` -The ```.html``` file can be then opened by a browser. The ```.html```file contains the full documentation of the REST API including all the REST methods (GET, POST, DELETE, etc) supported, query parameters (like ?id=foo) and the JSON formats in each API. +The `.html` file can be then opened by a browser. The `.html` file contains the +full documentation of the REST API including all the REST methods (GET, POST, +DELETE, etc) supported, query parameters (like ?id=foo) and the JSON formats in +each API. ## Examples -The following examples assumes your IoT OS enabled device runs on IP address: 192.168.0.1 +The following examples assumes the iot-rest-api-server runs on IP address: +192.168.0.1, port 8000. Get the system status: +``` http://192.168.0.1:8000/api/system +``` Discover all the OIC enabled devices on the local network: +``` http://192.168.0.1:8000/api/oic/res +``` See the more detailed API documentation in the chapter above. ## Tests -The [test](https://github.com/01org/iot-rest-api-server/tree/master/test) directory contains the following small test utilities: +The [test](./test/) directory contains the following small test utilities: ### oic-get -Send HTTP GET to ```/api/oic```endpoint. Environment variables API_SERVER_HOST and API_SERVER_PORT are used to construct the authority part of the URL. +Send HTTP GET to `/api/oic` endpoint. Environment variables `API_SERVER_HOST` +and `API_SERVER_PORT` are used to construct the authority part of the URL. -```` +``` export API_SERVER_HOST=10.211.55.3 # Discover ./test/oic-get /res @@ -106,26 +152,33 @@ export API_SERVER_HOST=10.211.55.3 ./test/oic-get ?di=&obs=1 ``` -### oic-put +### oic-post -Send HTTP PUT to ```/api/oic```endpoint. Environment variables API_SERVER_HOST and API_SERVER_PORT are used to construct the authority part of the URL. First parameter is ``ùri``` from the discover (```/res```) and second is JASON files with the properties that are being set. +Send HTTP POST to `/api/oic` endpoint. Environment variables `API_SERVER_HOST` +and `API_SERVER_PORT` are used to construct the authority part of the URL. +First parameter is `uri` from the discover (`/res`) and second is JSON files +with the properties that are being set. -```` +``` API_SERVER_HOST=10.211.55.3 -./test/oic-put ?di= +./test/oic-post ?di= ``` ### oic-api-tester -Performs the OIC discovery. On discovered resources performs a) GET or b) OBSERVE operations +Performs the OIC discovery. On discovered resources performs a) GET or b) +OBSERVE operations ``` ./test/oic-api-tester.js -? -./test/oic-api-tester.js -h 10.0.0.1 -p 8000 -o # start observing all found resources +# Start observing all found resources +./test/oic-api-tester.js -h 10.0.0.1 -p 8000 -o ``` ## Tips -If you are running Chrome and want to see the JSON objects in nicely formated way, install the JSONView extension. +If you are running Chrome and want to see the JSON objects in nicely formatted +way, install the JSONView extension. -Another great tool for REST API development and testing is Postman, another Chrome extension. +Another great tool for REST API development and testing is Postman, another +Chrome extension. diff --git a/appfw/appfw.js b/appfw/appfw.js deleted file mode 100644 index baa8205..0000000 --- a/appfw/appfw.js +++ /dev/null @@ -1,65 +0,0 @@ -var app_fw = require('iot/iot-appfw'); -var child_process = require('child_process'); - -var launcher = "iot-launch "; -var installer = " "; //TODO: Set the correct name when installer is ready. - -exports.listApps = function(all, callback) { - if (all) - app_fw.ListAllApplications(callback); - else - app_fw.ListRunningApplications(callback); -} - -exports.startApp = function(appId, args, callback) { - child_process.exec(launcher + appId, callback); -} - -exports.stopApp = function(appId, callback) { - child_process.exec(launcher + "--stop " + appId, callback); -} - -exports.updateApp = function(appId, callback) { - // TODO: Fix when installer is ready. - //child_process.exec(installer + appId, callback); -} - -exports.reinstallApp = function(appId, callback) { - // TODO: Fix when installer is ready. - //child_process.exec(installer + appId, callback); -} - -exports.uninstallApp = function(appId, callback) { - // TODO: Fix when installer is ready. - //child_process.exec(installer + appId, callback); -} - -exports.extractAppInfo = function(apps, status, allApps, appId) { - var result = []; - for (var i = 0; i < apps.length; i++) { - var o = {}; - - if (typeof apps[i].appid != "undefined") - o.app = apps[i].appid; - - if (typeof apps[i].description != "undefined") - o.description = apps[i].description; - - if (typeof apps[i].desktop != "undefined") - o.desktop = apps[i].desktop; - - if (typeof apps[i].user != "undefined") - o.user = apps[i].user; - - if (typeof apps[i].argv != "undefined") - o.argv = apps[i].argv; - - if (allApps) { - result.push(o); - } else if (appId == apps[i].appid) { - result.push(o); - break; - } - } - return result; -} diff --git a/bin/iot-rest-api-server.js b/bin/iot-rest-api-server.js new file mode 100755 index 0000000..d087f7a --- /dev/null +++ b/bin/iot-rest-api-server.js @@ -0,0 +1,86 @@ +#!/usr/bin/env node +var path = require('path'); +var rest; +try { + rest = require('iot-rest-api-server'); +} catch (err) { + rest = require('../index.js'); +} +var fs = null; +var args = process.argv.slice(2); +var options = { + help: false, + verbose: false, + https: false, + port: 8000, + cors: false +}; + +const usage = "usage: iot-rest-api-server [options]\n" + +"options: \n" + +" -h, --help \n" + +" -v, --verbose \n" + +" -p, --port \n" + +" -s, --https \n" + +" -c, --cors \n"; + +for (var i = 0; i < args.length; i++) { + var arg = args[i]; + + switch(arg) { + case '-h': + case '--help': + options.help = true; + break; + case '-v': + case '--verbose': + options.verbose = true; + break; + case '-s': + case '--https': + options.https = true; + break; + case '-p': + case '--port': + var num = args[i + 1]; + if (typeof num == 'undefined') { + console.log(usage); + process.exit(0); + } + options.port = parseInt(num); + break; + case '-c': + case '--cors': + options.cors = true; + break; + } +} + +if (options.help == true) { + console.log(usage); + process.exit(0); +} + +if (Number.isInteger(options.port) == false) { + console.log(usage); + process.exit(0); +} + +var httpsOptions = {key: null, cert: null}; +if (options.https == true) { + fs = require('fs'); + httpsOptions.key = fs.readFileSync(path.join(__dirname, 'config', 'private.key')); + httpsOptions.cert = fs.readFileSync(path.join(__dirname, 'config', 'certificate.pem')); +} + +rest.server.use("/api/oic", rest.ocf); +rest.server.use("/api/system", rest.system); + +// systemd socket activation support +if (process.env.LISTEN_FDS) { + // The first passed file descriptor is fd 3 + var fdStart = 3; + options.port = {fd: fdStart}; +} + +rest.server.start(options, httpsOptions); diff --git a/doc/oic.web-link.json b/doc/oic.web-link.json index a9423c0..e10e242 100644 --- a/doc/oic.web-link.json +++ b/doc/oic.web-link.json @@ -21,6 +21,22 @@ "type": "string", "description": "Interface - The interfaces supported by the resource referenced by the target URI" }, + "p": { + "readOnly": true, + "description": "JSON object containing a Bitmap indicating observable and discoverable plus security and port", + "type": "object", + "properties": { + "bm": { + "type": "integer" + }, + "sec": { + "type": "boolean" + }, + "port": { + "type": "integer" + } + } + }, "obs": { "type": "boolean", "description": "Specifies if the resource referenced by the target URIis observable or not", diff --git a/handlers/ocfHandler.js b/handlers/ocfHandler.js new file mode 100644 index 0000000..d0a3b3b --- /dev/null +++ b/handlers/ocfHandler.js @@ -0,0 +1,280 @@ +var OIC = require('../oic/oic'); +var device = require('iotivity-node'); + +const RESOURCE_FOUND_EVENT = "resourcefound"; +const RESOURCE_UPDATE_EVENT = "update"; +const RESOURCE_DELETE_EVENT = "delete"; +const RESOURCE_ERROR_EVENT = "error"; +const DEVICE_FOUND_EVENT = "devicefound"; +const PLATFORM_FOUND_EVENT = "platformfound"; + +const timeoutValue = 5000; // 5s +const timeoutStatusCode = 504; // Gateway Timeout +const socketTimeoutValue = 0; // No timeout + +const okStatusCode = 200; // All right +const noContentStatusCode = 204; // No content +const internalErrorStatusCode = 500; // Internal error +const badRequestStatusCode = 400; // Bad request +const notFoundStatusCode = 404; // Not found + +device.device = Object.assign(device.device, { + name: 'IoT REST API Server', + coreSpecVersion: 'ocf.1.1.0', + dataModels: ["ocf.res.1.1.0", "ocf.sh.1.1.0"] +}); + +device.platform = Object.assign(device.platform, { + manufacturerName: 'Intel', + manufactureDate: new Date('Tue Sep 19 14:41:28 (EET) 2017'), + platformVersion: '1.1.0' +}); + +var DEV = device.client; + +// Error handler +function errorHandler(error) { + console.log("OCF server responded with error", error.message); +} + +DEV.on("error", errorHandler); + +var routes = function(req, res) { + var discoveredResources = []; + var discoveredDevices = []; + var discoveredPlatforms = []; + var index; + + if (req.path == '/res') + discoverResources(req, res); + else if (req.path == '/d') + discoverDevices(req, res); + else if (req.path == '/p') + discoverPlatforms(req, res); + else { + if (req.method == "GET") + handleResourceGet(req, res); + else if (req.method == "POST") + handleResourcePost(req, res); + else { + res.writeHead(badRequestStatusCode, {'Content-Type':'text/plain'}); + res.end("Unsupported method: " + req.method); + } + } + + function onResourceFound(resourceInfo) { + var resource = OIC.parseRes(resourceInfo); + // Do not add resource to the list, if we have already seen it. + for (index in discoveredResources) { + if (JSON.stringify(resource) === + JSON.stringify(discoveredResources[index])) + return; + } + discoveredResources.push(resource); + } + + function onDeviceFound(deviceInfo) { + // Do not add device to the list, if we have already seen it. + for (index in discoveredDevices) { + if (deviceInfo.uuid === discoveredDevices[index].di) + return; + } + var device = OIC.parseDevice(deviceInfo); + discoveredDevices.push(device); + } + + function onPlatformFound(platformInfo) { + var platform = OIC.parsePlatform(platformInfo); + discoveredPlatforms.push(platform); + } + + function notSupported(req, res) { + res.writeHead(internalErrorStatusCode, {'Content-Type':'text/plain'}) + res.end("Not supported operation: " + req.method + " " + req.path); + } + + function discoverResources(req, res) { + console.log("discoverResources"); + res.setTimeout(timeoutValue, function() { + DEV.removeListener(RESOURCE_FOUND_EVENT, onResourceFound); + res.writeHead(okStatusCode, 'Content-Type', 'application/json'); + res.end(JSON.stringify(discoveredResources)); + }); + + res.on('close', function() { + console.log("Client: close"); + DEV.removeListener(RESOURCE_FOUND_EVENT, onResourceFound); + }); + + console.log("%s %s", req.method, req.url); + + discoveredResources.length = 0; + DEV.on(RESOURCE_FOUND_EVENT, onResourceFound); + + var options = {}; + if (typeof req.query.di != "undefined") + options.deviceId = req.query.di; + + if (typeof req.query.rt != "undefined") + options.resourceType = req.query.rt; + + console.log("Discovering resources for %d seconds.", timeoutValue/1000); + DEV.findResources(options).then(function() { + // TODO: should we send in-progress back to http-client + console.log("findResources() successful"); + }) + .catch(function(e) { + res.writeHead(internalErrorStatusCode, {'Content-Type':'text/plain'}) + res.end("Error: " + e.message); + }); + } + + function discoverDevices(req, res) { + res.setTimeout(timeoutValue, function() { + DEV.removeListener(DEVICE_FOUND_EVENT, onDeviceFound); + res.writeHead(okStatusCode, {'Content-Type': 'application/json'}); + res.end(JSON.stringify(discoveredDevices)); + }); + + res.on('close', function() { + console.log("Client: close"); + DEV.removeListener(DEVICE_FOUND_EVENT, onDeviceFound); + }); + + console.log("%s %s", req.method, req.url); + + discoveredDevices.length = 0; + DEV.on(DEVICE_FOUND_EVENT, onDeviceFound); + + console.log("Discovering devices for %d seconds.", timeoutValue/1000); + DEV.findDevices().then(function() { + // TODO: should we send in-progress back to http-client + console.log("findDevices() successful"); + }) + .catch(function(e) { + res.writeHead(internalErrorStatusCode, {'Content-Type':'text/plain'}) + res.end("Error: " + e.message); + }); + } + + function discoverPlatforms(req, res) { + res.setTimeout(timeoutValue, function() { + DEV.removeListener(PLATFORM_FOUND_EVENT, onPlatformFound); + res.writeHead(okStatusCode, {'Content-Type': 'application/json'}); + res.end(JSON.stringify(discoveredPlatforms)); + }); + + res.on('close', function() { + console.log("Client: close"); + DEV.removeListener(PLATFORM_FOUND_EVENT, onPlatformFound); + }); + + console.log("%s %s", req.method, req.url); + + discoveredPlatforms.length = 0; + DEV.on(PLATFORM_FOUND_EVENT, onPlatformFound); + + console.log("Discovering platforms for %d seconds.", timeoutValue/1000); + DEV.findPlatforms().then(function() { + console.log("findPlatforms() successful"); + }) + .catch(function(e) { + res.writeHead(internalErrorStatusCode, {'Content-Type':'text/plain'}) + res.end("Error: " + e.message); + }); + } + + function handleResourceGet(req, res) { + + if (typeof req.query.di == "undefined") { + res.writeHead(badRequestStatusCode, {'Content-Type':'text/plain'}) + res.end("Query parameter \"di\" is missing."); + return; + } + console.log("%s %s (fd: %d)", req.method, req.url, req.socket._handle.fd); + + function observer(resource) { + var fd = (res.socket._handle == null) ? -1 : res.socket._handle.fd; + console.log("obs: %d, fin: %s, di: %s, fd: %d",req.query.obs, res.finished, req.query.di, fd); + if (req.query.obs == true && res.finished == false) { + var json = OIC.parseResource(resource); + res.write(json); + } else { + resource.removeListener(RESOURCE_UPDATE_EVENT, observer); + resource.removeListener(RESOURCE_DELETE_EVENT, deleteHandler); + resource.removeListener(RESOURCE_ERROR_EVENT, errorHandler); + } + } + + function deleteHandler(resource) { + console.log("Resource %s has been deleted", req.url); + if (req.query.obs == true && res.finished == false) { + res.end(); + } + } + + DEV.retrieve({deviceId: req.query.di, resourcePath: req.path}, req.query).then( + function(resource) { + if (req.query.obs != "undefined" && req.query.obs == true) { + req.on('close', function() { + console.log("Client: close"); + resource.removeListener(RESOURCE_UPDATE_EVENT, observer); + resource.removeListener(RESOURCE_DELETE_EVENT, deleteHandler); + resource.removeListener(RESOURCE_ERROR_EVENT, errorHandler); + req.query.obs = false; + }); + res.writeHead(okStatusCode, {'Content-Type':'application/json'}); + req.setTimeout(socketTimeoutValue); + resource.on(RESOURCE_UPDATE_EVENT, observer); + resource.on(RESOURCE_DELETE_EVENT, deleteHandler); + resource.on(RESOURCE_ERROR_EVENT, errorHandler); + } else { + var json = OIC.parseResource(resource); + res.writeHead(okStatusCode, {'Content-Type':'application/json'}); + res.end(json); + } + }, + function(error) { + res.writeHead(notFoundStatusCode, {'Content-Type':'text/plain'}) + res.end("Resource retrieve failed: " + error.message); + } + ); + } + + function handleResourcePost(req, res) { + if (typeof req.query.di == "undefined") { + res.writeHead(badRequestStatusCode, {'Content-Type':'text/plain'}) + res.end("Query parameter \"di\" is missing."); + return; + } + + res.setTimeout(timeoutValue, function() { + res.writeHead(notFoundStatusCode, {'Content-Type':'text/plain'}) + res.end("Resource not found."); + }); + + var body = []; + req.on('data', function(chunk) { + body.push(chunk); + }).on('end', function() { + body = Buffer.concat(body).toString(); + var resource = { + deviceId: req.query.di, + resourcePath: req.path, + properties: JSON.parse(body) + }; + console.log("POST %s: %s", req.originalUrl, JSON.stringify(resource)); + DEV.update(resource).then( + function() { + res.statusCode = noContentStatusCode; + res.end(); + }, + function(error) { + res.writeHead(notFoundStatusCode, {'Content-Type':'text/plain'}) + res.end("Resource update failed: " + error.message); + } + ); + }); + } +} +module.exports = routes; diff --git a/handlers/systemHandler.js b/handlers/systemHandler.js new file mode 100644 index 0000000..f0c695f --- /dev/null +++ b/handlers/systemHandler.js @@ -0,0 +1,27 @@ +var os = require('os'); + +var routes = function(req, res) { + info(req, res); + + function info(req, res) { + console.log("sysHandler"); + var system = {}; + + system.hostname = os.hostname(); + system.type = os.type(); + system.platform = os.platform(); + system.arch = os.arch(); + system.release = os.release(); + system.uptime = os.uptime(); + system.loadavg = os.loadavg(); + system.totalmem = os.totalmem(); + system.freemem = os.freemem(); + system.cpus = os.cpus(); + system.networkinterfaces = os.networkInterfaces(); + + res.writeHead(200, {"Content-Type": "application/json"}); + res.end(JSON.stringify(system)); + } + +} +module.exports = routes; diff --git a/index.js b/index.js index c7f9857..d99f2f3 100644 --- a/index.js +++ b/index.js @@ -1,88 +1,5 @@ -var express = require('express'); -var path = require('path'); -var bodyParser = require('body-parser'); -var util = require('util'); -var systemd = require('systemd'); -var device = require('iotivity-node')(); -var http, https, fs = null; -var argv = require('minimist')(process.argv.slice(2)); - -const usage = "usage: node index.js [options]\n" + -"options: \n" + -" -h, --help \n" + -" -v, --verbose \n" + -" -p, --port \n" + -" -s, --https \n"; - -if (argv.h == true || argv.help == true) { - console.log(usage); - return; -} - -var port = 8000; /* default port */ -if (typeof argv.p != "undefined") - port = argv.p; -else if (typeof argv.port != "undefined") - port = argv.port; - -if (Number.isInteger(port) == false) { - console.log(usage); - return; -} - -var appfw = ""; -try { - appfw = require('./appfw/appfw'); -} -catch (e) { - if (argv.v == true || argv.verbose == true) - console.log("No AppFW module: " + e.message); -} - -if (argv.s == true || argv.https == true) { - fs = require('fs'); - https = require('https'); - - var httpsOptions = { - key: fs.readFileSync(path.join(__dirname, 'config', 'private.key')), - cert: fs.readFileSync(path.join(__dirname, 'config', 'certificate.pem')) - }; -} -else { - http = require('http'); -} - -var app = express(); - -// Allow cross origin requests -app.use(function(req, res, next) { - res.header('Access-Control-Allow-Origin', '*'); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); - next(); -}); - -app.use(bodyParser.urlencoded({extended:true})); -app.use(bodyParser.json()); - -appRouter = require('./routes/appRoutes')(appfw); -app.use('/api/apps', appRouter); - -installRouter = require('./routes/installRoutes')(appfw); -app.use('/api/install', installRouter); - -systemRouter = require('./routes/systemRoutes')(); -app.use('/api/system', systemRouter); - -oicRouter = require('./routes/oicRoutes')(device); -app.use('/api/oic', oicRouter); - -port = process.env.LISTEN_PID > 0 ? 'systemd' : port; - -if (argv.s == true || argv.https == true) { - https.createServer(httpsOptions, app).listen(port); - console.log('Running on https PORT: ' + port); -} -else { - http.createServer(app).listen(port); - console.log('Running on PORT: ' + port); -} +module.exports = { + 'ocf': require("./handlers/ocfHandler"), + 'system': require("./handlers/systemHandler"), + 'server': require("./server.js") +}; diff --git a/oic/oic.js b/oic/oic.js index add44a2..6b7c668 100644 --- a/oic/oic.js +++ b/oic/oic.js @@ -1,23 +1,34 @@ -exports.parseRes = function(payload) { - console.log(payload); +exports.parseRes = function(resource) { + console.log(resource); - var resource = payload.resource; var o = {}; // resource object according to the OIC core spec. var link = {}; var links = []; + var p = {}; // Policy Parameter. + + if (typeof resource.deviceId != "undefined") + o.di = resource.deviceId; + + if (typeof resource.resourcePath != "undefined") + link.href = resource.resourcePath; + + if (typeof resource.resourceTypes != "undefined") + link.rt = resource.resourceTypes; - if (typeof resource.id.deviceId != "undefined") - o.di = resource.id.deviceId; + if (typeof resource.interfaces != "undefined") + link.if = resource.interfaces; - if (typeof resource.id.path != "undefined") - link.href = resource.id.path; + if (resource.discoverable || resource.observable) { + p.bm = (0 | (resource.discoverable ? 1 << 0 : 0) | + (resource.observable ? 1 << 1 : 0)); + } - // TODO: collect all ... - if (typeof resource.resourceTypes[0] != "undefined") - link.rt = resource.resourceTypes[0]; + p.secure = resource.secure; - if (typeof resource.interfaces[0] != "undefined") - link.if = resource.interfaces[0]; + if (typeof resource.port != "undefined") + p.port = resource.port; + + link.p = p; links.push(link); o.links = links; @@ -27,10 +38,9 @@ exports.parseRes = function(payload) { return o; } -exports.parseDevice = function(payload) { - console.log(payload); +exports.parseDevice = function(device) { + console.log(device); - var device = payload.device; var o = {}; if (typeof device.uuid != "undefined") @@ -42,22 +52,24 @@ exports.parseDevice = function(payload) { if (typeof device.coreSpecVersion != "undefined") o.icv = device.coreSpecVersion; - if (typeof device.dataModelVersion != "undefined") - o.dmv = device.dataModelVersion; + if (typeof device.dataModels != "undefined") + o.dmv = device.dataModels; + + if (typeof device.piid != "undefined") + o.piid = device.piid; console.log(JSON.stringify(o)); return o; } -exports.parseP = function(payload) { +exports.parsePlatform = function(info) { var o = {}; - console.log(payload); - var info = payload.info; + console.log(info); - if (typeof info.platformID != "undefined") - o.pi = info.platformID; + if (typeof info.id != "undefined") + o.pi = info.id; if (typeof info.manufacturerName != "undefined") o.mnmn = info.manufacturerName; @@ -65,17 +77,17 @@ exports.parseP = function(payload) { if (typeof info.manufacturerUrl != "undefined") o.mnml = info.manufacturerUrl; - if (typeof info.modelNumber != "undefined") - o.mnmo = info.modelNumber; + if (typeof info.model != "undefined") + o.mnmo = info.model; - if (typeof info.dateOfManufacture != "undefined") - o.mndt = info.dateOfManufacture; + if (typeof info.manufactureDate != "undefined") + o.mndt = info.manufactureDate; if (typeof info.platformVersion != "undefined") o.mnpv = info.platformVersion; - if (typeof info.operatingSystemVersion != "undefined") - o.mnos = info.operatingSystemVersion; + if (typeof info.osVersion != "undefined") + o.mnos = info.osVersion; if (typeof info.hardwareVersion != "undefined") o.mnhw = info.hardwareVersion; @@ -89,21 +101,14 @@ exports.parseP = function(payload) { if (typeof info.systemTime != "undefined") o.st = info.systemTime; - var json = JSON.stringify(o); - console.log(json); - - return json; -} + console.log(JSON.stringify(o)); -exports.parseIP = function(addr) { - var result = ""; - for (var i=0; i < addr.length && addr[i] != 0; i++) - result += String.fromCharCode(addr[i]); - return result; + return o; } exports.parseResource = function(payload) { var o = {}; + var index, property; console.log(payload); @@ -111,7 +116,10 @@ exports.parseResource = function(payload) { o.href = payload.uri; if (typeof payload.properties != "undefined") - o.properties = payload.properties; + for (index in payload.properties) { + value = payload.properties[index]; + o[index] = value; + } var json = JSON.stringify(o); diff --git a/package.json b/package.json index e550ad2..23fdbae 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { "name": "iot-rest-api-server", - "version": "0.0.1", + "version": "0.6.0", "description": "REST API Server for IoT", - "repository": "https://github.com:01org/iot-rest-api-server", + "repository": "https://github.com/01org/iot-rest-api-server", "main": "index.js", "scripts": { + "start": "bin/iot-rest-api-server.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Sakari Poussa", "license": "Apache-2.0", "dependencies": { - "body-parser": "^1.12.4", - "express": "^4.12.3", - "iotivity-node": "^1.0.1-1", - "minimist": "^1.2.0", - "systemd": "^0.2.6" + "iotivity-node": "^1.3.0-2" + }, + "bin": { + "iot-rest-api-server": "bin/iot-rest-api-server.js" }, "devDependencies": { "gulp": "^3.8.11", diff --git a/router.js b/router.js new file mode 100644 index 0000000..5ef2712 --- /dev/null +++ b/router.js @@ -0,0 +1,29 @@ +var url = require('url'); + +function route(routes, req, res) { + var info = url.parse(req.url, true); + var handler = null; + + for (var key in routes) { + if (info.pathname.startsWith(key)) { + handler = routes[key]; + + // Attach express style properties to req + req.path = info.pathname.substr(key.length); + req.originalUrl = info.href; + req.query = info.query; + break; + } + } + + if (typeof handler === 'function') + handler(req, res); + else { + var msg = "No request handler for " + info.pathname; + console.log(msg); + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end(msg); + } +} + +exports.route = route; diff --git a/routes/appRoutes.js b/routes/appRoutes.js deleted file mode 100644 index e93d802..0000000 --- a/routes/appRoutes.js +++ /dev/null @@ -1,212 +0,0 @@ -var express = require('express'); - -var routes = function(AppFW) { - var router = express.Router(); - - var timeoutValue = 5000; // 5s - var okStatusCode = 200; // OK - var errorStatusCode = 500; // Error - - function sendResponse(res, result) { - if (res.finished == false) { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(result)); - } - } - - router.route('/') - .get(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appList; - if (status != 0) { - error = "Server failed to process the application request."; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - - if (appList.length == 0) - error = "Got list of 0 applications running."; - } - - if (error) - res.status(errorStatusCode).send({ error: error}); - else - sendResponse(res, appList); - } - AppFW.listApps(false, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .post(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0, appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to restart.'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to start one or more applications.'}); - } - - for (var index = 0 ; index < appCount ; index++) { - AppFW.startApp(appList[index]["app"], appList[index]["argv"], ps_callback); - } - } - } - AppFW.listApps(true, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .delete(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0, appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to restart'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - var result; - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to stop one or more applications.'}); - } - for (var index = 0 ; index < appCount ; index++) { - AppFW.stopApp(appList[index]["app"], ps_callback); - } - } - } - AppFW.listApps(false, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .put(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0,appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to restart'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to restart one or more applications.'}); - } - - for (var index = 0 ; index < appCount ; index++) { - AppFW.stopApp(appList[index]["app"], ps_callback); - AppFW.startApp(appList[index]["app"], appList[index]["argv"], ps_callback); - } - } - } - AppFW.listApps(false, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }); - - router.use('/:appId', function(req, res, next) { - var callback = function(id, status, msg, apps) { - if (status != 0) { - req.error = 'Server failed to process the application request.'; - } else { - req.app = AppFW.extractAppInfo(apps, status, false, req.params.appId); - if (req.app.length == 0) { - req.error = 'Got list of 0 applications to process the request.'; - } - } - - if (req.error) - res.status(errorStatusCode).send({ error: req.error }); - else - next(); - } - if (req.method == "POST") - AppFW.listApps(true, callback); - else - AppFW.listApps(false, callback); - }); - - router.route('/:appId') - .get(function(req, res) { - sendResponse(res, req.app); - }) - .post(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - if (err) - res.status(errorStatusCode).send({ error: 'Failed to start the application.'}); - else - res.status(okStatusCode).end(); - } - AppFW.startApp(req.app[0]["app"], req.app[0]["argv"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .delete(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - var result; - - if (err) - res.status(errorStatusCode).send({ error: 'Failed to stop the application.'}); - else - res.status(okStatusCode).end(); - } - AppFW.stopApp(req.app[0]["app"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .put(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to restart the application.'}); - } - AppFW.stopApp(req.app[0]["app"], ps_callback); - AppFW.startApp(req.app[0]["app"], req.app[0]["argv"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }); - - return router; -}; - -module.exports = routes; diff --git a/routes/installRoutes.js b/routes/installRoutes.js deleted file mode 100644 index e30a271..0000000 --- a/routes/installRoutes.js +++ /dev/null @@ -1,207 +0,0 @@ -var express = require('express'); - -var routes = function(AppFW) { - var router = express.Router(); - - var timeoutValue = 5000; // 5s - var okStatusCode = 200; // OK - var errorStatusCode = 500; // Error - - function sendResponse(res, result) { - if (res.finished == false) { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(result)); - } - } - - router.route('/') - .get(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appList; - if (status != 0) { - error = "Server failed to process the application request."; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - - if (appList.length == 0) - error = "Got list of 0 applications installed."; - } - - if (error) - res.status(errorStatusCode).send({ error: error}); - else - sendResponse(res, appList); - } - AppFW.listApps(true, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .post(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0, appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to update.'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to update one or more applications.'}); - } - - for (var index = 0 ; index < appCount ; index++) { - AppFW.updateApp(appList[index]["app"], ps_callback); - } - } - } - AppFW.listApps(true, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .delete(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0, appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to uninstall.'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - var result; - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to uninstall one or more applications.'}); - } - for (var index = 0 ; index < appCount ; index++) { - AppFW.uninstallApp(appList[index]["app"], ps_callback); - } - } - } - AppFW.listApps(true, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .put(function(req, res) { - var callback = function(id, status, msg, apps) { - var error = null, appCount = 0,appList; - - if (status != 0) { - error = 'Server failed to process the application request.'; - } else { - appList = AppFW.extractAppInfo(apps, status, true, null); - appCount = appList.length ? appList.length : 0; - - if (appCount == 0) - error = 'Got list of 0 applications to re-install.'; - } - - if (error) { - res.status(errorStatusCode).send({ error: error}); - } else { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to re-install one or more applications.'}); - } - - for (var index = 0 ; index < appCount ; index++) { - AppFW.reinstallApp(appList[index]["app"], ps_callback); - } - } - } - AppFW.listApps(true, callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }); - - router.use('/:appId', function(req, res, next) { - var callback = function(id, status, msg, apps) { - if (status != 0) { - req.error = 'Server failed to process the application request.'; - } else { - req.app = AppFW.extractAppInfo(apps, status, false, req.params.appId); - if (req.app.length == 0) { - req.error = 'Got list of 0 applications to process the request.'; - } - } - - if (req.error) - res.status(errorStatusCode).send({ error: req.error }); - else - next(); - } - AppFW.listApps(true, callback); - }); - - router.route('/:appId') - .get(function(req, res) { - sendResponse(res, req.app); - }) - .post(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - if (err) - res.status(errorStatusCode).send({ error: 'Failed to update the application.'}); - else - res.status(okStatusCode).end(); - } - AppFW.updateApp(req.app[0]["app"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .delete(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - var result; - - if (err) - res.status(errorStatusCode).send({ error: 'Failed to uninstall the application.'}); - else - res.status(okStatusCode).end(); - } - AppFW.uninstallApp(req.app[0]["app"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }) - .put(function(req, res) { - var ps_callback = function(err, stdout, stderr) { - if (err && res.finished == false) - res.status(errorStatusCode).send({ error: 'Failed to re-install the application.'}); - } - AppFW.reinstallApp(req.app[0]["app"], ps_callback); - - res.setTimeout(timeoutValue, function() { - res.status(okStatusCode).end(); - }); - }); - - return router; -}; - -module.exports = routes; diff --git a/routes/oicRoutes.js b/routes/oicRoutes.js deleted file mode 100644 index bc84c4c..0000000 --- a/routes/oicRoutes.js +++ /dev/null @@ -1,157 +0,0 @@ -var express = require('express'); -var OIC = require('../oic/oic'); - -const RESOURCE_FOUND_EVENT = "resourcefound"; -const RESOURCE_CHANGE_EVENT = "resourcechange"; -const UPDATE_EVENT = "update"; -const DEVICE_FOUND_EVENT = "devicefound"; - -const timeoutValue = 5000; // 5s -const timeoutStatusCode = 504; // Gateway Timeout - -const noContentStatusCode = 204; // No content -const internalErrorStatusCode = 500; // Internal error -const badRequestStatusCode = 400; // Bad request -const notFoundStatusCode = 404; // Not found - -var routes = function(DEV) { - var router = express.Router(); - var discoveredResources = []; - var discoveredDevices = []; - - DEV.configure({ - role: "client", - connectionMode: "acked" - }); - - function onResourceFound(event) { - var resource = OIC.parseRes(event); - discoveredResources.push(resource); - } - - function onDeviceFound(event) { - var device = OIC.parseDevice(event); - discoveredDevices.push(device); - } - - router.route('/p') - .get(function(req, res) { - res.status(badRequestStatusCode).send("/oic/p not supported."); - }); - - router.route('/d') - .get(function(req, res) { - res.setTimeout(timeoutValue, function() { - DEV.removeEventListener(DEVICE_FOUND_EVENT, onDeviceFound); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(discoveredDevices)); - }); - - console.log("GET %s", req.originalUrl); - - discoveredDevices.length = 0; - DEV.addEventListener(DEVICE_FOUND_EVENT, onDeviceFound); - - console.log("Discovering devices for %d seconds.", timeoutValue/1000); - DEV.findDevices().then(function() { - // TODO: should we send in-progress back to http-client - console.log("findDevices() successful"); - }) - .catch(function(e) { - console.log("Error: " + e.message); - res.status(internalErrorStatusCode).send(e.message); - }); - }); - - router.route('/res') - .get(function(req, res) { - res.setTimeout(timeoutValue, function() { - DEV.removeEventListener(RESOURCE_FOUND_EVENT, onResourceFound); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(discoveredResources)); - }); - - console.log("GET %s", req.originalUrl); - - discoveredResources.length = 0; - DEV.addEventListener(RESOURCE_FOUND_EVENT, onResourceFound); - - console.log("Discovering resources for %d seconds.", timeoutValue/1000); - DEV.findResources().then(function() { - // TODO: should we send in-progress back to http-client - console.log("findResources() successful"); - }) - .catch(function(e) { - console.log(e); - res.status(internalErrorStatusCode).send(e.message); - }); - }); - - router.route('/:resource(([a-zA-Z0-9\.\/\+-]+)(;obs)?)/') - .get(function(req, res) { - - if (typeof req.query.di == "undefined") { - res.status(badRequestStatusCode).send("Query parameter \"id\" is missing."); - return; - } - console.log("GET %s (fd: %d)", req.originalUrl, req.socket._handle.fd); - - function observer(event) { - var fd = (res.socket._handle == null) ? -1 : res.socket._handle.fd; - console.log("obs: %d, fin: %s, id: %s, fd: %d",req.query.obs, res.finished, req.query.di, fd); - if (req.query.obs == true && res.finished == false) { - var json = OIC.parseResource(event.resource); - res.write(json); - } else { - event.resource.removeEventListener(UPDATE_EVENT, observer); - } - } - - DEV.retrieveResource({deviceId: req.query.di, path: req.path}).then( - function(resource) { - if (req.query.obs != "undefined" && req.query.obs == true) { - req.on('close', function() { - console.log("Client: close"); - req.query.obs = false; - }); - resource.addEventListener(UPDATE_EVENT, observer); - } else { - var json = OIC.parseResource(resource); - res.setHeader('Content-Type', 'application/json'); - res.send(json); - } - }, - function(error) { - res.status(notFoundStatusCode).send("Resource retrieve failed: " + error.message); - }); - }) - .put(function(req, res) { - if (typeof req.query.di == "undefined") { - res.status(badRequestStatusCode).send("Query parameter \"id\" is missing."); - return; - } - - res.setTimeout(timeoutValue, function() { - res.status(notFoundStatusCode).send("Resource not found."); - }); - - var payload = {properties: req.body}; - var resource = { - id: {deviceId: req.query.di, path: req.path}, - properties: req.body - }; - console.log("PUT %s: %s", req.originalUrl, JSON.stringify(resource)); - DEV.updateResource(resource).then( - function() { - res.status(noContentStatusCode).send(); - }, - function(error) { - console.log("Error: " + error.message); - res.status(notFoundStatusCode).send("Resource update failed: " + error.message); - }); - }); - - return router; -}; - -module.exports = routes; diff --git a/routes/systemRoutes.js b/routes/systemRoutes.js deleted file mode 100644 index 8166985..0000000 --- a/routes/systemRoutes.js +++ /dev/null @@ -1,28 +0,0 @@ -var express = require('express'); -var os = require('os'); - -var routes = function() { - var router = express.Router(); - - router.route('/') - .get(function(req, res) { - var system = {}; - - system.hostname = os.hostname(); - system.type = os.type(); - system.platform = os.platform(); - system.arch = os.arch(); - system.release = os.release(); - system.uptime = os.uptime(); - system.loadavg = os.loadavg(); - system.totalmem = os.totalmem(); - system.freemem = os.freemem(); - system.cpus = os.cpus(); - system.networkinterfaces = os.networkInterfaces(); - - res.json(system); - }); - return router; -}; - -module.exports = routes; diff --git a/server.js b/server.js new file mode 100644 index 0000000..947fb9e --- /dev/null +++ b/server.js @@ -0,0 +1,34 @@ +var http, https; +var router = require('./router'); +var routes = {}; + +function start(config, options) { + function onRequest(req, res) { + if (config.cors) { + // Allow cross origin requests + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + } + router.route(routes, req, res); + } + if (config.https) { + https = require('https'); + https.createServer(options, onRequest).listen(config.port); + } + else { + http = require('http'); + http.createServer(onRequest).listen(config.port); + } + console.log("API server started on " + new Date().toISOString() + + " [port: " + config.port + ", https: " + config.https + "]"); +} + +function use(pathname, handler) { + if (typeof handler === 'function') + routes[pathname] = handler; + else + console.log("Server: handler is not a function"); +} + +exports.use = use; +exports.start = start; diff --git a/snapcraft.yaml b/snapcraft.yaml new file mode 100644 index 0000000..32560ff --- /dev/null +++ b/snapcraft.yaml @@ -0,0 +1,37 @@ +name: iot-rest-api-server +version: 0.3 +summary: REST API Server for OCF +description: Node.js server for OCF REST APIs and OCF client functionality. +confinement: strict +grade: devel + +apps: + start-iot-rest-api-server: + command: node $SNAP/lib/node_modules/iot-rest-api-server/index.js --port 80 + daemon: simple + plugs: [ network-bind ] + + hciconfig: + command: bin/hciconfig + plugs: [ bluez, bluetooth-control ] + + hcitool: + command: usr/bin/hcitool + plugs: [ bluez, bluetooth-control ] + + rfkill: + command: usr/sbin/rfkill + plugs: [ network-control ] + +parts: + meta-iot-web: + plugin: nodejs + source: https://github.com/01org/iot-rest-api-server.git + source-branch: master + build-packages: [git, scons, libtool, autoconf, valgrind, doxygen, wget, unzip, libboost-dev, libboost-program-options-dev, libboost-thread-dev, uuid-dev, libexpat1-dev, libglib2.0-dev] + + ubuntu: + plugin: nil + stage-packages: + - rfkill # provides /usr/sbin/rfkill + - bluez # provides /bin/hciconfig \ No newline at end of file diff --git a/test/README.md b/test/README.md index 30aa7ce..f814cb1 100644 --- a/test/README.md +++ b/test/README.md @@ -3,68 +3,11 @@ * `API_SERVER_HOST` - Hostname/address where API server is running (default: localhost) -* `API_SERVER_HOST` - Port where API server is running (default: 8000) -* `API_REQUEST_METHOD` - HTTP method (GET/POST/DELETE/PUT) in the request header - (default: GET) +* `API_SERVER_PORT` - Port where API server is running (default: 8000) +* `API_SERVER_HTTPS` - To use https instead of http (default: http) ## Examples -### app-install - -```sh -# List all installed applications (GET /install) -./app-installer GET / - -# List single installed application (GET /install/{appId}) -./app-installer GET '/terminal:x' - -# Update all installed applications (POST /install) -./app-installer POST / - -# Update single application (POST /install/{appId}) -./app-installer POST '/terminal:x' - -# Uninstall all installed applications (DELETE /install) -./app-installer DELETE / - -# Uninstall single installed application (DELETE /install/{appId}) -./app-installer DELETE '/terminal:x' - -# Reinstall all installed applications (POST /install) -./app-installer PUT / - -# Reinstall single installed application (POST /install/{appId}) -./app-installer PUT '/terminal:x' -``` - -### app-launcher - -```sh -# List all running applications (GET /apps) -./app-launcher GET / - -# List single running application (GET /apps/{appId}) -./app-launcher GET '/terminal:x' - -# Start all installed applications (POST /apps) -./app-launcher POST / - -# Start single application (POST /apps/{appId}) -./app-launcher POST '/terminal:x' - -# Stop all running applications (DELETE /apps) -./app-launcher DELETE / - -# Stop single running application (DELETE /apps/{appId}) -./app-launcher DELETE '/terminal:x' - -# Restart all running applications (POST /apps) -./app-launcher PUT / - -# Restart single running application (POST /apps/{appId}) -./app-launcher PUT '/terminal:x' -``` - ### oic-get ```sh @@ -77,19 +20,19 @@ # Platform discovery (/oic/p) ./oic-get "/p" -# Resource get (/a/light) -./oic-get "/a/light" +# Resource get (/a/light?di=) +./oic-get "/a/light?di=" -# Resource get with query filter (/a/light with power less than 50) -./oic-get "/a/light?power<50" +# Resource get with query filter (/a/light?di= with power less than 50) +./oic-get "/a/light?di=?di=&power<50" -# Resource observe (/a/light) -./oic-get "/a/light;obs" +# Resource observe (/a/light?di=&obs=1) +./oic-get "/a/light?di=&obs=1" ``` -### oic-put +### oic-post ```sh -# Resource put (/a/light from a file: put-light-values.txt) -./oic-put "/a/light" +# Resource post (/a/light?di= from a file: post-light-values.txt) +./oic-post "/a/light?di=" ``` diff --git a/test/app-installer b/test/app-installer deleted file mode 100755 index f4bbc94..0000000 --- a/test/app-installer +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -if test "x${API_SERVER_PORT}x" == "xx"; then - API_SERVER_PORT=8000 -fi - -if test "x${API_SERVER_HOST}x" == "xx"; then - API_SERVER_HOST=localhost -fi - -if test "x$1x" == "xx"; then - API_REQUEST_METHOD="" -else - API_REQUEST_METHOD="-X $1" -fi - -if test "x$2x" == "xx"; then - API_SERVER_URL="" -else - API_SERVER_URL=$2 -fi - -echo $API_SERVER_HOST":"$API_SERVER_PORT $API_REQUEST_METHOD $API_SERVER_URL - -curl ${API_REQUEST_METHOD} --noproxy "*" -w "\nHTTP: %{http_code}\n" --no-buffer http://${API_SERVER_HOST}:${API_SERVER_PORT}/api/install${API_SERVER_URL} diff --git a/test/app-launcher b/test/app-launcher deleted file mode 100755 index aba7daa..0000000 --- a/test/app-launcher +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -if test "x${API_SERVER_PORT}x" == "xx"; then - API_SERVER_PORT=8000 -fi - -if test "x${API_SERVER_HOST}x" == "xx"; then - API_SERVER_HOST=localhost -fi - -if test "x$1x" == "xx"; then - API_REQUEST_METHOD="" -else - API_REQUEST_METHOD="-X $1" -fi - -if test "x$2x" == "xx"; then - API_SERVER_URL="" -else - API_SERVER_URL=$2 -fi - -echo $API_SERVER_HOST":"$API_SERVER_PORT $API_REQUEST_METHOD $API_SERVER_URL - -curl ${API_REQUEST_METHOD} --noproxy "*" -w "\nHTTP: %{http_code}\n" --no-buffer http://${API_SERVER_HOST}:${API_SERVER_PORT}/api/apps${API_SERVER_URL} diff --git a/test/oic-api-tester.js b/test/oic-api-tester.js index ca0534d..c2adede 100755 --- a/test/oic-api-tester.js +++ b/test/oic-api-tester.js @@ -4,58 +4,86 @@ var proto = null; var path = require('path'); var fs = require('fs'); var ca = null; -var argv = require('minimist')(process.argv.slice(2)); - +var args = process.argv.slice(2); +var options = { + help: false, + host: "localhost", + port: 8000, + https: false, + obs: false +}; + +const okStatusCode = 200; // All right const usage = "usage: node oic-api-tester.js [options]\n" + "options: \n" + " -?, --help \n" + -" -v, --verbose \n" + " -h, --host \n" + " -p, --port \n" + " -s, --https \n" + " -o, --obs \n"; -if (argv.h == true || argv.help == true) { - console.log(usage); - return; +for (var i = 0; i < args.length; i++) { + var arg = args[i]; + + switch(arg) { + case '-?': + case '--help': + options.help = true; + break; + case '-h': + case '--host': + var host = args[i + 1]; + if (typeof host == 'undefined') { + console.log(usage); + return; + } + options.host = host; + break; + case '-p': + case '--port': + var num = args[i + 1]; + if (typeof num == 'undefined') { + console.log(usage); + return; + } + options.port = parseInt(num); + break; + case '-s': + case '--https': + options.https = true; + break; + case '-o': + case '--obs': + options.obs = true; + break; + } } -var obs = false; -if (argv.o == true || argv.obs == true) - obs = true - -var port = 8000; /* default port */ -if (typeof argv.p != "undefined") - port = argv.p; -else if (typeof argv.port != "undefined") - port = argv.port; - -if (Number.isInteger(port) == false) { - console.log(usage); - return; +if (options.help == true) { + console.log(usage); + return; } -var host = "localhost"; /* default host */ -if (typeof argv.h != "undefined") - host = argv.h; -else if (typeof argv.host != "undefined") - host = argv.host; +if (Number.isInteger(options.port) == false) { + console.log(usage); + return; +} -if (typeof host != "string") { - console.log(usage); - return; +if (typeof options.host !== "string") { + console.log(usage); + return; } -if (argv.s == true || argv.https == true) { - ca = fs.readFileSync(path.join(__dirname, '..', 'config', 'certificate.pem')) - proto = require('https'); +if (options.https == true) { + ca = fs.readFileSync(path.join(__dirname, '..', 'config', 'certificate.pem')); + proto = require('https'); } else proto = require('http'); var reqOptions = { - host: host, - port: port, + host: options.host, + port: options.port, agent: new proto.Agent({keepAlive: true}), headers: {Connection: "keep-alive"}, ca: ca @@ -70,8 +98,12 @@ function findResources(callback) { }); res.on('end', function() { - var resources = JSON.parse(json); - callback(resources); + if (res.statusCode == okStatusCode) { + var resources = JSON.parse(json); + callback(resources); + } else { + console.log(json.toString('utf8') ); + } }); } var req = proto.request(reqOptions, discoveryCallback); @@ -89,13 +121,13 @@ function onResourceFound(resources) { for (var i = 0; i < resources.length; i++) { var uri = resources[i].links[0].href; console.log("%s : %s", resources[i].di, uri); - retrieveResources(uri + "?di=" + resources[i].di, onResource, obs) + retrieveResources(uri + "?di=" + resources[i].di, onResource, options.obs); } } function onResource(resource) { console.log("--- onResource:"); - console.log(resource) + console.log(resource.toString('utf8')); } function retrieveResources(uri, callback, observe) { @@ -107,7 +139,7 @@ function retrieveResources(uri, callback, observe) { resourceCallback = function(res) { res.on('data', function(data) { if (observe) { - callback(JSON.parse(data)); + callback(data); } else { json += data; @@ -116,7 +148,7 @@ function retrieveResources(uri, callback, observe) { res.on('end', function() { if (json) - callback(JSON.parse(json)); + callback(json); }); res.on('abort', function() { diff --git a/test/oic-put b/test/oic-post similarity index 78% rename from test/oic-put rename to test/oic-post index eb87aaa..a701249 100755 --- a/test/oic-put +++ b/test/oic-post @@ -23,7 +23,7 @@ else fi if test "x$2x" == "xx"; then - API_SERVER_FILE="put-light-values.json" + API_SERVER_FILE="post-light-values.json" else API_SERVER_FILE=$2 fi @@ -31,5 +31,5 @@ fi echo $API_SERVER_HOST":"$API_SERVER_PORT $API_SERVER_URL -curl -X PUT --noproxy "*" -w "\nHTTP: %{http_code}\n" -H "Content-Type: application/json" -T ${API_SERVER_FILE} ${CERT} \ +curl -X POST --noproxy "*" -w "\nHTTP: %{http_code}\n" -H "Content-Type: application/json" -T ${API_SERVER_FILE} ${CERT} \ ${API_SERVER_PROTO}://${API_SERVER_HOST}:${API_SERVER_PORT}/api/oic${API_SERVER_URL} diff --git a/test/post-light-values.json b/test/post-light-values.json new file mode 100644 index 0000000..255fa7e --- /dev/null +++ b/test/post-light-values.json @@ -0,0 +1 @@ +{"power": 13, "state": true} diff --git a/test/put-light-values.json b/test/put-light-values.json deleted file mode 100644 index 0cb3e51..0000000 --- a/test/put-light-values.json +++ /dev/null @@ -1 +0,0 @@ -{"power": 13, "state": true} \ No newline at end of file