From d6c4b72278481766def68c49e28a08640f62d436 Mon Sep 17 00:00:00 2001 From: matzman Date: Mon, 8 Jun 2015 16:25:24 +0200 Subject: [PATCH] . --- .../src/BundleActivator.cpp | 7 + .../webresource/DeviceRepoHandler.cpp | 109 +----- .../webresource/DeviceRepoHandler.h | 4 - src/hos_cosgi_ruleengine/CMakeLists.txt | 4 + .../service/ruleengine/RuleEngineService.h | 6 + .../hos/cosgi/service/ruleengine/rules/Rule.h | 8 +- .../service/ruleengine/rules/RulePackage.h | 3 +- .../rules/conditions/RuleCondition.h | 3 +- .../rules/conditions/RuleConditionImpl.h | 23 +- .../src/BundleActivator.cpp | 35 +- .../ruleengine/RuleEngineServiceImpl.h | 6 +- .../cosgi/service/ruleengine/rules/RuleImpl.h | 11 +- .../ruleengine/rules/RulePackageImpl.h | 3 +- .../webresource/RuleEngineHandler.cpp | 330 +++++++++++++++++- .../webresource/RuleEngineHandler.h | 72 +++- .../service/scripting/ScriptingService.h | 7 +- .../scripting/python/PyScriptingServiceImpl.h | 16 + .../webroot/libs/hos/DeviceRepo.js | 47 ++- .../webroot/libs/hos/RuleEngine.js | 21 ++ src/hos_cosgi_testwebapp/webroot/rule01.html | 52 +++ src/hos_cosgi_testwebapp/webroot/test04.css | 5 + src/hos_cosgi_testwebapp/webroot/test04.html | 122 +++++++ .../webroot/widgettest01.html | 26 +- .../webresource/WebResourceHandlerImpl.h | 315 +++++++++++++---- .../webresource/DirectoryResource.cpp | 1 + .../hos_cpp_utils/hos/utils/FunctionTable.h | 65 ++++ src/hos_deps/cmake/Modules/FindPoco.cmake | 9 +- 27 files changed, 1075 insertions(+), 235 deletions(-) create mode 100644 src/hos_cosgi_testwebapp/webroot/libs/hos/RuleEngine.js create mode 100644 src/hos_cosgi_testwebapp/webroot/rule01.html create mode 100644 src/hos_cosgi_testwebapp/webroot/test04.css create mode 100644 src/hos_cosgi_testwebapp/webroot/test04.html create mode 100644 src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/FunctionTable.h diff --git a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp index 806ae55e..26177c66 100644 --- a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp +++ b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp @@ -112,6 +112,13 @@ celix_status_t bundleActivator_stop(void * userData, bundle_context_pt context) celix_status_t status = CELIX_ILLEGAL_STATE; if (userData != nullptr) { UserData* myUserData = (UserData*)userData; + if (myUserData->webResourceReg != nullptr) { + serviceRegistration_unregister(myUserData->webResourceReg); + myUserData->webResourceReg = nullptr; + } + if (myUserData->webResourceImpl) { + myUserData->webResourceImpl.reset(); + } if (myUserData->serviceReg != nullptr) { serviceRegistration_unregister(myUserData->serviceReg); myUserData->serviceReg = nullptr; 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 29252cf6..4adc2a72 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 @@ -12,10 +12,6 @@ #include #include "Poco/Net/NetException.h" #include -#include -#include -#include -#include @@ -288,103 +284,6 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco: -void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue) { - Variant msg; - bool breakLoop = false; - boost::asio::deadline_timer connCheckTimer(parent->getIOService()); - Net::HTTPServerRequestImpl* reqImpl = static_cast(&req); - std::mutex _m; - std::function connCheckFun = [&](const boost::system::error_code& error) { - if (error != boost::asio::error::operation_aborted) { - std::lock_guard _l(_m); - // How to find out whether the client disconnected: Monitor the socket with Posix "select": - // If select says that there is data, available() returns 0 then apparently the client has disconnected. - // WTF, is this really the only way to notice that a client properly disconnected? Internet says so. - auto sockfs = reqImpl->socket().impl()->sockfd(); - fd_set fds; - FD_ZERO(&fds); - FD_SET(sockfs, &fds); - struct timeval timeout = {0, 1000}; - int sel = select(sockfs + 1, &fds, 0, 0, &timeout); - if (sel < 0 || (sel > 0 && reqImpl->socket().available() == 0)) { - ROS_ERROR_STREAM("Apparently we have a disconnect in device event queue \"" << eventQueue->clientID << "\""); - breakLoop = true; - eventQueue->cancelPopEvent(); - } - connCheckTimer.expires_from_now(boost::posix_time::milliseconds(100)); - connCheckTimer.async_wait(connCheckFun); - } - }; - connCheckTimer.expires_from_now(boost::posix_time::milliseconds(100)); - connCheckTimer.async_wait(connCheckFun); - try { - msg = eventQueue->popEvent(); - } catch (DeviceEventQueue::TimeoutException& e) { - msg["eventtype"] = "timeoutevent"; - } - { - std::lock_guard _l(_m); - breakLoop = true; - connCheckTimer.cancel(); - } - handleResponse(req, resp, msg); -}; - - -void DeviceRepoHandler::__handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue) { - try { - Net::WebSocket ws(req, resp); - std::cout << "WebSocket connection established." << std::endl; - bool breakLoop = false; - - parent->getIOService().post([&]() { - char buffer[1024]; - int flags; - int n; - bool timeout = false; - do { - try { - timeout = false; - n = ws.receiveFrame(buffer, sizeof(buffer), flags); - // ToDo: Handle ping and other shit. - std::cout << Poco::format("Frame received (length=%d, flags=0x%x).", n, unsigned(flags)) << std::endl; - } catch (TimeoutException& e) { - // Timeout may happen, nothing fatal. - timeout = true; - } catch (Net::NetException& e) { - breakLoop= true; - ROS_ERROR_STREAM("Exception in WebSocket receive activity: " << e.what()); - } - } while (timeout || (!breakLoop && n > 0 && (flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE)); - breakLoop= true; - eventQueue->cancelPopEvent(); - }); - - auto format = getResponseDataFormat(req); - while (!breakLoop) { - Variant msg; - msg = eventQueue->popEvent(); - std::string encoded = encodeVariant(msg, req, format); - ws.sendFrame(encoded.c_str(), encoded.size(), Net::WebSocket::FRAME_FLAG_FIN | Net::WebSocket::FRAME_OP_TEXT); - } - std::cout << "WebSocket connection closed." << std::endl; - } catch (Net::WebSocketException& exc) { - std::cout << "WebSocketException caught: " << exc.what() << std::endl; - switch (exc.code()) { - case Net::WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION: - resp.set("Sec-WebSocket-Version", Net::WebSocket::WEBSOCKET_VERSION); - // fallthrough - case Net::WebSocket::WS_ERR_NO_HANDSHAKE: - case Net::WebSocket::WS_ERR_HANDSHAKE_NO_VERSION: - case Net::WebSocket::WS_ERR_HANDSHAKE_NO_KEY: - resp.setStatusAndReason(Net::HTTPResponse::HTTP_BAD_REQUEST); - resp.setContentLength(0); - resp.send(); - break; - } - } -}; - void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket) { std::vector pathPieces, matchPieces; @@ -476,9 +375,9 @@ void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::N } eventQueue->checkRegistration(event, devptr, path.str()); if (websocket) { - __handleEventWebSocket(req, resp, eventQueue.get()); + __handleEventWebSocket(req, resp, eventQueue.get(), parent); } else { - __handleEventLongPolling(req, resp, eventQueue.get()); + __handleEventLongPolling(req, resp, eventQueue.get(), parent); } eventQueue->unsetHandler(); } else { @@ -659,7 +558,9 @@ DeviceAPIMethod DeviceRepoHandler::getDeviceAPIMethod(Net::HTTPServerRequest &re methodRaw = req.get("X-API-Method"); } if (!methodRaw.empty()) { - if (methodRaw == "command") { + if (methodRaw == "get") { + retval = DeviceAPIMethod::GET; + } else if (methodRaw == "command") { retval = DeviceAPIMethod::COMMAND; } else if (methodRaw == "statusupdate") { retval = DeviceAPIMethod::STATUSUPDATE; 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 8fd6127a..1bb0ce23 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 @@ -121,10 +121,6 @@ public: void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket); - void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue); - - void __handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue); - void sendDeviceCommand( devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& cmd, diff --git a/src/hos_cosgi_ruleengine/CMakeLists.txt b/src/hos_cosgi_ruleengine/CMakeLists.txt index 84a865fe..9ae06476 100644 --- a/src/hos_cosgi_ruleengine/CMakeLists.txt +++ b/src/hos_cosgi_ruleengine/CMakeLists.txt @@ -19,6 +19,7 @@ find_package(APR REQUIRED) find_package(CELIX REQUIRED) find_package(Boost REQUIRED) find_package(libvariant REQUIRED) +find_package(Poco REQUIRED COMPONENTS Net NetSSL) ################################### @@ -44,6 +45,7 @@ include_directories( ${APR_INCLUDE_DIR} ${APR_INCLUDE_DIR}/apr-1.0 ${libvariant_INCLUDE_DIR} + ${Poco_INCLUDE_DIRS} ) set(BUNDLE_SYMBOLICNAME "hos.hos_ruleengine") @@ -60,12 +62,14 @@ bundle(hos_cosgi_ruleengine ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rules/conditions/LogicalOrCondition ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rules/conditions/LogicalAndCondition ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/rules/conditions/DeviceMsgCondition + ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler ) target_link_libraries(hos_cosgi_ruleengine ${catkin_LIBRARIES} ${APR_LIBRARIES} ${libvariant_LIBRARY} + ${Poco_LIBRARIES} ) diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/RuleEngineService.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/RuleEngineService.h index e325138d..7b00e678 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/RuleEngineService.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/RuleEngineService.h @@ -68,11 +68,16 @@ enum class RuleEngineEventType : int { RULE_REMOVED, RULE_UPDATED, RULE_STATE_CHANGED, + RULECONDITION_ADDED, + RULECONDITION_REMOVED, + RULECONDITION_UPDATED, + RULECONDITION_STATE_CHANGED, ALL_EVENTS, CHANGE_EVENTS }; + class RuleEngineEvent { public: typedef std::chrono::system_clock::time_point timestamp_t; @@ -86,6 +91,7 @@ public: }; + /** * Public COSGI interface of the rule engine service. */ diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/Rule.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/Rule.h index 94e436f3..55cf5135 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/Rule.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/Rule.h @@ -3,6 +3,7 @@ #include +#include #include @@ -14,6 +15,7 @@ namespace service { // forward declarations namespace devicerepository { class DeviceRepositoryService; } +namespace scripting { class Script; } // main namespace @@ -34,7 +36,7 @@ class RulePackage; * Public COSGI interface of a rule. */ class Rule - : public virtual interface::Object { + : public virtual interface::ObservableObject { public: @@ -91,6 +93,10 @@ public: */ virtual void executeExitScript(const libvariant::Variant& lContext = libvariant::Variant()) = 0; + virtual std::shared_ptr getScript() = 0; + + virtual std::shared_ptr getExitScript() = 0; + /** * Returns whether this rule is enabled. * diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/RulePackage.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/RulePackage.h index e6b5f0ea..94ee3202 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/RulePackage.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/RulePackage.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -40,7 +41,7 @@ class Rule; * Public COSGI interface of a rule package. */ class RulePackage - : public virtual interface::Object { + : public virtual interface::ObservableObject { public: diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleCondition.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleCondition.h index 903b2162..65dbc158 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleCondition.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleCondition.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -50,7 +51,7 @@ enum class ConditionTriggerType : int { * Public COSGI interface of a rule condition. */ class RuleCondition - : public virtual interface::Object { + : public virtual interface::ObservableObject { public: diff --git a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleConditionImpl.h b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleConditionImpl.h index 3d682b41..ae1f0f1d 100644 --- a/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleConditionImpl.h +++ b/src/hos_cosgi_ruleengine/include/hos_cosgi_ruleengine/hos/cosgi/service/ruleengine/rules/conditions/RuleConditionImpl.h @@ -4,8 +4,29 @@ #include "RuleCondition.h" #include +#include +// Quick and dirty workaround +// ToDo: Find better solution +#ifndef _STD_HASH_RULEENGINEEVENTTYPE_ +#define _STD_HASH_RULEENGINEEVENTTYPE_ +namespace std { +/** + * Specialization of std::hash for RuleEngineEventType. + * + * Required by std::map. + */ +template <> +class hash { +public : + size_t operator()(const hos::cosgi::service::ruleengine::RuleEngineEventType& x) const { + return static_cast(x); + } +}; +} // namespace std +#endif + // main namespace namespace hos { @@ -22,7 +43,7 @@ namespace conditions { */ class RuleConditionImpl : public virtual RuleCondition, - public implementation::Object { + public implementation::ObservableObject { protected: diff --git a/src/hos_cosgi_ruleengine/src/BundleActivator.cpp b/src/hos_cosgi_ruleengine/src/BundleActivator.cpp index 7a14b530..ab56daff 100644 --- a/src/hos_cosgi_ruleengine/src/BundleActivator.cpp +++ b/src/hos_cosgi_ruleengine/src/BundleActivator.cpp @@ -13,6 +13,7 @@ #include // CoreService #include "hos/cosgi/service/ruleengine/RuleEngineServiceImpl.h" +#include "hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.h" #include #include @@ -23,13 +24,16 @@ using namespace hos::cosgi::service::scripting; using namespace hos::cosgi::service::devicerepository; using namespace hos::cosgi::service::core; using namespace hos::cosgi; +namespace ruleweb = hos::cosgi::service::ruleengine::webresource; struct UserData { apr_pool_t* memoryPool = nullptr; - std::unique_ptr serviceImpl = nullptr; + std::unique_ptr serviceImpl; + std::unique_ptr webResourceImpl; service_registration_pt serviceReg = nullptr; + service_registration_pt webResourceReg = nullptr; serviceListener myServiceListener; bundle_context_pt bundleContext; }; @@ -56,10 +60,16 @@ celix_status_t cb_serviceChanged(void* listener, service_event_pt event) { checkReg = true; } - // Register rule engine service + // Register rule engine service and webresource if (checkReg && !myUserData->serviceReg && myUserData->serviceImpl->isInitialized()) { RuleEngineService* engineInterface = dynamic_cast(myUserData->serviceImpl.get()); bundleContext_registerService(myUserData->bundleContext, (char*)RuleEngineServiceImpl::SERVICE_NAME, engineInterface, nullptr, &myUserData->serviceReg); + // web resource + service::webserver::WebResourceHandlerFactory* webInterface = dynamic_cast(myUserData->webResourceImpl.get()); + // ToDo: Properly delete properties + properties_pt webResourceProps = properties_create(); + properties_set(webResourceProps, (char*)"path", (char*)"/ruleengine0"); + bundleContext_registerService(myUserData->bundleContext, (char*)service::webserver::WebResourceHandlerFactory::SERVICE_NAME, webInterface, webResourceProps, &myUserData->webResourceReg); } } else if (event->type == OSGI_FRAMEWORK_SERVICE_EVENT_UNREGISTERING) { @@ -110,6 +120,12 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) } myUserData->serviceImpl.reset(new RuleEngineServiceImpl()); + // Create rule engine webresource + if (myUserData->webResourceReg != nullptr) { + serviceRegistration_unregister(myUserData->webResourceReg); + } + myUserData->webResourceImpl.reset(new ruleweb::RuleEngineHandlerFactory(myUserData->serviceImpl.get())); + // Get core Service service_reference_pt ref = nullptr; bundleContext_getServiceReference(context, (char*)CoreService::SERVICE_NAME, &ref); @@ -130,10 +146,16 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) myUserData->serviceImpl->setRepoService(repoService); } - // Register rule engine service + // Register rule engine service and webresource if (myUserData->serviceImpl->isInitialized()) { RuleEngineService* engineInterface = dynamic_cast(myUserData->serviceImpl.get()); bundleContext_registerService(context, (char*)RuleEngineServiceImpl::SERVICE_NAME, engineInterface, nullptr, &myUserData->serviceReg); + // web resource + service::webserver::WebResourceHandlerFactory* webInterface = dynamic_cast(myUserData->webResourceImpl.get()); + // ToDo: Properly delete properties + properties_pt webResourceProps = properties_create(); + properties_set(webResourceProps, (char*)"path", (char*)"/ruleengine0"); + bundleContext_registerService(context, (char*)service::webserver::WebResourceHandlerFactory::SERVICE_NAME, webInterface, webResourceProps, &myUserData->webResourceReg); } // Register service listener @@ -155,6 +177,13 @@ celix_status_t bundleActivator_stop(void * userData, bundle_context_pt context) if (userData != nullptr) { UserData* myUserData = (UserData*)userData; status = bundleContext_removeServiceListener(context, &myUserData->myServiceListener); + if (myUserData->webResourceReg != nullptr) { + serviceRegistration_unregister(myUserData->webResourceReg); + myUserData->webResourceReg = nullptr; + } + if (myUserData->webResourceImpl) { + myUserData->webResourceImpl.reset(); + } if (myUserData->serviceReg != nullptr) { serviceRegistration_unregister(myUserData->serviceReg); myUserData->serviceReg = nullptr; diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.h index a7358386..f4fd3041 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.h @@ -21,7 +21,10 @@ #include #include - +// Quick and dirty workaround +// ToDo: Find better solution +#ifndef _STD_HASH_RULEENGINEEVENTTYPE_ +#define _STD_HASH_RULEENGINEEVENTTYPE_ namespace std { /** * Specialization of std::hash for RuleEngineEventType. @@ -36,6 +39,7 @@ public : } }; } // namespace std +#endif // Service Namespace diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RuleImpl.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RuleImpl.h index 4888fc52..c0950b2d 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RuleImpl.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RuleImpl.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "RulePackageImpl.h" #include #include @@ -31,7 +32,7 @@ using namespace libvariant; */ class RuleImpl : public virtual Rule, - public implementation::Object { + public implementation::ObservableObject { private: @@ -74,6 +75,14 @@ public: virtual void executeExitScript(const libvariant::Variant& lContext = libvariant::Variant()) override; + virtual std::shared_ptr getScript() override { + return script; + } + + virtual std::shared_ptr getExitScript() override { + return exitScript; + } + virtual bool isEnabled() noexcept override { return enabled; } diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RulePackageImpl.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RulePackageImpl.h index 27528712..858622d3 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RulePackageImpl.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/rules/RulePackageImpl.h @@ -4,6 +4,7 @@ #include #include +#include #include "../RuleEngineServiceImpl.h" #include #include @@ -33,7 +34,7 @@ using namespace ::libvariant; */ class RulePackageImpl : public virtual RulePackage, - public implementation::Object { + public implementation::ObservableObject { private: diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.cpp index 411d3e12..f8336d42 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.cpp +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.cpp @@ -1,10 +1,328 @@ -/* - * RuleEngineHandler.cpp - * - * Created on: 05.06.2015 - * Author: matthias - */ + +#include "RuleEngineHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Poco/Net/NetException.h" +#include +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace ruleengine { +namespace webresource { + + + +using namespace Poco; +using namespace libvariant; + + + +HOS_SERVICE_IMPL_INFO( RuleEngineHandlerFactory, // Implementation + service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name + 1, 0 ) // Version + + +RuleEngineHandlerFactory::RuleEngineHandlerFactory(RuleEngineService* ruleService) + : ruleService(ruleService), + pkgInfoFuncs([](const std::string& key)->pkgInfoFuncs_t::Func_t { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown category \"%1%\"") % key).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + }), + ruleInfoFuncs([](const std::string& key)->ruleInfoFuncs_t::Func_t { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown category \"%1%\"") % key).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + }) { + + pkgInfoFuncs.add("id", [](rules::RulePackage* pkg, const std::string& category)->Variant { + return pkg->getName(); + }); + + pkgInfoFuncs.add("language", [](rules::RulePackage* pkg, const std::string& category)->Variant { + return pkg->getLanguage(); + }); + + pkgInfoFuncs.add("enabled", [](rules::RulePackage* pkg, const std::string& category)->Variant { + return pkg->isEnabled(); + }); + + pkgInfoFuncs.add("globalcontext", [](rules::RulePackage* pkg, const std::string& category)->Variant { + auto gc = pkg->getGlobalScriptContext(); + if (!gc.expired()) { + return gc.lock()->getJSONRepresentation(); + } else { + return libvariant::Variant::NullType; + } + }); + + pkgInfoFuncs.add("rules", [this](rules::RulePackage* pkg, const std::string& category)->Variant { + Variant retval = libvariant::Variant::List(); + for (auto r : pkg->getRules()) { + if (!r.expired()) { + retval.Append(getRuleInfo(r.lock().get())); + } + } + return retval; + }); + + + ruleInfoFuncs.add("id", [](rules::Rule* rule, const std::string& category)->Variant { + return rule->getName(); + }); + + ruleInfoFuncs.add("enabled", [](rules::Rule* rule, const std::string& category)->Variant { + return rule->isEnabled(); + }); + + ruleInfoFuncs.add("script", [](rules::Rule* rule, const std::string& category)->Variant { + auto script = rule->getScript(); + if (script) { + return script->getJSONRepresentation(); + } else { + return Variant::NullType; + } + }); + + ruleInfoFuncs.add("exitscript", [](rules::Rule* rule, const std::string& category)->Variant { + auto script = rule->getExitScript(); + if (script) { + return script->getJSONRepresentation(); + } else { + return Variant::NullType; + } + }); + + ruleInfoFuncs.add("condition", [](rules::Rule* rule, const std::string& category)->Variant { + return "ToDo"; + }); + +} + + + +Net::HTTPRequestHandler* RuleEngineHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { + return new RuleEngineHandler( this, matchUri, ruleService, ssl); +} + + + +Variant RuleEngineHandlerFactory::getRulePkgInfo( rules::RulePackage* pkg, const std::string& category, const std::string& path ) { + Variant retval; + // We want all info categories + if (category.empty()) { + pkgInfoFuncs.forEach([&](const std::string& key, const Variant& value){ + retval[key] = value; + }, pkg, category); + + // We want a single info category + } else { + retval = pkgInfoFuncs.call(category, pkg, category); + if (!path.empty()) { + if (retval.HasPath(path)) { + retval = retval.GetPath(path); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for rule package info category \"%2%\".") % path % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + } + return retval; +} + + + +Variant RuleEngineHandlerFactory::getRuleInfo( rules::Rule* rule, const std::string& category, const std::string& path ) { + Variant retval; + // We want all info categories + if (category.empty()) { + ruleInfoFuncs.forEach([&](const std::string& key, const Variant& value){ + retval[key] = value; + }, rule, category); + + // We want a single info category + } else { + retval = ruleInfoFuncs.call(category, rule, category); + if (!path.empty()) { + if (retval.HasPath(path)) { + retval = retval.GetPath(path); + } else { + throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for rule info category \"%2%\".") % path % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + } + return retval; +} + + + +void RuleEngineHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) { + //__handleEvent(req, resp, true); + /*throw webserver::webresource::WebResourceHandlerException("ToDo", + Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);*/ + } else { + 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) { + RuleEngineAPIMethod method = getRuleEngineAPIMethod(req); + if (method == RuleEngineAPIMethod::GET) { + __handleInfo(req, resp); + } else if (method == RuleEngineAPIMethod::SET) { + //__handleCommand(req, resp, method); + } else if (method == RuleEngineAPIMethod::EVENT) { + //__handleEvent(req, resp, false); + } else { + throw webserver::webresource::WebResourceHandlerException("Could not understood request", + Net::HTTPResponse::HTTP_BAD_REQUEST); + } + } else { + throw webserver::webresource::WebResourceHandlerException("Could not understood request", + Net::HTTPResponse::HTTP_BAD_REQUEST); + } + } +} + + + +void RuleEngineHandler::__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)); + } + + // Nothing given, query mode (there is no real query functionality implemented yet though) + if (pathPieces.size() <= matchPieces.size()) { + auto pkgs = ruleService->getRulePackages(); + + // Parse HTTP header "Range" parameter (to support dojo/store) + size_t rangeStart = 0; + size_t rangeStop = pkgs.size() - 1; + std::string rangeRaw; + 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 << "\"."); + } + } + } + } + + // Iterate over packages + Variant result = Variant::List(); + if (pkgs.size() > 0) { + for (size_t t = rangeStart; t <= rangeStop; t++) { + auto pkg = pkgs[t].lock(); + result.Append(parent->getRulePkgInfo(pkg.get())); + } + } + + // 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 % pkgs.size()).str(); + resp.set("Content-Range", rangeOut); + resp.set("X-Content-Range", rangeOut); + handleResponse(req, resp, result); + } + + // rule package given + } else if (pathPieces.size() == matchPieces.size() + 1) { + + // rule given + } else if (pathPieces.size() == matchPieces.size() + 2) { + + } +} + + + +RuleEngineAPIMethod RuleEngineHandler::getRuleEngineAPIMethod(Net::HTTPServerRequest &req) noexcept { + std::unique_ptr form; + RuleEngineAPIMethod retval = RuleEngineAPIMethod::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 == "get") { + retval = RuleEngineAPIMethod::GET; + } else if (methodRaw == "set") { + retval = RuleEngineAPIMethod::SET; + } else if (methodRaw == "event") { + retval = RuleEngineAPIMethod::EVENT; + } else { + throw webserver::webresource::WebResourceHandlerException( + (boost::format("Unknown rule engine api method \"%1%\"") % methodRaw).str(), + Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST ); + } + } else if (form->has("onevent") || req.has("On-Event") || req.has("X-On-Event")) { + retval = RuleEngineAPIMethod::EVENT; + } + return retval; +} + + + +} // namespace webresource +} // namespace ruleengine +} // namespace service +} // namespace cosgi +} // namespace hos diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.h b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.h index 64f8eb59..078e748f 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.h +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/webresource/RuleEngineHandler.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -42,33 +43,59 @@ enum class RuleEngineAPIMethod { +typedef webserver::webresource::WebResourceEventQueue< + interface::Observer, + RuleEngineHandlerFactory, + RuleEngineHandler +> RuleEngineEventQueueBase; + +class RuleEngineEventQueue + : public RuleEngineEventQueueBase { +public: + RuleEngineEventType eventType = RuleEngineEventType::ALL_EVENTS; + RuleEngineService* const ruleService; + + RuleEngineEventQueue( const std::string& clientID, + RuleEngineHandlerFactory* handlerFactory, + RuleEngineService* ruleService ) + : WebResourceEventQueue(clientID, handlerFactory), ruleService(ruleService) {} + virtual ~RuleEngineEventQueue() { + unregisterEvents(); + } + + /*void checkRegistration(RuleEngineEventType eventType, Device* device, const std::string& pointer); + void registerEvents(RuleEngineEventType eventType, Device* device = nullptr);*/ + void unregisterEvents() { + } + + virtual void notificationReceived( interface::Observer::observable_type* o, + const RuleEngineEventType& key, + const RuleEngineEvent* e) noexcept override { + } +}; + + class RuleEngineHandlerFactory - : public virtual webserver::WebResourceHandlerFactory, - public implementation::Service { + : public webserver::webresource::WebResourceHandlerFactoryImpl { +public: + typedef std::function pkgInfoFunc_t; + typedef std::function ruleInfoFuncFunc_t; private: - webserver::WebServerService* service = nullptr; + typedef utils::FunctionTable pkgInfoFuncs_t; + typedef utils::FunctionTable ruleInfoFuncs_t; RuleEngineService* ruleService; - boost::asio::io_service io_service; - std::unique_ptr io_service_thread; + pkgInfoFuncs_t pkgInfoFuncs; + ruleInfoFuncs_t ruleInfoFuncs; public: RuleEngineHandlerFactory(RuleEngineService* ruleService); - ~RuleEngineHandlerFactory(); 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; - } + libvariant::Variant getRulePkgInfo( rules::RulePackage* pkg, const std::string& category = "", const std::string& path = "" ); - virtual bool excludeFromAuthentication() noexcept override { - return false; - } - - boost::asio::io_service& getIOService() { - return io_service; - } + libvariant::Variant getRuleInfo( rules::Rule* pkg, const std::string& category = "", const std::string& path = "" ); }; @@ -82,6 +109,7 @@ protected: RuleEngineService* ruleService; bool ssl; + public: RuleEngineHandler( RuleEngineHandlerFactory* parent, @@ -92,11 +120,23 @@ public: virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp); + + void __handleSet(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, RuleEngineAPIMethod method); + + void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket); + static RuleEngineAPIMethod getRuleEngineAPIMethod(Poco::Net::HTTPServerRequest &req) noexcept; + + /*void sendDeviceCommand( devicerepository::Device* device, + Poco::Net::HTTPServerRequest &req, + const std::string& cmd, + const std::vector& path = std::vector() );*/ }; + } // namespace webresource } // namespace ruleengine } // namespace service diff --git a/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/ScriptingService.h b/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/ScriptingService.h index e7f73239..fc3e5b35 100644 --- a/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/ScriptingService.h +++ b/src/hos_cosgi_scripting/include/hos_cosgi_scripting/hos/cosgi/service/scripting/ScriptingService.h @@ -25,7 +25,10 @@ namespace scripting { * Currently it is just an opaque handle that is passed to the script execution handle. */ class GlobalScriptContext - : public virtual interface::Object {}; + : public virtual interface::Object { +public: + virtual libvariant::Variant getJSONRepresentation() = 0; +}; @@ -49,6 +52,8 @@ public: */ virtual void execute( std::shared_ptr gContext = std::shared_ptr(), const libvariant::Variant& lContext = libvariant::Variant() ) = 0; + + virtual libvariant::Variant getJSONRepresentation() = 0; }; diff --git a/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.h b/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.h index 119a5761..32f6d145 100644 --- a/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.h +++ b/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.h @@ -53,6 +53,16 @@ public: PyGlobalScriptContext(PyScriptingService* service, const boost::python::object& gContext, const std::string& code) : service(service), gContext(gContext), code(code) {} + + /* Public Service Implementations */ + + virtual libvariant::Variant getJSONRepresentation() override { + libvariant::Variant retval; + retval["code"] = code; + return retval; + } + + /* Internal */ const std::string& getScriptCode() noexcept { @@ -90,6 +100,12 @@ public: virtual void execute( std::shared_ptr gContext = std::shared_ptr(), const libvariant::Variant& lContext = libvariant::Variant() ) override; + virtual libvariant::Variant getJSONRepresentation() override { + libvariant::Variant retval; + retval["code"] = code; + return retval; + } + /* Internal */ diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/DeviceRepo.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/DeviceRepo.js index dc8bb29e..455aaad3 100644 --- a/src/hos_cosgi_testwebapp/webroot/libs/hos/DeviceRepo.js +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/DeviceRepo.js @@ -1,23 +1,32 @@ define([ "dojo/_base/config", - "dojo/store/JsonRest", - 'dojo/store/Observable', + 'dojo/_base/declare', + 'dstore/Rest', + 'dstore/RequestMemory', + 'dstore/SimpleQuery', + 'dstore/Trackable', "dojox/socket", - "dojo/_base/xhr" -], function(config, JsonRest, Observable, Socket, xhr){ - var _store = new Observable(new JsonRest({ + "dojo/_base/xhr", + "dojox/uuid/generateRandomUuid" +], function(config, declare, Rest, RequestMemory, SimpleQuery, Trackable, Socket, xhr, generateRandomUuid){ + + var RuleEngineStore = declare([Rest, RequestMemory, SimpleQuery, Trackable]); + + var _store = new RuleEngineStore({ target: config.hos.deviceRepoUrl - })); + }); + var _updateSocketOpened = false; + var _updateSocketClientId = generateRandomUuid(); var _updateSocket = new Socket({ - url: config.hos.deviceRepoUrl + '?onevent=changes&clientid=1234', - headers: { - "On-Event": "changes", - "Connection-Timeout": 60000, - "Client-Id": 1234 - }, - transport: xhr.post }); - _updateSocket = Observable(_updateSocket); + url: config.hos.deviceRepoUrl + '?onevent=changes&clientid=' + _updateSocketClientId, + headers: { + "On-Event": "changes", + "Connection-Timeout": 60000, + "Client-Id": 1234 + }, + transport: xhr.post + }); _updateSocket.on("open", function(event){ _socketOpened = true; console.log("Socket Opened ", event.message); @@ -33,9 +42,10 @@ define([ _updateSocket.on("message", function(event) { var data = JSON.parse(event.data); switch (data['eventtype']) { - case "statechanged": - _store.notify(data['device'], data['deviceid']); - break; + case "statechanged": + _store.evict(data['deviceid']); + _store.emit('update', {id: data['deviceid'], target: data['device']}); + break; } console.log("Message: " + data); }); @@ -51,5 +61,6 @@ define([ // browser supports XHR2 }); }; + return _store; -}); \ No newline at end of file +}); diff --git a/src/hos_cosgi_testwebapp/webroot/libs/hos/RuleEngine.js b/src/hos_cosgi_testwebapp/webroot/libs/hos/RuleEngine.js new file mode 100644 index 00000000..42f3dac2 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/libs/hos/RuleEngine.js @@ -0,0 +1,21 @@ +define([ + "dojo/_base/config", + 'dojo/_base/declare', + 'dstore/Rest', + 'dstore/RequestMemory', + 'dstore/SimpleQuery', + 'dstore/Trackable', + 'dstore/Tree', + "dojox/socket", + "dojo/_base/xhr", + "dojox/uuid/generateRandomUuid" +], function(config, declare, Rest, RequestMemory, SimpleQuery, Trackable, Tree, Socket, xhr, generateRandomUuid){ + + var RuleEngineStore = declare([Rest, RequestMemory, SimpleQuery, Trackable, Tree]); + + var _store = new RuleEngineStore({ + target: config.hos.ruleEngineUrl + }); + + return _store; +}); diff --git a/src/hos_cosgi_testwebapp/webroot/rule01.html b/src/hos_cosgi_testwebapp/webroot/rule01.html new file mode 100644 index 00000000..7d591714 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/rule01.html @@ -0,0 +1,52 @@ + + + + + HOS Widget Test + + + + + + + + + + + +

HOS Rules Test

+
  • + + + + + + + diff --git a/src/hos_cosgi_testwebapp/webroot/test04.css b/src/hos_cosgi_testwebapp/webroot/test04.css new file mode 100644 index 00000000..6f5b0efe --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/test04.css @@ -0,0 +1,5 @@ +.claro .dijitToggleButtonChecked .dijitButtonNode { + background-color: #0090ff; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2) inset; + transition-duration: 0.1s; +} \ No newline at end of file diff --git a/src/hos_cosgi_testwebapp/webroot/test04.html b/src/hos_cosgi_testwebapp/webroot/test04.html new file mode 100644 index 00000000..d71c5745 --- /dev/null +++ b/src/hos_cosgi_testwebapp/webroot/test04.html @@ -0,0 +1,122 @@ + + + + + Tutorial: Hello Dojo! + + + + + + + + + + + + +

    Switches

    +
  • + + + + + + + diff --git a/src/hos_cosgi_testwebapp/webroot/widgettest01.html b/src/hos_cosgi_testwebapp/webroot/widgettest01.html index db2cf06c..e052491a 100644 --- a/src/hos_cosgi_testwebapp/webroot/widgettest01.html +++ b/src/hos_cosgi_testwebapp/webroot/widgettest01.html @@ -50,7 +50,8 @@ var self = this; - devices = DeviceRepo.query(); + _devices = DeviceRepo.filter().track(); + devices = _devices.fetch(); devices.then(function(data){ console.log("Data: ", data); for (var i = 0; i < data.length; i++) { @@ -101,19 +102,18 @@ console.log("Update: ", update); } ); - devices.observe(function(device, removedFrom, insertedInto){ + _devices.on('update', function(event){ // this will be called any time a market is added, removed, or updated - console.log("Observe triggered: ", device, ', ', removedFrom, ', ', insertedInto); - if (removedFrom == insertedInto) { - if (device.type == "core/devices/switch") { - var b = widgets[device.id]; - b.set('checked', device.current_state.switch.value); - } else if (device.type == "core/devices/dimmer") { - var s = widgets[device.id]; - s.set('value', device.current_state.dimmer.value); - } - } - }, true); // we can indicate to be notified of object updates as well + console.log("Observe triggered: ", event); + var device = event.target; + if (device.type == "core/devices/switch") { + var b = widgets[device.id]; + b.set('checked', device.current_state.switch.value); + } else if (device.type == "core/devices/dimmer") { + var s = widgets[device.id]; + s.set('value', device.current_state.dimmer.value); + } + }); // we can indicate to be notified of object updates as well }); 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 3cb29986..4512f18a 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 @@ -21,6 +21,16 @@ #include #include +#include +#include +#include +#include "Poco/Net/NetException.h" +#include +#include +#include +#include +#include + // service namespace @@ -279,7 +289,7 @@ public: -enum class ResponseDataFormat : int { +enum class DataFormat : int { JSON, JSONP, YAML, @@ -298,47 +308,87 @@ protected: class NotImplementedException : public std::exception {}; - ResponseDataFormat getResponseDataFormat(const Poco::Net::HTTPServerRequest& request) { - ResponseDataFormat retval = ResponseDataFormat::JSON; + DataFormat getDataFormat(const Poco::Net::HTTPServerRequest& request, bool incoming = false) { + DataFormat retval = DataFormat::JSON; // defaults to json Poco::Net::HTMLForm form(request); if (form.has("format")) { std::string formatRaw = form.get("format"); if (formatRaw.compare("json") == 0) { - // do nothing + retval = DataFormat::JSON; } else if (formatRaw.compare("jsonp") == 0) { - retval = ResponseDataFormat::JSONP; + retval = DataFormat::JSONP; } else if (formatRaw.compare("yaml") == 0) { - retval = ResponseDataFormat::YAML; + retval = DataFormat::YAML; } else if (formatRaw.compare("xmlplist") == 0) { - retval = ResponseDataFormat::XMLPLIST; + retval = DataFormat::XMLPLIST; } else if (formatRaw.compare("bundlehdr") == 0) { - retval = ResponseDataFormat::BUNDLEHDR; + retval = DataFormat::BUNDLEHDR; } else if (formatRaw.compare("msgpack") == 0) { - retval = ResponseDataFormat::MSGPACK; + retval = DataFormat::MSGPACK; } else { throw WebResourceHandlerException((boost::format("Unknown format parameter \"%1%\"") % formatRaw).str(), Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); } + } else if (form.has("json")) { + retval = DataFormat::JSON; + } else if (form.has("jsonp")) { + retval = DataFormat::JSONP; + } else if (form.has("yaml")) { + retval = DataFormat::YAML; + } else if (form.has("xmlplist")) { + retval = DataFormat::XMLPLIST; + } else if (form.has("bundlehdr")) { + retval = DataFormat::BUNDLEHDR; + } else if (form.has("msgpack")) { + retval = DataFormat::MSGPACK; + } else if (incoming) { + if (request.getContentType() == "application/json") { + retval = DataFormat::JSON; + } else if (request.getContentType() == "application/javascript") { + retval = DataFormat::JSONP; + } else if (request.getContentType() == "application/yaml") { + retval = DataFormat::YAML; + } else if (request.getContentType() == "application/xml") { + retval = DataFormat::XMLPLIST; + } else if (request.getContentType() == "application/msgpack") { + retval = DataFormat::MSGPACK; + } else if (request.getContentType() == "application/bundlehdr") { + retval = DataFormat::BUNDLEHDR; + } + } else { // outgoing + if (request.hasToken("Accept", "application/json")) { + retval = DataFormat::JSON; + } else if (request.hasToken("Accept", "application/javascript")) { + retval = DataFormat::JSONP; + } else if (request.hasToken("Accept", "application/yaml")) { + retval = DataFormat::YAML; + } else if (request.hasToken("Accept", "application/xml")) { + retval = DataFormat::XMLPLIST; + } else if (request.hasToken("Accept", "application/msgpack")) { + retval = DataFormat::MSGPACK; + } else if (request.hasToken("Accept", "application/bundlehdr")) { + retval = DataFormat::BUNDLEHDR; + } } return retval; } std::string encodeVariant( const libvariant::Variant& msg, const Poco::Net::HTTPServerRequest& request, - ResponseDataFormat format = ResponseDataFormat::AUTO_GUESS ) { - if (format == ResponseDataFormat::AUTO_GUESS) { - format = getResponseDataFormat(request); + DataFormat format = DataFormat::AUTO_GUESS ) { + if (format == DataFormat::AUTO_GUESS) { + format = getDataFormat(request); } switch (format) { - case ResponseDataFormat::JSON: + case DataFormat::JSON: return libvariant::SerializeJSON(msg, true); - case ResponseDataFormat::JSONP: + case DataFormat::JSONP: { Poco::Net::HTMLForm form(request); - std::string padding; + std::string padding("callback"); if (form.has("callback")) { padding = form.get("callback"); - } else if (form.has("jsonp")) { + } else if (form.has("jsonp") && !form.get("jsonp").empty()) { padding = form.get("jsonp"); } if (padding.empty()) { @@ -349,13 +399,13 @@ protected: ss << padding << "(" << libvariant::SerializeJSON(msg, false) << ")"; return ss.str(); } - case ResponseDataFormat::YAML: + case DataFormat::YAML: return libvariant::SerializeYAML(msg); - case ResponseDataFormat::XMLPLIST: + case DataFormat::XMLPLIST: return libvariant::SerializeXMLPLIST(msg, true); - case ResponseDataFormat::BUNDLEHDR: + case DataFormat::BUNDLEHDR: return libvariant::SerializeBundleHdr(msg); - case ResponseDataFormat::MSGPACK: + case DataFormat::MSGPACK: return libvariant::Serialize(msg, libvariant::SERIALIZE_MSGPACK); default: throw WebResourceHandlerException("Internal Server Error", @@ -363,80 +413,122 @@ protected: } } - void handleResponse( const Poco::Net::HTTPServerRequest& request, - Poco::Net::HTTPServerResponse &resp, - const libvariant::Variant& msg, - const std::string& mime = "text/plain", - Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK ) { + std::string getContentTypeString(DataFormat format) { + if (format == DataFormat::JSON) { + return "application/json"; + } else if (format == DataFormat::JSONP) { + return "application/javascript"; + } else if (format == DataFormat::YAML) { + return "application/yaml"; + } else if (format == DataFormat::XMLPLIST) { + return "application/xml"; + } else { + return ""; + } + } + + void setContentType(Poco::Net::HTTPServerResponse &resp, DataFormat format) { + auto type = getContentTypeString(format); + if (!type.empty()) { + resp.setContentType(type); + } + } + + void handleResponse( Poco::Net::HTTPServerRequest& request, + Poco::Net::HTTPServerResponse &resp, + const libvariant::Variant& msg, + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK, + const std::string& mime = "" ) { + resp.setStatusAndReason(status); + auto format = getDataFormat(request); + if (mime.empty()) { + setContentType(resp, format); + } else { + resp.setContentType(mime); + } + std::stringstream ss; + ss << encodeVariant(msg, request, format); + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); + } + + void handleResponse( Poco::Net::HTTPServerRequest& request, + Poco::Net::HTTPServerResponse &resp, + const std::string& msg, + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK, + const std::string& mime = "text/plain" ) { resp.setStatusAndReason(status); resp.setContentType(mime); - auto format = getResponseDataFormat(request); - resp.send() << encodeVariant(msg, request, format); + resp.sendBuffer(msg.c_str(), msg.size()); } - void handleResponse( const Poco::Net::HTTPServerRequest& request, + void handleErrorResponse( Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse &resp, const std::string& msg, - const std::string& mime = "text/plain", - Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK ) { + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR, + const std::string& mime = "text/plain" ) { resp.setStatusAndReason(status); resp.setContentType(mime); - resp.send() << msg; + std::stringstream ss; + ss << "ERROR: " << msg; + addDebugInfo(ss, request, resp); + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); } - void handleErrorResponse( const Poco::Net::HTTPServerRequest& request, - Poco::Net::HTTPServerResponse &resp, - const std::string& msg, - Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) { - resp.setStatusAndReason(status); - resp.setContentType("text/plain"); - std::ostream& out = resp.send(); - out << "ERROR: " << msg; - addDebugInfo(out, request, resp); - } - - void handleErrorResponse( const Poco::Net::HTTPServerRequest& request, + void handleErrorResponse( Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) { resp.setStatusAndReason(status); resp.setContentType("text/plain"); - std::ostream& out = resp.send(); - out << "ERROR: " << resp.getReason(); - addDebugInfo(out, request, resp); + std::stringstream ss; + ss << "ERROR: " << resp.getReason(); + addDebugInfo(ss, request, resp); + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); } - void handleErrorResponse( const Poco::Net::HTTPServerRequest& request, + void handleErrorResponse( Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse &resp, const WebResourceHandlerException& e ) { resp.setStatusAndReason(e.getStatusCode()); resp.setContentType("text/plain"); - std::ostream& out = resp.send(); - out << "ERROR: " << e.what(); - addDebugInfo(out, request, resp); + std::stringstream ss; + ss << "ERROR: " << e.what(); + addDebugInfo(ss, request, resp); + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); } - void handleErrorResponse( const Poco::Net::HTTPServerRequest& request, + void handleErrorResponse( Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse &resp, const std::exception& e, Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) { resp.setStatusAndReason(status); resp.setContentType("text/plain"); - std::ostream& out = resp.send(); - out << "ERROR: " << e.what(); - addDebugInfo(out, request, resp); + std::stringstream ss; + ss << "ERROR: " << e.what(); + addDebugInfo(ss, request, resp); + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); } - void handleRequireAuthenticationResponse( const Poco::Net::HTTPServerRequest& request, - Poco::Net::HTTPServerResponse &resp, - const std::string& realm, - const std::string& body = std::string(), - Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED ) { + void handleRequireAuthenticationResponse( Poco::Net::HTTPServerRequest& request, + Poco::Net::HTTPServerResponse &resp, + const std::string& realm, + const std::string& body = "", + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED, + const std::string& mime = "text/plain" ) { resp.requireAuthentication(realm); + resp.setContentType(mime); + std::stringstream ss; if (body.empty()) { - resp.send() << "ERROR: " << resp.getReason(); + ss << "ERROR: " << resp.getReason(); } else { - resp.send() << body; + ss << body; } + std::string buffer = ss.str(); + resp.sendBuffer(buffer.c_str(), buffer.size()); } void addDebugInfo(std::ostream& out, const Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse &resp) { @@ -500,6 +592,107 @@ protected: } } + template + void __handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, EventQueue* eventQueue, HandlerFactory* factory) { + try { + Poco::Net::WebSocket ws(req, resp); + std::cout << "WebSocket connection established." << std::endl; + bool breakLoop = false; + + factory->getIOService().post([&]() { + char buffer[1024]; + int flags; + int n; + bool timeout = false; + do { + try { + timeout = false; + n = ws.receiveFrame(buffer, sizeof(buffer), flags); + // ToDo: Handle ping and other shit. + std::cout << Poco::format("Frame received (length=%d, flags=0x%x).", n, unsigned(flags)) << std::endl; + } catch (typename EventQueue::TimeoutException& e) { + // Timeout may happen, nothing fatal. + timeout = true; + } catch (Poco::Net::NetException& e) { + breakLoop= true; + ROS_ERROR_STREAM("Exception in WebSocket receive activity: " << e.what()); + } catch (std::exception& e) { + ROS_ERROR_STREAM("WebServer Receive Exception caught: " << e.what()); + } + } while (timeout || (!breakLoop && n > 0 && (flags & Poco::Net::WebSocket::FRAME_OP_BITMASK) != Poco::Net::WebSocket::FRAME_OP_CLOSE)); + breakLoop= true; + eventQueue->cancelPopEvent(); + }); + + while (!breakLoop) { + libvariant::Variant msg; + msg = eventQueue->popEvent(); + std::string encoded = encodeVariant(msg, req); + ws.sendFrame(encoded.c_str(), encoded.size(), Poco::Net::WebSocket::FRAME_FLAG_FIN | Poco::Net::WebSocket::FRAME_OP_TEXT); + } + std::cout << "WebSocket connection closed." << std::endl; + } catch (Poco::Net::WebSocketException& exc) { + std::cout << "WebSocketException caught: " << exc.what() << std::endl; + switch (exc.code()) { + case Poco::Net::WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION: + resp.set("Sec-WebSocket-Version", Poco::Net::WebSocket::WEBSOCKET_VERSION); + // fallthrough + case Poco::Net::WebSocket::WS_ERR_NO_HANDSHAKE: + case Poco::Net::WebSocket::WS_ERR_HANDSHAKE_NO_VERSION: + case Poco::Net::WebSocket::WS_ERR_HANDSHAKE_NO_KEY: + resp.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); + resp.setContentLength(0); + resp.send(); + break; + } + } catch (std::exception& e) { + ROS_ERROR_STREAM("WebServer Send Exception caught: " << e.what()); + } + } + + template + void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, EventQueue* eventQueue, HandlerFactory* factory) { + libvariant::Variant msg; + bool breakLoop = false; + boost::asio::deadline_timer connCheckTimer(factory->getIOService()); + Poco::Net::HTTPServerRequestImpl* reqImpl = static_cast(&req); + std::mutex _m; + std::function connCheckFun = [&](const boost::system::error_code& error) { + if (error != boost::asio::error::operation_aborted) { + std::lock_guard _l(_m); + // How to find out whether the client disconnected: Monitor the socket with Posix "select": + // If select says that there is data, available() returns 0 then apparently the client has disconnected. + // WTF, is this really the only way to notice that a client properly disconnected? Internet says so. + auto sockfs = reqImpl->socket().impl()->sockfd(); + fd_set fds; + FD_ZERO(&fds); + FD_SET(sockfs, &fds); + struct timeval timeout = {0, 1000}; + int sel = select(sockfs + 1, &fds, 0, 0, &timeout); + if (sel < 0 || (sel > 0 && reqImpl->socket().available() == 0)) { + ROS_ERROR_STREAM("Apparently we have a disconnect in device event queue \"" << eventQueue->clientID << "\""); + breakLoop = true; + eventQueue->cancelPopEvent(); + } + connCheckTimer.expires_from_now(boost::posix_time::milliseconds(100)); + connCheckTimer.async_wait(connCheckFun); + } + }; + connCheckTimer.expires_from_now(boost::posix_time::milliseconds(100)); + connCheckTimer.async_wait(connCheckFun); + try { + msg = eventQueue->popEvent(); + } catch (typename EventQueue::TimeoutException& e) { + msg["eventtype"] = "timeoutevent"; + } + { + std::lock_guard _l(_m); + breakLoop = true; + connCheckTimer.cancel(); + } + handleResponse(req, resp, msg); + } + }; diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp index afaa416b..9aed6ff0 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp @@ -138,6 +138,7 @@ void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &re if (type != parent->contentTypes.end()) { resp.setContentType(type->second); } + resp.setContentLength(boost::filesystem::file_size(file)); filesystem::ifstream fIn; fIn.open(file, std::ios::in); std::ostream& out = resp.send(); diff --git a/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/FunctionTable.h b/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/FunctionTable.h new file mode 100644 index 00000000..6770bf57 --- /dev/null +++ b/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/FunctionTable.h @@ -0,0 +1,65 @@ + +#pragma once + + +#include +#include + + + +namespace hos { +namespace utils { + + + +template +class FunctionTable { +public: + typedef std::function Func_t; + typedef std::function MissingKeyFunc_t; +protected: + std::map _funcs; + MissingKeyFunc_t missingKeyFunc; +public: + FunctionTable(MissingKeyFunc_t missingKeyFunc = [](const Key&)->Func_t { + return [](Args...)->RetType { return RetType(); }; + }) : missingKeyFunc(missingKeyFunc) {} + + void add(const Key& key, const std::function& func) { + _funcs.insert({key, func}); + } + + std::function operator[](const Key& key) { + auto f = _funcs.find(key); + if (f != _funcs.end()) { + return f->second; + } else { + return missingKeyFunc(key); + } + } + + RetType call(const Key& key, Args... args) { + return (*this)[key](args...); + } + + void forEach(std::function collector, Args... args) { + for (auto f : _funcs) { + collector(f.first, f.second(args...)); + } + } + + /*typename std::map::iterator begin() { + return _funcs.begin(); + } + + typename std::map::iterator end() { + return _funcs.end(); + }*/ + +}; + + + +} // namespace utils +} // namespace hos + diff --git a/src/hos_deps/cmake/Modules/FindPoco.cmake b/src/hos_deps/cmake/Modules/FindPoco.cmake index e9a0533a..3df57382 100644 --- a/src/hos_deps/cmake/Modules/FindPoco.cmake +++ b/src/hos_deps/cmake/Modules/FindPoco.cmake @@ -1,11 +1,16 @@ +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(POCO_LIB_POSTFIX "d") +else (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(POCO_LIB_POSTFIX "") +endif (CMAKE_BUILD_TYPE STREQUAL "Debug") find_path(Poco_INCLUDE_DIR Poco/SharedLibrary.h PATHS $ENV{HOS_PREFIX}/poco PATH_SUFFIXES include ) -find_library(Poco_FOUNDATION_LIB PocoFoundation +find_library(Poco_FOUNDATION_LIB PocoFoundation${POCO_LIB_POSTFIX} PATHS $ENV{HOS_PREFIX}/poco PATH_SUFFIXES lib lib64 ) @@ -18,7 +23,7 @@ SET(Comp_List "") IF(Poco_FOUNDATION_LIB) SET(Comp_List "Foundation") FOREACH(COMPONENT ${Poco_FIND_COMPONENTS}) - FIND_LIBRARY(LIB${COMPONENT} "Poco${COMPONENT}" PATHS ${Poco_LIBRARY_DIR}) + FIND_LIBRARY(LIB${COMPONENT} "Poco${COMPONENT}${POCO_LIB_POSTFIX}" PATHS ${Poco_LIBRARY_DIR}) IF (LIB${COMPONENT}) LIST(APPEND Poco_LIBRARIES "${LIB${COMPONENT}}") LIST(APPEND Comp_List "${COMPONENT}")