From 44dafd1958bfe91440cefe9330a5d4400f4e309c Mon Sep 17 00:00:00 2001 From: matzman Date: Tue, 2 Jun 2015 09:16:26 +0200 Subject: [PATCH] . --- .../webresource/DeviceRepoHandler.cpp | 584 ++++++++++++++---- .../webresource/DeviceRepoHandler.h | 234 +++---- .../webresource/tmp/DeviceRepoHandler.cpp | 306 +++++++++ .../webresource/tmp/DeviceRepoHandler.h | 250 ++++++++ src/hos_cosgi_testwebapp/API-Notes.txt | 21 + .../webroot/DojoApp/dojo/hos/DeviceRepo.js | 5 +- 6 files changed, 1154 insertions(+), 246 deletions(-) create mode 100644 src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.cpp create mode 100644 src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.h diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp index fad3a429..de1851cb 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.cpp @@ -21,11 +21,14 @@ using namespace Poco; using namespace libvariant; + HOS_SERVICE_IMPL_INFO( DeviceRepoHandlerFactory, // Implementation service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name 1, 0 ) // Version + + DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService) : repoService(repoService) { infoCategoryFuncs.insert({"id", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { @@ -73,128 +76,78 @@ DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepos } -DeviceAPIMethod DeviceRepoHandlerFactory::getDeviceAPIMethod() noexcept { - -} - - -std::shared_ptr DeviceRepoHandlerFactory::getDeviceEventQueue(std::string clientID) { - if (clientID.empty()) { - - } else { - - } -} - Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { - if(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) - return new DeviceRepoEventWebSocketHandler(matchUri, repoService, infoCategoryFuncs, ssl); - else { - DeviceAPIMethod method = getDeviceAPIMethod(); - if (method == DeviceAPIMethod::REGISTEREVENT) { - return new DeviceRepoEventLongPollingHandler(matchUri, repoService, infoCategoryFuncs, ssl); + return new DeviceRepoHandler( this, matchUri, repoService, infoCategoryFuncs, ssl); +} + +std::shared_ptr DeviceRepoHandlerFactory::getDeviceEventQueue( const std::string& clientID ) { + if (clientID.empty()) { + return std::make_shared(clientID, repoService); + } else { + auto i = deviceEventQueues.find(clientID); + if (i == deviceEventQueues.end()) { + auto n = std::make_shared(clientID, repoService); + deviceEventQueues.insert({clientID, n}); + return n; } else { - return new DeviceRepoInfoHandler(matchUri, method, repoService, infoCategoryFuncs, ssl); + return i->second; } } } - - - - -Variant DeviceRepoHandlerBase::getDeviceInfo(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& category, const std::vector& path) { - Variant retval; - - // We want all info categories - if (category.empty()) { - for (auto c : infoCategoryFuncs) { - retval[c.first] = c.second(matchUri, device, req, category, ssl); - } - - // We want a single info category +void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) { + // WebSocket } else { - auto c = infoCategoryFuncs.find(category); - if (c != infoCategoryFuncs.end()) { - retval = c->second(matchUri, device, req, category, ssl); - if (path.size() > 0) { - std::stringstream ss; - for (auto p : path) { - ss << "/" << p; - } - std::string pathstr = ss.str(); - if (retval.HasPath(pathstr)) { - retval = retval.GetPath(pathstr); - } else { - throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % pathstr % category).str(), - Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); - } + if (req.getMethod() == Net::HTTPRequest::HTTP_PUT) { + // Put Command + throw webserver::webresource::WebResourceHandlerException("Still a ToDo.", + Net::HTTPResponse::HTTP_NOT_IMPLEMENTED); + } else if (req.getMethod() == Net::HTTPRequest::HTTP_GET || req.getMethod() == Net::HTTPRequest::HTTP_POST) { + DeviceAPIMethod method = getDeviceAPIMethod(req); + if (method == DeviceAPIMethod::GET) { + __handleInfo(req, resp); + } else if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) { + __handleCommand(req, resp, method); + } else if (method == DeviceAPIMethod::EVENT) { + __handleEventLongPolling(req, resp); + } else { + throw webserver::webresource::WebResourceHandlerException("Could not understood request", + Net::HTTPResponse::HTTP_BAD_REQUEST); } } else { - throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown device info category \"%1%\"") % category).str(), - Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + throw webserver::webresource::WebResourceHandlerException("Could not understood request", + Net::HTTPResponse::HTTP_BAD_REQUEST); } } - - return retval; } -void DeviceRepoHandlerBase::sendDeviceCommand(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& cmd, const std::vector& path) { - Variant cmdMsg; - VariantRef mPart = cmdMsg.Ref(); - if (path.size() > 0) { - for (int i = 0; i < path.size(); i++) { - mPart[path[i]] = Variant(); - mPart.ReassignRef(mPart[path[i]]); - } - } - /* - * The libvariant json deserializer knows nothing about RFC 7159, therefore non-object or non-array values are rejected. - * As a workaround we use the yaml deserializer (yaml can be considered a super-set of json). - * Fun fact: the json serializer accepts non-object or non-array values. - */ - //mPart = DeserializeJSON(cmd); - mPart = DeserializeYAML(cmd); - device->sendCommand(cmdMsg); -} - - - -void DeviceRepoInfoHandler::__handle(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) { - - std::string methodValue; - if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) { - if (form.has("value")) { - methodValue = form.get("value"); - } else { - throw webserver::webresource::WebResourceHandlerException("API method requires a value parameter.", - Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); - } - } +void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { std::vector pathPieces, matchPieces; matchUri.getPathSegments(matchPieces); Poco::URI uri(req.getURI()); uri.getPathSegments(pathPieces); + std::unique_ptr form; + if (req.getMethod() == Net::HTTPRequest::HTTP_POST) { + form.reset(new Net::HTMLForm(req, req.stream())); + } else { + form.reset(new Net::HTMLForm(req)); + } - // No device given + // No device given, query mode (there is no real query functionality implemented yet though) if (pathPieces.size() <= matchPieces.size()) { - if (method != DeviceAPIMethod::GET) { - throw webserver::webresource::WebResourceHandlerException("Requested API method requires a device", - Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); - } - auto devices = repoService->getDevices(); - // Range header parameter - std::string rangeRaw; + // Parse HTTP header "Range" parameter (to support dojo/store) size_t rangeStart = 0; size_t rangeStop = devices.size() - 1; + std::string rangeRaw; if (req.has("Range")) { rangeRaw = req.get("Range"); } else if (req.has("X-Range")) { @@ -220,19 +173,22 @@ void DeviceRepoInfoHandler::__handle(Poco::Net::HTTPServerRequest &req, Poco::Ne } } - // - + // Iterate over devices Variant result = Variant::List(); for (size_t t = rangeStart; t <= rangeStop; t++) { auto dev = devices[t].lock(); result.Append(getDeviceInfo(dev.get(), req)); } + + // There are no devices and a range was given, probably out of range 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); } + + // Return devices array } else { std::string rangeOut = (boost::format("items %1%-%2%/%3%") % rangeStart % rangeStop % devices.size()).str(); resp.set("Content-Range", rangeOut); @@ -240,41 +196,33 @@ void DeviceRepoInfoHandler::__handle(Poco::Net::HTTPServerRequest &req, Poco::Ne handleResponse(req, resp, result); } - // Device given + // Single device mode } else { + // Get Device std::string devid = pathPieces[matchPieces.size()]; auto device = repoService->getDevice(devid); if (!device.expired()) { - switch (method) { - case DeviceAPIMethod::GET: - { - std::string category; - if (pathPieces.size() > matchPieces.size() + 1) { - category = pathPieces[matchPieces.size() + 1]; - } - std::vector path; - for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { - path.push_back(pathPieces[i]); - } - Variant result = getDeviceInfo(device.lock().get(), req, category, path); - handleResponse(req, resp, result); - } - break; - case DeviceAPIMethod::COMMAND: - { - std::vector path; - for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) { - path.push_back(pathPieces[i]); - } - sendDeviceCommand(device.lock().get(), req, methodValue, path); - handleResponse(req, resp, std::string("OK: Command send")); - } - break; - case DeviceAPIMethod::STATUSUPDATE: - throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method."); - default: - throw webserver::webresource::WebResourceHandlerException("Internal Server Error"); + // Get info category + std::string category; + if (pathPieces.size() > matchPieces.size() + 1) { + category = pathPieces[matchPieces.size() + 1]; } + // Get path from url and append pointer parameter + std::stringstream path; + for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { + path << "/" << pathPieces[i]; + } + if (form->has("pointer")) { + auto tmp = form->get("pointer"); + if (tmp.size() > 0 && tmp[0] != '/') { + path << "/"; + } + path << tmp; + } + // Get device info + Variant result = getDeviceInfo(device.lock().get(), req, category, path.str()); + // Return device info + handleResponse(req, resp, result); } else { throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(), Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); @@ -283,24 +231,392 @@ void DeviceRepoInfoHandler::__handle(Poco::Net::HTTPServerRequest &req, Poco::Ne } +void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method) { + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); + Poco::URI uri(req.getURI()); + uri.getPathSegments(pathPieces); + std::unique_ptr form; + if (req.getMethod() == Net::HTTPRequest::HTTP_POST) { + form.reset(new Net::HTMLForm(req, req.stream())); + } else { + form.reset(new Net::HTMLForm(req)); + } -void DeviceRepoInfoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { - Net::HTMLForm form(req); - __handle(req, resp, form); -} + // No device given + if (pathPieces.size() <= matchPieces.size()) { + throw webserver::webresource::WebResourceHandlerException("API method requires a device", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } -void DeviceRepoInfoHandler::_handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { - Net::HTMLForm form(req, req.stream()); - __handle(req, resp, form); + // Get message + std::string msg; + if (form->has("value")) { + msg = form->get("value"); + } else { + throw webserver::webresource::WebResourceHandlerException("API method requires a value.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + + // Get path from url and append pointer parameter + std::vector path; + for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) { + path.push_back(pathPieces[i]); + } + if (form->has("pointer")) { + URI pointer(form->get("pointer")); + pointer.getPathSegments(path); + } + + // Get device and do stuff + std::string devid = pathPieces[matchPieces.size()]; + auto device = repoService->getDevice(devid); + if (!device.expired()) { + switch (method) { + case DeviceAPIMethod::COMMAND: + sendDeviceCommand(device.lock().get(), req, msg, path); + handleResponse(req, resp, std::string("OK: Command send")); + break; + case DeviceAPIMethod::STATUSUPDATE: + throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method."); + default: + throw webserver::webresource::WebResourceHandlerException("Internal Server Error"); + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } } +void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); + Poco::URI uri(req.getURI()); + uri.getPathSegments(pathPieces); + std::unique_ptr form; + if (req.getMethod() == Net::HTTPRequest::HTTP_POST) { + form.reset(new Net::HTMLForm(req, req.stream())); + } else { + form.reset(new Net::HTMLForm(req)); + } + bool singleDeviceMode = true; + + // No device given + if (pathPieces.size() <= matchPieces.size()) { + singleDeviceMode = false; + } + + // Get device and do stuff + std::weak_ptr device; + std::string devid; + if (singleDeviceMode) { + devid = pathPieces[matchPieces.size()]; + device = repoService->getDevice(devid); + } + if (!singleDeviceMode || !device.expired()) { + // Get event type + std::string eventRaw; + DeviceEventType event = DeviceEventType::STATE_CHANGED; + if (form->has("onevent")) { + eventRaw = form->get("onevent"); + } else if (req.has("ON-EVENT")) { + eventRaw = req.get("ON-EVENT"); + } else if (req.has("X-ON-EVENT")) { + eventRaw = req.get("X-ON-EVENT"); + } + if (!eventRaw.empty()) { + if (eventRaw == "all") { + event = DeviceEventType::ALL_EVENTS; + } else if (eventRaw == "command") { + event = DeviceEventType::COMMAND_RECEIVED; + } else if (eventRaw == "statusupdate") { + event = DeviceEventType::STATUS_UPDATE_RECEIVED; + } else if (eventRaw == "statechanged") { + event = DeviceEventType::STATE_CHANGED; + } else if (eventRaw == "added") { + event = DeviceEventType::DEVICE_ADDED; + } else if (eventRaw == "removed") { + event = DeviceEventType::DEVICE_REMOVED; + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown event type \"%1%\"") % eventRaw).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } + // Get client ID + std::string clientID; + if (form->has("clientid")) { + clientID = form->get("clientid"); + } else if (req.has("Client-ID")) { + clientID = req.get("Client-ID"); + } else if (req.has("X-Client-ID")) { + clientID = req.get("X-Client-ID"); + } + // Get path from url and append pointer parameter + std::stringstream path; + for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { + path << "/" << pathPieces[i]; + } + if (form->has("pointer")) { + auto tmp = form->get("pointer"); + if (tmp.size() > 0 && tmp[0] != '/') { + path << "/"; + } + path << tmp; + } + // Get event queue + auto eventQueue = parent->getDeviceEventQueue(clientID); + devicerepository::Device* devptr = nullptr; + if (singleDeviceMode) { + devptr = device.lock().get(); + } + eventQueue->checkRegistration(event, devptr, path.str()); + auto msg = eventQueue->popEvent(); + handleResponse(req, resp, msg); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } +} + + + +void DeviceEventQueue::notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept { + pushEvent(Variant("Test")); +} + + +Variant DeviceRepoHandler::getDeviceInfo(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& category, const std::string& path) { + Variant retval; + + // We want all info categories + if (category.empty()) { + for (auto c : infoCategoryFuncs) { + retval[c.first] = c.second(matchUri, device, req, category, ssl); + } + + // We want a single info category + } else { + auto c = infoCategoryFuncs.find(category); + if (c != infoCategoryFuncs.end()) { + retval = c->second(matchUri, device, req, category, ssl); + if (!path.empty()) { + if (retval.HasPath(path)) { + retval = retval.GetPath(path); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % path % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown device info category \"%1%\"") % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + + return retval; +} + + + +void DeviceRepoHandler::sendDeviceCommand(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& cmd, const std::vector& path) { + Variant cmdMsg; + VariantRef mPart = cmdMsg.Ref(); + if (path.size() > 0) { + for (int i = 0; i < path.size(); i++) { + mPart[path[i]] = Variant(); + mPart.ReassignRef(mPart[path[i]]); + } + } + /* + * The libvariant json deserializer knows nothing about RFC 7159, therefore non-object or non-array values are rejected. + * As a workaround we use the yaml deserializer (yaml can be considered a super-set of json). + * Fun fact: the json serializer accepts non-object or non-array values. + */ + //mPart = DeserializeJSON(cmd); + mPart = DeserializeYAML(cmd); + device->sendCommand(cmdMsg); +} + + +DeviceAPIMethod DeviceRepoHandler::getDeviceAPIMethod(Net::HTTPServerRequest &req) noexcept { + std::unique_ptr form; + DeviceAPIMethod retval = DeviceAPIMethod::GET; // default + if (req.getMethod() == Net::HTTPRequest::HTTP_POST && req.has("Content-type") + && req.get("Content-type") == Net::HTMLForm::ENCODING_URL) { + form.reset(new Net::HTMLForm(req, req.stream())); + } else { + form.reset(new Net::HTMLForm(req)); + } + std::string methodRaw; + if (form->has("method")) { + methodRaw = form->get("method"); + } else if (req.has("API-Method")) { + methodRaw = req.get("API-Method"); + } else if (req.has("X-API-Method")) { + methodRaw = req.get("X-API-Method"); + } + if (!methodRaw.empty()) { + if (methodRaw == "command") { + retval = DeviceAPIMethod::COMMAND; + } else if (methodRaw == "statusupdate") { + retval = DeviceAPIMethod::STATUSUPDATE; + } else if (methodRaw == "event") { + retval = DeviceAPIMethod::EVENT; + } else { + throw webserver::webresource::WebResourceHandlerException( + (boost::format("Unknown device api method \"%1%\"") % methodRaw).str(), + Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST ); + } + } else if (form->has("onevent")) { + retval = DeviceAPIMethod::EVENT; + } + return retval; +} + + + + + +/* + +void DeviceEventQueue::notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept { + if (eventType == DeviceEventType::ALL_EVENTS || eventType == key) { + switch (key) { + case devicerepository::DeviceEventType::COMMAND_RECEIVED: + break; + case devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED: + break; + case devicerepository::DeviceEventType::STATE_CHANGED: + break; + case devicerepository::DeviceEventType::DEVICE_ADDED: + break; + case devicerepository::DeviceEventType::DEVICE_REMOVED: + break; + default: + break; + } + } + } + + + +Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { + if(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) { + return new DeviceRepoEventHandler( matchUri, DeviceAPIMethod::EVENT, repoService, ssl, true); + } else { + if (request.getMethod() == Net::HTTPRequest::HTTP_PUT) { + return new DeviceRepoCommandHandler(matchUri, DeviceAPIMethod::UNKNOWN, repoService, ssl); + } else if (request.getMethod() == Net::HTTPRequest::HTTP_GET || request.getMethod() == Net::HTTPRequest::HTTP_POST) { + DeviceAPIMethod method = getDeviceAPIMethod(request); + if (method == DeviceAPIMethod::GET) { + return new DeviceRepoInfoHandler( matchUri, method, repoService, infoCategoryFuncs, ssl ); + } else if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::COMMAND) { + return new DeviceRepoCommandHandler(matchUri, method, repoService, infoCategoryFuncs, ssl); + } else if (method == DeviceAPIMethod::EVENT) { + return new DeviceRepoEventHandler( matchUri, method, repoService, infoCategoryFuncs, ssl, false); + } + } + } + return service->createErrorHandler(request, ssl, "", Net::HTTPResponse::HTTP_BAD_REQUEST); +} + + + +DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* 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(); + } }); + + infoCategoryFuncs.insert({"status_topic", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + return device->getStatusTopic(); + } }); + + infoCategoryFuncs.insert({"command_topic", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + return device->getCmdTopic(); + } }); + + infoCategoryFuncs.insert({"current_state", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + auto retval = device->getCurrentState(); + if (retval.IsMap()) { + retval["timestamp"] = utils::timestamp_to_ISO8601(device->getCurrentStateTimestamp()); + } + return retval; + } }); + + infoCategoryFuncs.insert({"last_state", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + auto retval = device->getLastState(); + if (retval.IsMap()) { + retval["timestamp"] = utils::timestamp_to_ISO8601(device->getLastStateTimestamp()); + } + return retval; + } }); + + infoCategoryFuncs.insert({"uri", [=](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + std::stringstream ss; + if (ssl) { + ss << "https://"; + } else { + ss << "http://"; + } + ss << req.getHost() << matchUri.toString() << device->getDeviceID(); + return ss.str(); + } }); +} + + + +DeviceAPIMethod DeviceRepoHandlerFactory::getDeviceAPIMethod(Net::HTTPServerRequest &req) noexcept { + Net::HTMLForm form; + DeviceAPIMethod retval = DeviceAPIMethod::GET; // default + if (req.getMethod() == Net::HTTPRequest::HTTP_POST && req.has("Content-type") + && req.get("Content-type") == Net::HTMLForm::ENCODING_URL) { + form = Net::HTMLForm(req, req.stream()); + } else { + form = Net::HTMLForm(req); + } + std::string methodRaw; + if (form.has("method")) { + methodRaw = form.has("method"); + } else if (req.has("API-Method")) { + methodRaw = req.has("API-Method"); + } else if (req.has("X-API-Method")) { + methodRaw = req.has("X-API-Method"); + } + if (!methodRaw.empty()) { + if (methodRaw == "command") { + retval = DeviceAPIMethod::COMMAND; + } else if (methodRaw == "statusupdate") { + retval = DeviceAPIMethod::STATUSUPDATE; + } else if (methodRaw == "event") { + retval = DeviceAPIMethod::EVENT; + } else { + throw webserver::webresource::WebResourceHandlerException( + (boost::format("Unknown device api method \"%1%\"") % methodRaw).str(), + Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST ); + } + } else if (form.has("event")) { + retval = DeviceAPIMethod::EVENT; + } + return retval; +} + +*/ + } // namespace resources } // namespace webserver } // namespace service } // namespace cosgi } // namespace hos - - diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h index a3885833..fba45c62 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h @@ -9,9 +9,19 @@ #include #include #include +#pragma once + + +#include +#include +#include +#include +#include +#include #include #include #include +#include #include #include @@ -30,54 +40,90 @@ enum class DeviceAPIMethod { GET, COMMAND, STATUSUPDATE, - REGISTEREVENT + EVENT, + UNKNOWN }; + class DeviceEventQueue : public virtual interface::Observer { public: const std::string clientID; + DeviceEventType eventType = DeviceEventType::ALL_EVENTS; DeviceRepositoryService* const repoService; - DeviceEventType eventType; - Device* device; + Device* device = nullptr; std::string pointer; - std::queue eventQueue; + bool registeredToEvent = false; size_t openConnectionCount = 0; + std::queue _eventQueue; + std::mutex _mutex; + std::condition_variable _cv; DeviceEventQueue( const std::string& clientID, - const DeviceEventType eventType, - DeviceRepositoryService* repoService, - Device* device, - std::string pointer ) - : clientID(clientID), eventType(eventType), repoService(repoService), device(device), pointer(pointer) {} - - virtual void notificationReceived( interface::Observer::observable_type* o, - const devicerepository::DeviceEventType& key, - const devicerepository::DeviceEvent* e) noexcept override { - if (eventType == DeviceEventType::ALL_EVENTS || eventType == key) { - switch (key) { - case devicerepository::DeviceEventType::COMMAND_RECEIVED: - break; - case devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED: - break; - case devicerepository::DeviceEventType::STATE_CHANGED: - break; - case devicerepository::DeviceEventType::DEVICE_ADDED: - break; - case devicerepository::DeviceEventType::DEVICE_REMOVED: - break; - default: - break; - } + DeviceRepositoryService* repoService ) + : clientID(clientID), repoService(repoService) {} + void pushEvent(const libvariant::Variant& event) { + { + std::lock_guard lock(_mutex); + _eventQueue.push(event); + } + _cv.notify_one(); + } + libvariant::Variant popEvent() { + std::unique_lock lock(_mutex); + libvariant::Variant retval; + while (_eventQueue.empty()) { + _cv.wait(lock); + } + retval = _eventQueue.front(); + _eventQueue.pop(); + return retval; + } + void checkRegistration(DeviceEventType eventType, Device* device, const std::string& pointer) { + if (!registeredToEvent || eventType != this->eventType || device != this->device) { + unregisterEvent(); + registerEvent(eventType, device); + } + if (this->pointer != pointer) { + this->pointer = pointer; } } - + void registerEvent(DeviceEventType eventType, Device* device = nullptr) { + std::lock_guard lock(_mutex); + if (!registeredToEvent) { + if (device) { + device->addObserver(eventType, this); + } else { + repoService->addObserver(eventType, this); + } + this->device = device; + this->eventType = eventType; + registeredToEvent = true; + } + } + void unregisterEvent() { + std::lock_guard lock(_mutex); + if (registeredToEvent) { + if (device) { + device->deleteObserver(this); + } else { + repoService->deleteObserver(this); + } + registeredToEvent = false; + } + } + ~DeviceEventQueue() { + unregisterEvent(); + } + virtual void notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept override; }; + class DeviceRepoHandlerFactory : public virtual webserver::WebResourceHandlerFactory, public implementation::Service { @@ -106,140 +152,110 @@ public: return false; } - static DeviceAPIMethod getDeviceAPIMethod() noexcept; - - std::shared_ptr getDeviceEventQueue( const std::string& clientID, - const DeviceEventType eventType, - DeviceRepositoryService* repoService, - Device* device, - std::string pointer ); + std::shared_ptr getDeviceEventQueue( const std::string& clientID ); }; -/** - * Implements a REST API for accessing the device repository and getting info about a device. - * - * HTTP GET REQUESTS: - * 1. http://[prefixPath]/devices - Returns all known devices - * 2. http://[prefixPath]/devices/[deviceID] - Returns complete info about the given device as JSON object. - * 3. http://[prefixPath]/devices/[deviceID]/[infoCategory]/[infoPath] - Returns selected info about the given device as JSON object ([infoCategory]/[infoPath] is treated as JSON Path selecting parts of the complete JSON info object from 1). - * 4. http://[prefixPath]/devices/[deviceID]/[cmdPath]?method=command&value=[cmdValue] - Sends the command [cmdPath]/[cmdValue] to the given device. - * 5. http://[prefixPath]/devices/[deviceID]/[statusPath]?method=statusupdate&value=[statusValue] - Sends the status update [statusPath]/[statusValue] to the given device (Only useful for virtual devices). - * - * HTTP POST REQUESTS (content-type: application/x-www-form-urlencoded): - * - Basically the same as 4 and 5 from the HTTP GET REQUESTS list, but parameters are transmitted in the request body instead of the URL. - * - * HTTP POST REQUESTS (content-type: application/json): - * - http://[prefixPath]/devices/[deviceID]/[method]/[methodPath] - Semantically the same as 4 and 5 from the HTTP GET REQUESTS list, but the method parameter is part of the URL and the request body only contains a single JSON text. - * - * - * A note about JSON object returned by the above URLs: According to RFC 4627 a JSON text is a serialized object or array. - * This was later relaxed by RFC 7159 which defines that a JSON text is a serialized value. Our API adheres to RFC 7159 and may return - * 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 +class DeviceRepoHandler : public webserver::webresource::WebResourceHandlerImpl { - protected: + DeviceRepoHandlerFactory* parent; Poco::URI matchUri; devicerepository::DeviceRepositoryService* repoService; const std::map& infoCategoryFuncs; bool ssl; + +public: + + DeviceRepoHandler( DeviceRepoHandlerFactory* parent, + Poco::URI matchUri, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : parent(parent), matchUri(matchUri), repoService(repoService), infoCategoryFuncs(infoCategoryFuncs), ssl(ssl) {} + + virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + + void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp); + + void __handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method); + + void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp); + libvariant::Variant getDeviceInfo( devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category = std::string(), - const std::vector& path = std::vector() ); + const std::string& path = std::string() ); 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) {} - + static DeviceAPIMethod getDeviceAPIMethod(Poco::Net::HTTPServerRequest &req) noexcept; }; - +/* class DeviceRepoInfoHandler : public DeviceRepoHandlerBase { - protected: - DeviceAPIMethod method; - + const std::map& infoCategoryFuncs; public: - DeviceRepoInfoHandler( Poco::URI matchUri, DeviceAPIMethod method, devicerepository::DeviceRepositoryService* repoService, const std::map& infoCategoryFuncs, bool ssl = false ) - : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, ssl), method(method) {} + : DeviceRepoHandlerBase(matchUri, method, repoService, ssl), infoCategoryFuncs(infoCategoryFuncs) {} - 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 __handle(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form); + virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + libvariant::Variant getDeviceInfo( devicerepository::Device* device, + Poco::Net::HTTPServerRequest &req, + const std::string& category = std::string(), + const std::vector& path = std::vector() ); }; -class DeviceRepoEventLongPollingHandler +class DeviceRepoCommandHandler : public DeviceRepoHandlerBase { - -protected: - - public: - - DeviceRepoEventLongPollingHandler( Poco::URI matchUri, + DeviceRepoCommandHandler( Poco::URI matchUri, + DeviceAPIMethod method, devicerepository::DeviceRepositoryService* repoService, - const std::map& infoCategoryFuncs, bool ssl = false ) - : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, ssl) {} + : DeviceRepoHandlerBase(matchUri, method, repoService, 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 _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + void sendDeviceCommand( devicerepository::Device* device, + Poco::Net::HTTPServerRequest &req, + const std::string& cmd, + const std::vector& path = std::vector() ); }; -class DeviceRepoEventWebSocketHandler +class DeviceRepoEventHandler : public DeviceRepoHandlerBase { - protected: - - + bool webSocketConnection; public: - - DeviceRepoEventWebSocketHandler( Poco::URI matchUri, + DeviceRepoEventHandler( Poco::URI matchUri, + DeviceAPIMethod method, devicerepository::DeviceRepositoryService* repoService, - const std::map& infoCategoryFuncs, - bool ssl = false ) - : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, 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; + bool ssl = false, + bool webSocketConnection = false) + : DeviceRepoHandlerBase(matchUri, method, repoService, ssl), webSocketConnection(webSocketConnection) {} + virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; }; - +*/ } // namespace webresource diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.cpp new file mode 100644 index 00000000..7ba34cd7 --- /dev/null +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.cpp @@ -0,0 +1,306 @@ + +#include "DeviceRepoHandler.h" +#include +#include +#include +#include +#include +#include + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace devicerepository { +namespace webresource { + + + +using namespace Poco; +using namespace libvariant; + + +HOS_SERVICE_IMPL_INFO( DeviceRepoHandlerFactory, // Implementation + service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name + 1, 0 ) // Version + + +DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* 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(); + } }); + + infoCategoryFuncs.insert({"status_topic", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + return device->getStatusTopic(); + } }); + + infoCategoryFuncs.insert({"command_topic", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + return device->getCmdTopic(); + } }); + + infoCategoryFuncs.insert({"current_state", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + auto retval = device->getCurrentState(); + if (retval.IsMap()) { + retval["timestamp"] = utils::timestamp_to_ISO8601(device->getCurrentStateTimestamp()); + } + return retval; + } }); + + infoCategoryFuncs.insert({"last_state", [](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + auto retval = device->getLastState(); + if (retval.IsMap()) { + retval["timestamp"] = utils::timestamp_to_ISO8601(device->getLastStateTimestamp()); + } + return retval; + } }); + + infoCategoryFuncs.insert({"uri", [=](Poco::URI& matchUri, devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + std::stringstream ss; + if (ssl) { + ss << "https://"; + } else { + ss << "http://"; + } + ss << req.getHost() << matchUri.toString() << device->getDeviceID(); + return ss.str(); + } }); +} + + +DeviceAPIMethod DeviceRepoHandlerFactory::getDeviceAPIMethod() noexcept { + +} + + +/*std::shared_ptr DeviceRepoHandlerFactory::getDeviceEventQueue(std::string clientID) { + if (clientID.empty()) { + + } else { + + } +}*/ + + +Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { + if(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) + return new DeviceRepoEventWebSocketHandler(matchUri, repoService, infoCategoryFuncs, ssl); + else { + DeviceAPIMethod method = getDeviceAPIMethod(); + if (method == DeviceAPIMethod::EVENT) { + return new DeviceRepoEventLongPollingHandler(matchUri, repoService, infoCategoryFuncs, ssl); + } else { + return new DeviceRepoInfoHandler(matchUri, method, repoService, infoCategoryFuncs, ssl); + } + } +} + + + + + + + +Variant DeviceRepoHandlerBase::getDeviceInfo(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& category, const std::vector& path) { + Variant retval; + + // We want all info categories + if (category.empty()) { + for (auto c : infoCategoryFuncs) { + retval[c.first] = c.second(matchUri, device, req, category, ssl); + } + + // We want a single info category + } else { + auto c = infoCategoryFuncs.find(category); + if (c != infoCategoryFuncs.end()) { + retval = c->second(matchUri, device, req, category, ssl); + if (path.size() > 0) { + std::stringstream ss; + for (auto p : path) { + ss << "/" << p; + } + std::string pathstr = ss.str(); + if (retval.HasPath(pathstr)) { + retval = retval.GetPath(pathstr); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % pathstr % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown device info category \"%1%\"") % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + + return retval; +} + + + +void DeviceRepoHandlerBase::sendDeviceCommand(devicerepository::Device* device, Net::HTTPServerRequest &req, const std::string& cmd, const std::vector& path) { + Variant cmdMsg; + VariantRef mPart = cmdMsg.Ref(); + if (path.size() > 0) { + for (int i = 0; i < path.size(); i++) { + mPart[path[i]] = Variant(); + mPart.ReassignRef(mPart[path[i]]); + } + } + /* + * The libvariant json deserializer knows nothing about RFC 7159, therefore non-object or non-array values are rejected. + * As a workaround we use the yaml deserializer (yaml can be considered a super-set of json). + * Fun fact: the json serializer accepts non-object or non-array values. + */ + //mPart = DeserializeJSON(cmd); + mPart = DeserializeYAML(cmd); + device->sendCommand(cmdMsg); +} + + + +void DeviceRepoInfoHandler::__handle(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) { + + std::string methodValue; + if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) { + if (form.has("value")) { + methodValue = form.get("value"); + } else { + throw webserver::webresource::WebResourceHandlerException("API method requires a value parameter.", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + } + + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); + Poco::URI uri(req.getURI()); + uri.getPathSegments(pathPieces); + + // No device given + if (pathPieces.size() <= matchPieces.size()) { + if (method != DeviceAPIMethod::GET) { + throw webserver::webresource::WebResourceHandlerException("Requested API method requires a device", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + + 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); + } + + // Device given + } else { + std::string devid = pathPieces[matchPieces.size()]; + auto device = repoService->getDevice(devid); + if (!device.expired()) { + switch (method) { + case DeviceAPIMethod::GET: + { + std::string category; + if (pathPieces.size() > matchPieces.size() + 1) { + category = pathPieces[matchPieces.size() + 1]; + } + std::vector path; + for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { + path.push_back(pathPieces[i]); + } + Variant result = getDeviceInfo(device.lock().get(), req, category, path); + handleResponse(req, resp, result); + } + break; + case DeviceAPIMethod::COMMAND: + { + std::vector path; + for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) { + path.push_back(pathPieces[i]); + } + sendDeviceCommand(device.lock().get(), req, methodValue, path); + handleResponse(req, resp, std::string("OK: Command send")); + } + break; + case DeviceAPIMethod::STATUSUPDATE: + throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method."); + default: + throw webserver::webresource::WebResourceHandlerException("Internal Server Error"); + } + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } +} + + + +void DeviceRepoInfoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + Net::HTMLForm form(req); + __handle(req, resp, form); +} + +void DeviceRepoInfoHandler::_handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + Net::HTMLForm form(req, req.stream()); + __handle(req, resp, form); +} + + + +} // namespace resources +} // namespace webserver +} // namespace service +} // namespace cosgi +} // namespace hos + + + diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.h new file mode 100644 index 00000000..3e9bcfca --- /dev/null +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/webresource/tmp/DeviceRepoHandler.h @@ -0,0 +1,250 @@ + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace devicerepository { +namespace webresource { + + + +enum class DeviceAPIMethod { + GET, + COMMAND, + STATUSUPDATE, + EVENT +}; + + +class DeviceEventQueue + : public virtual interface::Observer { +public: + const std::string clientID; + DeviceRepositoryService* const repoService; + DeviceEventType eventType; + Device* device; + std::string pointer; + std::queue eventQueue; + size_t openConnectionCount = 0; + + DeviceEventQueue( const std::string& clientID, + const DeviceEventType eventType, + DeviceRepositoryService* repoService, + Device* device, + std::string pointer ) + : clientID(clientID), eventType(eventType), repoService(repoService), device(device), pointer(pointer) {} + + virtual void notificationReceived( interface::Observer::observable_type* o, + const devicerepository::DeviceEventType& key, + const devicerepository::DeviceEvent* e) noexcept override { + if (eventType == DeviceEventType::ALL_EVENTS || eventType == key) { + switch (key) { + case devicerepository::DeviceEventType::COMMAND_RECEIVED: + break; + case devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED: + break; + case devicerepository::DeviceEventType::STATE_CHANGED: + break; + case devicerepository::DeviceEventType::DEVICE_ADDED: + break; + case devicerepository::DeviceEventType::DEVICE_REMOVED: + break; + default: + break; + } + } + } + +}; + + + +class DeviceRepoHandlerFactory + : public virtual webserver::WebResourceHandlerFactory, + public implementation::Service { +public: + typedef std::function infoCategoryFunc_t; +private: + webserver::WebServerService* service = nullptr; + DeviceRepositoryService* repoService; + std::map infoCategoryFuncs; + std::map> deviceEventQueues; + +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 { + this->service = service; + } + + virtual bool excludeFromAuthentication() noexcept override { + return false; + } + + static DeviceAPIMethod getDeviceAPIMethod() noexcept; + + std::shared_ptr getDeviceEventQueue( const std::string& clientID, + const DeviceEventType eventType, + DeviceRepositoryService* repoService, + Device* device, + std::string pointer ); + +}; + + + +/** + * Implements a REST API for accessing the device repository and getting info about a device. + * + * HTTP GET REQUESTS: + * 1. http://[prefixPath]/devices - Returns all known devices + * 2. http://[prefixPath]/devices/[deviceID] - Returns complete info about the given device as JSON object. + * 3. http://[prefixPath]/devices/[deviceID]/[infoCategory]/[infoPath] - Returns selected info about the given device as JSON object ([infoCategory]/[infoPath] is treated as JSON Path selecting parts of the complete JSON info object from 1). + * 4. http://[prefixPath]/devices/[deviceID]/[cmdPath]?method=command&value=[cmdValue] - Sends the command [cmdPath]/[cmdValue] to the given device. + * 5. http://[prefixPath]/devices/[deviceID]/[statusPath]?method=statusupdate&value=[statusValue] - Sends the status update [statusPath]/[statusValue] to the given device (Only useful for virtual devices). + * + * HTTP POST REQUESTS (content-type: application/x-www-form-urlencoded): + * - Basically the same as 4 and 5 from the HTTP GET REQUESTS list, but parameters are transmitted in the request body instead of the URL. + * + * HTTP POST REQUESTS (content-type: application/json): + * - http://[prefixPath]/devices/[deviceID]/[method]/[methodPath] - Semantically the same as 4 and 5 from the HTTP GET REQUESTS list, but the method parameter is part of the URL and the request body only contains a single JSON text. + * + * + * A note about JSON object returned by the above URLs: According to RFC 4627 a JSON text is a serialized object or array. + * This was later relaxed by RFC 7159 which defines that a JSON text is a serialized value. Our API adheres to RFC 7159 and may return + * 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 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) {} + +}; + + + +class DeviceRepoInfoHandler + : public DeviceRepoHandlerBase { + +protected: + DeviceAPIMethod method; + +public: + + DeviceRepoInfoHandler( Poco::URI matchUri, + DeviceAPIMethod method, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, ssl), method(method) {} + + 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 __handle(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form); + +}; + + + +class DeviceRepoEventLongPollingHandler + : public DeviceRepoHandlerBase { + +protected: + + +public: + + DeviceRepoEventLongPollingHandler( Poco::URI matchUri, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, 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; + +}; + + + +class DeviceRepoEventWebSocketHandler + : public DeviceRepoHandlerBase { + +protected: + + +public: + + DeviceRepoEventWebSocketHandler( Poco::URI matchUri, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : DeviceRepoHandlerBase(matchUri, repoService, infoCategoryFuncs, 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; + +}; + + + +} // namespace webresource +} // namespace devicerepository +} // namespace service +} // namespace cosgi +} // namespace hos + diff --git a/src/hos_cosgi_testwebapp/API-Notes.txt b/src/hos_cosgi_testwebapp/API-Notes.txt index e57d64ef..af38c716 100644 --- a/src/hos_cosgi_testwebapp/API-Notes.txt +++ b/src/hos_cosgi_testwebapp/API-Notes.txt @@ -56,3 +56,24 @@ EVENT Parameters: ============================================================== +GET/POST Info Handler [Condition: GET/POST + AP::GET] + +GET/POST/PUT Command Handler [Condition: GET/POST/PUT + API::COMMAND/API::STATUSUPDATE] + +GET/POST Event Handler [Condition: GET/POST + API::EVENT] + +WebSocket Event Handler [Condition: WebSocket] + + + +IF GET/POST: + Get API Method + Get other parameters + Do stuff +ELSEIF PUT: + Get other parameters + Do stuff +ELSEIF WebSocket: + + + diff --git a/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js b/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js index ebff544c..d6642172 100644 --- a/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js +++ b/src/hos_cosgi_testwebapp/webroot/DojoApp/dojo/hos/DeviceRepo.js @@ -9,9 +9,8 @@ define([ }); var _updateSocketOpened = false; var _updateSocket = new Socket({ - url: config.hos.deviceRepoUrl, - interval: 5000, - transport: xhr.get }); + url: config.hos.deviceRepoUrl + "?onevent=statechanged", + transport: xhr.post }); _updateSocket.on("open", function(event){ _socketOpened = true; console.log("Socket Opened ", event.message);