From 03d71034f84b7477ac8ec7a9d3b5a5f0d490feb5 Mon Sep 17 00:00:00 2001 From: matzman Date: Mon, 1 Jun 2015 09:21:36 +0200 Subject: [PATCH] . --- src/hos_cosgi_devicerepository/CMakeLists.txt | 2 +- .../src/BundleActivator.cpp | 2 +- .../webresource/DeviceRepoHandler.cpp | 97 ++++++++++++++++--- .../webresource/DeviceRepoHandler.h | 56 ++++++++++- src/hos_cosgi_testwebapp/API-Notes.txt | 32 ++++++ .../webroot/DojoApp/dojo/hos/DeviceRepo.js | 32 ++++++ .../webroot/DojoApp/test04.html | 52 ++++++++++ .../webresource/WebResourceHandlerImpl.h | 8 +- .../webserver/WebServerServiceImpl.cpp | 5 +- 9 files changed, 262 insertions(+), 24 deletions(-) rename src/hos_cosgi_devicerepository/src/hos/cosgi/service/{ => devicerepository}/webresource/DeviceRepoHandler.cpp (72%) rename src/hos_cosgi_devicerepository/src/hos/cosgi/service/{ => devicerepository}/webresource/DeviceRepoHandler.h (68%) create mode 100644 src/hos_cosgi_testwebapp/API-Notes.txt create mode 100644 src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js create mode 100644 src/hos_cosgi_testwebapp/webroot/DojoApp/test04.html diff --git a/src/hos_cosgi_devicerepository/CMakeLists.txt b/src/hos_cosgi_devicerepository/CMakeLists.txt index d7d84152..01e80b8c 100644 --- a/src/hos_cosgi_devicerepository/CMakeLists.txt +++ b/src/hos_cosgi_devicerepository/CMakeLists.txt @@ -55,7 +55,7 @@ bundle(hos_cosgi_devicerepository SOURCES ${PROJECT_SOURCE_DIR}/src/BundleActivator ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl - ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webresource/DeviceRepoHandler + ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler ) target_link_libraries(hos_cosgi_devicerepository diff --git a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp index 9783fe02..806ae55e 100644 --- a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp +++ b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp @@ -13,7 +13,7 @@ #include // Services #include "hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.h" -#include "hos/cosgi/service/webresource/DeviceRepoHandler.h" +#include "hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h" using namespace hos::cosgi; diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp similarity index 72% rename from src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp rename to src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp index 44d1de42..8574759a 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // Service Namespace @@ -26,7 +27,11 @@ HOS_SERVICE_IMPL_INFO( DeviceRepoHandlerFactory, // Implementation DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService) - : repoService(repoService) { + : repoService(repoService) { + infoCategoryFuncs.insert({"id", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + return device->getDeviceID(); + } }); + infoCategoryFuncs.insert({"type", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { return device->getDeviceType(); } }); @@ -68,6 +73,9 @@ DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepos } +DeviceAPIMethod DeviceRepoHandlerFactory::getDeviceAPIMethod() noexcept { + +} Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { return new DeviceRepoHandler(matchUri, repoService, infoCategoryFuncs, ssl); @@ -76,9 +84,9 @@ Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::U void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { - // Get method parameter + // Get API method parameter Net::HTMLForm form(req); - DevicesAPIMethod dataMethod = DevicesAPIMethod::GET; // Default method is get + DeviceAPIMethod dataMethod = DeviceAPIMethod::GET; // Default method is get std::string methodValue; if (form.has("method")) { std::string methodRaw = form.get("method"); @@ -86,10 +94,10 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net: if (methodRaw == "get") { // do nothing } else if (methodRaw == "command") { - dataMethod = DevicesAPIMethod::COMMAND; + dataMethod = DeviceAPIMethod::COMMAND; requireValue = true; } else if (methodRaw.compare("statusupdate") == 0) { - dataMethod = DevicesAPIMethod::STATUSUPDATE; + dataMethod = DeviceAPIMethod::STATUSUPDATE; requireValue = true; } else { throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown API method \"%1%\"") % methodRaw).str(), @@ -112,16 +120,61 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net: // No device given if (pathPieces.size() <= matchPieces.size()) { - if (dataMethod != DevicesAPIMethod::GET) { + if (dataMethod != DeviceAPIMethod::GET) { throw webserver::webresource::WebResourceHandlerException("Requested API method requires a device", Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); } - Variant result; - for (auto d : repoService->getDevices()) { - auto dev = d.lock(); - result[dev->getDeviceID()] = getDeviceInfo(dev.get(), req); + + auto devices = repoService->getDevices(); + + // Range header parameter + std::string rangeRaw; + size_t rangeStart = 0; + size_t rangeStop = devices.size() - 1; + if (req.has("Range")) { + rangeRaw = req.get("Range"); + } else if (req.has("X-Range")) { + rangeRaw = req.get("X-Range"); + } + if (!rangeRaw.empty()) { + if (boost::algorithm::starts_with(rangeRaw, "items=")) { + auto range = rangeRaw.substr(6); + std::vector splitParts; + boost::algorithm::split(splitParts, range, boost::algorithm::is_any_of("-")); + if (splitParts.size() == 2) { + try { + size_t r1 = std::stol(splitParts[0]); + size_t r2 = std::stol(splitParts[1]); + if (r1 <= r2) { + rangeStart = r1; + rangeStop = std::min(r2, rangeStop); + } + } catch (...) { + ROS_ERROR_STREAM("Could not parse Range parameter \"" << rangeRaw << "\"."); + } + } + } + } + + // + + Variant result = Variant::List(); + for (size_t t = rangeStart; t <= rangeStop; t++) { + auto dev = devices[t].lock(); + result.Append(getDeviceInfo(dev.get(), req)); + } + if (result.Size() == 0 && rangeStart > 0) { + if (rangeStart > 0) { + handleErrorResponse(req, resp, "Out of range", Poco::Net::HTTPResponse::HTTP_NOT_FOUND); + } else { + handleResponse(req, resp, result); + } + } else { + std::string rangeOut = (boost::format("items %1%-%2%/%3%") % rangeStart % rangeStop % devices.size()).str(); + resp.set("Content-Range", rangeOut); + resp.set("X-Content-Range", rangeOut); + handleResponse(req, resp, result); } - handleResponse(req, resp, result); // Device given } else { @@ -129,7 +182,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net: auto device = repoService->getDevice(devid); if (!device.expired()) { switch (dataMethod) { - case DevicesAPIMethod::GET: + case DeviceAPIMethod::GET: { std::string category; if (pathPieces.size() > matchPieces.size() + 1) { @@ -143,7 +196,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net: handleResponse(req, resp, result); } break; - case DevicesAPIMethod::COMMAND: + case DeviceAPIMethod::COMMAND: { std::vector path; for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) { @@ -153,7 +206,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net: handleResponse(req, resp, std::string("OK: Command send")); } break; - case DevicesAPIMethod::STATUSUPDATE: + case DeviceAPIMethod::STATUSUPDATE: throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method."); default: throw webserver::webresource::WebResourceHandlerException("Internal Server Error"); @@ -230,6 +283,22 @@ void DeviceRepoHandler::sendDeviceCommand(devicerepository::Device* device, Net: device->sendCommand(cmdMsg); } +void DeviceRepoHandler::notificationReceived(interface::Observer::observable_type* o, const devicerepository::DeviceEventType& key, const devicerepository::DeviceEvent* e) noexcept { + switch (key) { + case devicerepository::DeviceEventType::DEVICE_ADDED: + break; + case devicerepository::DeviceEventType::DEVICE_REMOVED: + break; + case devicerepository::DeviceEventType::COMMAND_RECEIVED: + break; + case devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED: + break; + case devicerepository::DeviceEventType::STATE_CHANGED: + break; + default: + break; + } +} diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h similarity index 68% rename from src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h rename to src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h index 1882a850..fd3b0c70 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h @@ -24,7 +24,7 @@ namespace webresource { -enum class DevicesAPIMethod { +enum class DeviceAPIMethod { GET, COMMAND, STATUSUPDATE @@ -47,6 +47,7 @@ private: std::map infoCategoryFuncs; public: DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService); + virtual Poco::Net::HTTPRequestHandler* createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override; virtual void setWebServerService(webserver::WebServerService* service) noexcept override { @@ -57,6 +58,8 @@ public: return false; } + static DeviceAPIMethod getDeviceAPIMethod() noexcept; + }; @@ -83,8 +86,52 @@ public: * JSON texts (namely single strings, numbers or boolean values) that are not valid JSON texts according to RFC 4627. * Keep in mind that there are still a lot of JSON libraries and validators out there which still no nothing about RFC 7159. */ + +class DeviceRepoHandlerBase + : public interface::Observer, + public webserver::webresource::WebResourceHandlerImpl { + +protected: + Poco::URI matchUri; + devicerepository::DeviceRepositoryService* repoService; + const std::map& infoCategoryFuncs; + bool ssl; + + libvariant::Variant getDeviceInfo( devicerepository::Device* device, + Poco::Net::HTTPServerRequest &req, + const std::string& category = std::string(), + const std::vector& path = std::vector() ); + + void sendDeviceCommand( devicerepository::Device* device, + Poco::Net::HTTPServerRequest &req, + const std::string& cmd, + const std::vector& path = std::vector() ); + + +public: + + DeviceRepoHandlerBase( Poco::URI matchUri, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : matchUri(matchUri), repoService(repoService), infoCategoryFuncs(infoCategoryFuncs), ssl(ssl) {} + + virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + + virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + + virtual void notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept override; + +}; + + + class DeviceRepoHandler - : public virtual webserver::webresource::WebResourceHandlerImpl { + : public interface::Observer, + public webserver::webresource::WebResourceHandlerImpl { private: Poco::URI matchUri; @@ -115,6 +162,11 @@ public: virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + virtual void notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept override; + }; diff --git a/src/hos_cosgi_testwebapp/API-Notes.txt b/src/hos_cosgi_testwebapp/API-Notes.txt new file mode 100644 index 00000000..61891a3b --- /dev/null +++ b/src/hos_cosgi_testwebapp/API-Notes.txt @@ -0,0 +1,32 @@ + +HTTP - GET / HTTP - POST (application/x-www-form-urlencoded): +============================================================== + x) [OUT] DeviceAPIMethod::GET [Args: format, pointer, query?] + x) [IN] DeviceAPIMethod::COMMAND [Args: format, pointer, msg] + x) [IN] DeviceAPIMethod::STATUS_UPDATE [Args: format, pointer, msg] + x) [EVENT] DeviceAPIMethod::REGISTER_EVENT [Args: format, pointer, event] + - Long-Polling + - WebSocket + + +API Method Parameter: + -- Query: method = get | command | statusupdate | event + -- Header: API-METHOD | X-API-METHOD + +Common Parameters: + + x) Format-Parameters: + -- Query: format = json | jsonp | xmlplist | ... + -- Query: json | jsonp [= ] | xmlplist | ... + -- Query: callback = callback - in conjunction with jsonp or format=jsonp, defaults to callback + -- Header: application/[x-]javascript = jsonp | application/json = json | application/yaml = yaml | application/xml = xmlplist + + x) Pointer Parameter: + -- Query: pointer = + -- URL: .../devices//[.../] + +COMMAND/STATUS_UPDATE Parameters: + -- Query: body = + +EVENT Parameters: + -- Query: event = command | statusupdate | statechange | added | removed | all diff --git a/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js b/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js new file mode 100644 index 00000000..ebff544c --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js @@ -0,0 +1,32 @@ +define([ + "dojo/_base/config", + "dojo/store/JsonRest", + "dojox/socket", + "dojo/_base/xhr" +], function(config, JsonRest, Socket, xhr){ + var _store = new JsonRest({ + target: config.hos.deviceRepoUrl + }); + var _updateSocketOpened = false; + var _updateSocket = new Socket({ + url: config.hos.deviceRepoUrl, + interval: 5000, + transport: xhr.get }); + _updateSocket.on("open", function(event){ + _socketOpened = true; + console.log("Socket Opened ", event.message); + }); + _updateSocket.on("close", function(event){ + _socketOpened = false; + console.log("Socket Closed ", event.message); + }); + _updateSocket.on("error", function(event){ + _socketOpened = false; + console.log("Socket Error ", event.message); + }); + _updateSocket.on("message", function(event) { + var data = event.data; + console.log("Message: " /*+ data*/); + }); + return _store; +}); \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/DojoApp/test04.html b/src/hos_cosgi_testwebapp/webroot/DojoApp/test04.html new file mode 100644 index 00000000..c2481475 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/DojoApp/test04.html @@ -0,0 +1,52 @@ + + + + + Tutorial: Hello Dojo! + + + + + + + + + + + + + + + + + diff --git a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h index 2b13c4d3..d4eef0ba 100644 --- a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h +++ b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h @@ -273,13 +273,13 @@ protected: _handlePOST(req, resp); } else { std::cout << "Unsupported HTTP Request Method" << std::endl; - handleErrorResponse(req, resp, (boost::format("Unsupported HTTP Request Method %1%") % req.getMethod()).str(), - Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + handleErrorResponse(req, resp, (boost::format("HTTP method %1% not implemented") % req.getMethod()).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED); } } catch (NotImplementedException& e) { std::cout << "Unsupported HTTP Request Method" << std::endl; - handleErrorResponse(req, resp, (boost::format("Unsupported HTTP Request Method %1%") % req.getMethod()).str(), - Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + handleErrorResponse(req, resp, (boost::format("HTTP method %1% not implemented") % req.getMethod()).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED); } } } catch (WebResourceHandlerException& e) { diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp index aefb13e9..1d095fee 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp @@ -57,11 +57,12 @@ void WebServerServiceImpl::setCoreService(service::core::CoreService* coreServic Poco::Net::HTTPRequestHandler* WebServerServiceImpl::RequestHandlerFactory::createRequestHandler( const Poco::Net::HTTPServerRequest& request) { WebResourceHandlerFactory* factory = nullptr; std::string matchUri; + Poco::URI uri(request.getURI()); // We want a longest prefix match. // Since we have an ordered map, we just need to iterate it in reverse order and return the first match. for (auto i = parent->webResourceFactories.rbegin(); i != parent->webResourceFactories.rend(); i++) { - if ( boost::algorithm::starts_with(request.getURI(), i->first) - && ( request.getURI().size() == i->first.size() || request.getURI()[i->first.size()] == '/' ) ) { + if ( boost::algorithm::starts_with(uri.getPath(), i->first) + && ( uri.getPath().size() == i->first.size() || uri.getPath()[i->first.size()] == '/' ) ) { factory = i->second; matchUri = i->first; break;