From 8be54f1a4056982a7b1a02c95e26491375fc7bba Mon Sep 17 00:00:00 2001 From: matzman Date: Thu, 28 May 2015 10:09:28 +0200 Subject: [PATCH] . --- .../hos/cosgi/service/Implementation.h | 10 + src/hos_cosgi_core/src/launcher.cpp | 3 +- src/hos_cosgi_devicerepository/CMakeLists.txt | 5 + src/hos_cosgi_devicerepository/package.xml | 1 + .../src/BundleActivator.cpp | 37 ++- .../DeviceRepositoryServiceImpl.cpp | 6 +- .../service/webresource/DeviceRepoHandler.cpp | 243 ++++++++++++++++++ .../service/webresource/DeviceRepoHandler.h | 118 +++++++++ .../src/BundleActivator.cpp | 6 +- .../ruleengine/RuleEngineServiceImpl.cpp | 9 +- .../src/BundleActivator.cpp | 6 +- .../python/PyScriptingServiceImpl.cpp | 6 +- src/hos_cosgi_webserver/CMakeLists.txt | 3 +- .../certs/ssl-cert-snakeoil.key | 28 ++ .../certs/ssl-cert-snakeoil.pem | 17 ++ .../service/webserver/WebServerService.h | 23 +- .../resources/WebResourceHandlerImpl.h | 21 +- src/hos_cosgi_webserver/package.xml | 4 +- .../src/BundleActivator.cpp | 55 +++- .../webserver/WebServerServiceImpl.cpp | 107 +++++--- .../service/webserver/WebServerServiceImpl.h | 22 +- .../webserver/resources/DevicesResource.cpp | 42 ++- .../webserver/resources/DevicesResource.h | 17 +- .../webserver/resources/SchemaResource.cpp | 29 ++- .../webserver/resources/SchemaResource.h | 7 +- src/hos_deps/cmake/Modules/FindPoco.cmake | 12 +- 26 files changed, 709 insertions(+), 128 deletions(-) create mode 100644 src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp create mode 100644 src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h create mode 100644 src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.key create mode 100644 src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.pem diff --git a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h index 8209fa44..c59a78e6 100644 --- a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h +++ b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h @@ -34,6 +34,16 @@ struct ServiceInfo { +/** + * Macro to instantiate the ServiceInfo struct. + */ +#define HOS_SERVICE_IMPL_INFO(ImplClass, ServiceName, ServiceMajor, ServiceMinor) \ + template<> const char* ::hos::cosgi::service::implementation::ServiceInfo::name = ServiceName; \ + template<> const int ::hos::cosgi::service::implementation::ServiceInfo::major = ServiceMajor; \ + template<> const int ::hos::cosgi::service::implementation::ServiceInfo::minor = ServiceMinor; + + + /** * Implementation of interface::Object. */ diff --git a/src/hos_cosgi_core/src/launcher.cpp b/src/hos_cosgi_core/src/launcher.cpp index 703dc261..b3826d8e 100644 --- a/src/hos_cosgi_core/src/launcher.cpp +++ b/src/hos_cosgi_core/src/launcher.cpp @@ -113,7 +113,8 @@ int main(int argc, char **argv) { // Create and register hos-core-service CoreServiceImpl hosCoreService(rosPublicNodeHandle, rosPrivateNodeHandle); service_registration_pt reg = nullptr; - bundleContext_registerService(context, (char*)CoreService::SERVICE_NAME, &hosCoreService, nullptr, ®); + CoreService* coreInterface = dynamic_cast(&hosCoreService); + bundleContext_registerService(context, (char*)CoreService::SERVICE_NAME, coreInterface, nullptr, ®); ROS_INFO("HOS core service registered"); diff --git a/src/hos_cosgi_devicerepository/CMakeLists.txt b/src/hos_cosgi_devicerepository/CMakeLists.txt index 63700594..fe019be3 100644 --- a/src/hos_cosgi_devicerepository/CMakeLists.txt +++ b/src/hos_cosgi_devicerepository/CMakeLists.txt @@ -7,6 +7,7 @@ find_package(catkin REQUIRED COMPONENTS hos_core_msgs hos_cosgi_core hos_deps + hos_cosgi_webserver hos_cpp_utils roscpp ) @@ -16,6 +17,7 @@ find_package(APR REQUIRED) find_package(CELIX REQUIRED) find_package(Boost REQUIRED) find_package(libvariant REQUIRED) +find_package(Poco REQUIRED COMPONENTS Net NetSSL) ################################### @@ -42,6 +44,7 @@ include_directories( ${APR_INCLUDE_DIR} ${APR_INCLUDE_DIR}/apr-1.0 ${libvariant_INCLUDE_DIR} + ${Poco_INCLUDE_DIRS} ) set(BUNDLE_SYMBOLICNAME "hos.hos_devicerepository") @@ -52,12 +55,14 @@ bundle(hos_cosgi_devicerepository SOURCES ${PROJECT_SOURCE_DIR}/src/BundleActivator ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl + ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webresource/DeviceRepoHandler ) target_link_libraries(hos_cosgi_devicerepository ${catkin_LIBRARIES} ${APR_LIBRARIES} ${libvariant_LIBRARY} + ${Poco_LIBRARIES} ) diff --git a/src/hos_cosgi_devicerepository/package.xml b/src/hos_cosgi_devicerepository/package.xml index ac926b46..808e8da8 100644 --- a/src/hos_cosgi_devicerepository/package.xml +++ b/src/hos_cosgi_devicerepository/package.xml @@ -46,6 +46,7 @@ roscpp hos_deps hos_cpp_utils + hos_cosgi_webserver hos_celix hos_core_msgs hos_cosgi_core diff --git a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp index 9f3a9ab9..2dcb1c90 100644 --- a/src/hos_cosgi_devicerepository/src/BundleActivator.cpp +++ b/src/hos_cosgi_devicerepository/src/BundleActivator.cpp @@ -11,18 +11,25 @@ #include // CoreService #include +// Services #include "hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.h" +#include "hos/cosgi/service/webresource/DeviceRepoHandler.h" using namespace hos::cosgi; using namespace hos::cosgi::service::devicerepository; +using namespace hos::cosgi::service::webserver; struct UserData { apr_pool_t* memoryPool = nullptr; std::unique_ptr serviceImpl; + std::unique_ptr webResourceImpl; service_registration_pt serviceReg = nullptr; + service_registration_pt webResourceReg = nullptr; + service_registration_pt webResourceReg2 = nullptr; + service_registration_pt webResourceReg3 = nullptr; }; @@ -65,6 +72,12 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) } myUserData->serviceImpl.reset(new DeviceRepositoryServiceImpl()); + // Create device repository webresource + if (myUserData->webResourceReg != nullptr) { + serviceRegistration_unregister(myUserData->webResourceReg); + } + myUserData->webResourceImpl.reset(new webresource::DeviceRepoHandlerFactory(myUserData->serviceImpl.get())); + // Get core service service_reference_pt ref = nullptr; bundleContext_getServiceReference(context, (char*)service::core::CoreService::SERVICE_NAME, &ref); @@ -76,9 +89,29 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) myUserData->serviceImpl->setCoreService(coreService); } - // Register device repository service + // Register device repository service and webresource if (myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(context, (char*)DeviceRepositoryService::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + // repository service + DeviceRepositoryService* repoInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(context, (char*)DeviceRepositoryService::SERVICE_NAME, repoInterface, nullptr, &myUserData->serviceReg); + // web resource + WebResourceHandlerFactory* webInterface = dynamic_cast(myUserData->webResourceImpl.get()); + // ToDo: Properly delete properties + properties_pt webResourceProps = properties_create(); + properties_set(webResourceProps, (char*)"path", (char*)"/devices"); + bundleContext_registerService(context, (char*)WebResourceHandlerFactory::SERVICE_NAME, webInterface, webResourceProps, &myUserData->webResourceReg); + + // web resource + // ToDo: Properly delete properties + properties_pt webResourceProps2 = properties_create(); + properties_set(webResourceProps2, (char*)"path", (char*)"/blabla"); + bundleContext_registerService(context, (char*)WebResourceHandlerFactory::SERVICE_NAME, webInterface, webResourceProps2, &myUserData->webResourceReg2); + + // web resource + // ToDo: Properly delete properties + properties_pt webResourceProps3 = properties_create(); + properties_set(webResourceProps3, (char*)"path", (char*)"/blabla"); + bundleContext_registerService(context, (char*)WebResourceHandlerFactory::SERVICE_NAME, webInterface, webResourceProps3, &myUserData->webResourceReg3); } ROS_INFO("DeviceRepositoryService: Bundle successfully started"); diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp index c1102270..651bb5ca 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.cpp @@ -17,9 +17,9 @@ namespace devicerepository { // Service information -template<> const char* implementation::ServiceInfo::name = service::devicerepository::DeviceRepositoryService::SERVICE_NAME; -template<> const int implementation::ServiceInfo::major = 1; -template<> const int implementation::ServiceInfo::minor = 0; +HOS_SERVICE_IMPL_INFO( DeviceRepositoryServiceImpl, // Implementation + service::devicerepository::DeviceRepositoryService::SERVICE_NAME, // Service Name + 1, 0 ) // Version diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp new file mode 100644 index 00000000..f91b46f9 --- /dev/null +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.cpp @@ -0,0 +1,243 @@ + +#include "DeviceRepoHandler.h" +#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({"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(); + } }); +} + + + +Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { + return new DeviceRepoHandler(matchUri, repoService, infoCategoryFuncs, ssl); +} + + + +void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + // Get method parameter + Net::HTMLForm form(req); + DevicesAPIMethod dataMethod = DevicesAPIMethod::GET; // Default method is get + std::string methodValue; + if (form.has("method")) { + std::string methodRaw = form.get("method"); + bool requireValue = false; + if (methodRaw == "get") { + // do nothing + } else if (methodRaw == "command") { + dataMethod = DevicesAPIMethod::COMMAND; + requireValue = true; + } else if (methodRaw.compare("statusupdate") == 0) { + dataMethod = DevicesAPIMethod::STATUSUPDATE; + requireValue = true; + } else { + throw webserver::resources::WebResourceHandlerException((boost::format("Unknown API method \"%1%\"") % methodRaw).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + if (requireValue) { + if (form.has("value")) { + methodValue = form.get("value"); + } else { + throw webserver::resources::WebResourceHandlerException((boost::format("API method \"%1%\" requires a value parameter.") % methodRaw).str(), + 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 (dataMethod != DevicesAPIMethod::GET) { + throw webserver::resources::WebResourceHandlerException("Requested API method requires a device", + Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); + } + Variant result; + for (auto d : repoService->getDevices()) { + auto dev = d.lock(); + result[dev->getDeviceID()] = getDeviceInfo(dev.get(), req); + } + handleResponse(req, resp, result); + + // Device given + } else { + std::string devid = pathPieces[matchPieces.size()]; + auto device = repoService->getDevice(devid); + if (!device.expired()) { + switch (dataMethod) { + case DevicesAPIMethod::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 DevicesAPIMethod::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 DevicesAPIMethod::STATUSUPDATE: + throw webserver::resources::WebResourceHandlerException("ToDo: Implement method."); + default: + throw webserver::resources::WebResourceHandlerException("Internal Server Error"); + } + } else { + throw webserver::resources::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } +} + + + +void DeviceRepoHandler::_handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + throw webserver::resources::WebResourceHandlerException("ToDo: Implement POST."); +} + + + +Variant DeviceRepoHandler::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::resources::WebResourceHandlerException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % pathstr % category).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } + } else { + throw webserver::resources::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); +} + + + + +} // namespace resources +} // namespace webserver +} // namespace service +} // namespace cosgi +} // namespace hos + + + diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h new file mode 100644 index 00000000..cec23bc9 --- /dev/null +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h @@ -0,0 +1,118 @@ + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace devicerepository { +namespace webresource { + + + +enum class DevicesAPIMethod { + GET, + COMMAND, + STATUSUPDATE +}; + + + +class DeviceRepoHandlerFactory + : public virtual webserver::WebResourceHandlerFactory, + public implementation::Service { +public: + typedef std::function infoCategoryFunc_t; +private: + devicerepository::DeviceRepositoryService* repoService; + std::map infoCategoryFuncs; +public: + DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService); + virtual Poco::Net::HTTPRequestHandler* createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override; + +}; + + + +/** + * 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 DeviceRepoHandler + : public virtual webserver::resources::WebResourceHandlerImpl { + +private: + 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: + + DeviceRepoHandler( Poco::URI matchUri, + devicerepository::DeviceRepositoryService* repoService, + const std::map& infoCategoryFuncs, + bool ssl = false ) + : matchUri(matchUri), repoService(repoService), infoCategoryFuncs(infoCategoryFuncs), ssl(ssl) {} + + virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + + virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; + +}; + + + +} // namespace webresource +} // namespace devicerepository +} // namespace service +} // namespace cosgi +} // namespace hos + diff --git a/src/hos_cosgi_ruleengine/src/BundleActivator.cpp b/src/hos_cosgi_ruleengine/src/BundleActivator.cpp index 35f0f35a..7a14b530 100644 --- a/src/hos_cosgi_ruleengine/src/BundleActivator.cpp +++ b/src/hos_cosgi_ruleengine/src/BundleActivator.cpp @@ -58,7 +58,8 @@ celix_status_t cb_serviceChanged(void* listener, service_event_pt event) { // Register rule engine service if (checkReg && !myUserData->serviceReg && myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(myUserData->bundleContext, (char*)RuleEngineServiceImpl::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + RuleEngineService* engineInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(myUserData->bundleContext, (char*)RuleEngineServiceImpl::SERVICE_NAME, engineInterface, nullptr, &myUserData->serviceReg); } } else if (event->type == OSGI_FRAMEWORK_SERVICE_EVENT_UNREGISTERING) { @@ -131,7 +132,8 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) // Register rule engine service if (myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(context, (char*)RuleEngineServiceImpl::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + RuleEngineService* engineInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(context, (char*)RuleEngineServiceImpl::SERVICE_NAME, engineInterface, nullptr, &myUserData->serviceReg); } // Register service listener diff --git a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.cpp b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.cpp index 6483fcb4..f38ab422 100644 --- a/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.cpp +++ b/src/hos_cosgi_ruleengine/src/hos/cosgi/service/ruleengine/RuleEngineServiceImpl.cpp @@ -27,15 +27,14 @@ namespace service { namespace ruleengine { -using namespace ::libvariant; +using namespace libvariant; // Service information -template<> const char* implementation::ServiceInfo::name = service::ruleengine::RuleEngineService::SERVICE_NAME; -template<> const int implementation::ServiceInfo::major = 1; -template<> const int implementation::ServiceInfo::minor = 0; - +HOS_SERVICE_IMPL_INFO( RuleEngineServiceImpl, // Implementation + service::ruleengine::RuleEngineService::SERVICE_NAME, // Service Name + 1, 0 ) // Version void RuleEngineServiceImpl::setCoreService(service::core::CoreService* coreService) noexcept { diff --git a/src/hos_cosgi_scripting_python/src/BundleActivator.cpp b/src/hos_cosgi_scripting_python/src/BundleActivator.cpp index cde70553..f413d92f 100644 --- a/src/hos_cosgi_scripting_python/src/BundleActivator.cpp +++ b/src/hos_cosgi_scripting_python/src/BundleActivator.cpp @@ -48,7 +48,8 @@ celix_status_t cb_serviceChanged(void* listener, service_event_pt event) { } // Register python scripting service if (checkReg && !myUserData->serviceReg && myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(myUserData->bundleContext, (char*)PyScriptingService::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + ScriptingService* serviceInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(myUserData->bundleContext, (char*)PyScriptingService::SERVICE_NAME, serviceInterface, nullptr, &myUserData->serviceReg); } } else if (event->type == OSGI_FRAMEWORK_SERVICE_EVENT_UNREGISTERING) { } @@ -111,7 +112,8 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) } // Register hos-ruleexecution-python-service if (myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(context, (char*)PyScriptingService::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + ScriptingService* serviceInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(context, (char*)PyScriptingService::SERVICE_NAME, serviceInterface, nullptr, &myUserData->serviceReg); } // Register service listener myUserData->myServiceListener.handle = myUserData; diff --git a/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.cpp b/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.cpp index 16101f5c..38181f47 100644 --- a/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.cpp +++ b/src/hos_cosgi_scripting_python/src/hos/cosgi/service/scripting/python/PyScriptingServiceImpl.cpp @@ -15,9 +15,9 @@ namespace python { // Service information -template<> const char* implementation::ServiceInfo::name = service::scripting::python::PyScriptingService::SERVICE_NAME; -template<> const int implementation::ServiceInfo::major = 1; -template<> const int implementation::ServiceInfo::minor = 0; +HOS_SERVICE_IMPL_INFO( PyScriptingService, // Implementation + service::scripting::python::PyScriptingService::SERVICE_NAME, // Service Name + 1, 0 ) // Version diff --git a/src/hos_cosgi_webserver/CMakeLists.txt b/src/hos_cosgi_webserver/CMakeLists.txt index 220be69a..2cfdf663 100644 --- a/src/hos_cosgi_webserver/CMakeLists.txt +++ b/src/hos_cosgi_webserver/CMakeLists.txt @@ -4,7 +4,6 @@ project(hos_cosgi_webserver) ## Find catkin macros and libraries find_package(catkin REQUIRED COMPONENTS hos_celix - hos_cosgi_devicerepository hos_cosgi_core hos_cpp_utils hos_deps @@ -56,7 +55,7 @@ bundle(hos_cosgi_webserver SOURCES ${PROJECT_SOURCE_DIR}/src/BundleActivator ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/WebServerServiceImpl - ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/resources/DevicesResource +# ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/resources/DevicesResource ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/resources/SchemaResource ) diff --git a/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.key b/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.key new file mode 100644 index 00000000..fefe2528 --- /dev/null +++ b/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDENZtNsygHr0t1 +jgXQsaOCAYqswNxJpIsXQFPoJovvAO8cEhEaIJIzvso28eM4LOEEJN/JGRRNlXJT +8C0lFAwMULY0cO01pGBZO5rFDwY2hDNbWASkPQVq4a+oaxFfPDRpOsjGf8koj3Ev +bGjtw7UWhgSC42VpJZS3JxVvSlfZc+UKEdhcGHPZoYOVIMTaCcOky/u3ARh226Yw +bcqqbVrXkEjTjNXZIYu86W2fZdf3spDvhpbOWvzFo1HLY87cwEvUa5eDors8cW5Y +uVNWBsiPrQUxSEUT/BGjnlJqXMD5VfxvUvYYXK9gLGyjRnxwkiKgKPc+bmx/EBdx +KAZE23m1AgMBAAECggEAFcrab/vZE1lt6SWYEcDZJ0RikaNuFCPZawCzERLO8gHJ +jPWYvq0lb+fd79JX6VJav6oUzPIjJibWJuqDKR4b5HZGdnagchfKt75+BysPBpkS +Vy/kfBVm96MPboZNzKUUMSfdSubAgefEq36//MCC6XiDIcJ97frFlL4bjFvWfAlI +etnPbJapkM7o30tpTA/M1aEXHfVyJAceLztFb+8E3PaAWQ7nSlck42ESn5QGlIo9 +2jlwgb+h96WgAZgBnTJFtmZTzhFVDca9hDPLe+CuF2ybyyHR3U1WEY3T4c47zUgZ +nchWhtGu+vk8Hq275RirjDmJ7xeCpn056C4g3oYGtQKBgQDhdE90LoOlLClA7Q8E +Ie+6J29+RcV12ppIx/9WQYQG0trMtAXRRC/9C8DAcE4fgF49yfEMM2oKI8H7nU6N ++0KzuRQEEwSnz0Y931D0cxpJqh1owBW812Bf4J7nbL5Q5zYIGmv5l7geHdIEqI1l +RVBrk4dVdffxeS7oWbKi9lUH0wKBgQDeyvX5vZkXtV3A8ZBgwnkstu/KHEdF2e8P +dayldtuVgUReR5OqrfgXRcmK3uILzGA7xxuwH+mtepwri/NOsgP7DPTP8/irLgLf +5w5G6YeqU90VDE8pEDeNpXV3mCZ8471OncaDjGrTbOaf1PS31HknqpNG8+q5sgEa +pM3fgMlLVwKBgERRLV5kVenwNtQAwaMb8Xu0RXm7BLnQsx3I1Nu7YJl8vWZ4Sqb+ +BZilPhRSM6oiCOyqS8vJrKFD8p0fLtWhRLYXKDSP2sJK8dr/15UrZ9TYoq7CPhQK +I58J/rzb29PCLEJKV1u7mMQvfxuAQ9BjL5pZixHffHGEqi0oORRelqeXAoGBAJe+ +SyzPax9Pw0xv+3M74caHzuisvAqidgXkkzObsdGxWryQKc59cVWAjdYNGo8Io57z +bTNXeRRUR1wNlSlLbBSo7XFZ7UvCKekQeZPo6SMkvZbhbGU+i2fpgwly0vwLLVwH +cDcXA4d/S9Ee/ZLbn61Zq7gO2BekWsYn66TIFdI/AoGBAJvHKSziHh8zqJQVHtxK +mBLeCnfhUTdm7enUAJ4pxWswwnRUjX8eAg87oHbCm32RP/RtnOKQUUEJS3bqwDT4 +tySdE1Jo6khrx1G+8RBJ8HB6Ne1zd9PN5Pkc3+aG9Jli6pOuuNQCiRi1xO1ZDAC4 +mt6CUGQbEtDuv56qXfLl8++B +-----END PRIVATE KEY----- diff --git a/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.pem b/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.pem new file mode 100644 index 00000000..89efbfe2 --- /dev/null +++ b/src/hos_cosgi_webserver/certs/ssl-cert-snakeoil.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICtDCCAZygAwIBAgIJAPyF1ij7MJfbMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMTB2t1YnVudHUwHhcNMTUwMjAxMjM0MTMxWhcNMjUwMTI5MjM0MTMxWjASMRAw +DgYDVQQDEwdrdWJ1bnR1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xDWbTbMoB69LdY4F0LGjggGKrMDcSaSLF0BT6CaL7wDvHBIRGiCSM77KNvHjOCzh +BCTfyRkUTZVyU/AtJRQMDFC2NHDtNaRgWTuaxQ8GNoQzW1gEpD0FauGvqGsRXzw0 +aTrIxn/JKI9xL2xo7cO1FoYEguNlaSWUtycVb0pX2XPlChHYXBhz2aGDlSDE2gnD +pMv7twEYdtumMG3Kqm1a15BI04zV2SGLvOltn2XX97KQ74aWzlr8xaNRy2PO3MBL +1GuXg6K7PHFuWLlTVgbIj60FMUhFE/wRo55SalzA+VX8b1L2GFyvYCxso0Z8cJIi +oCj3Pm5sfxAXcSgGRNt5tQIDAQABow0wCzAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB +CwUAA4IBAQAseKosUKyIpOzEChtKzBdWnvGYq7RHA8mWL+4RLhglbvjMCq/5Dt1B +1vAPexTz96Zti+erHQglTanpIsJjw8fuYVPYI0DTCw3Vp2abTrlmOR6oLLE193LN ++yE5cERaRYSTkJq49FYzYhKoAuNKRb6zR2BAYyO/pcp8l3gBgtQAp6A/cRhhvHd6 +DgmzphHscK1nZXGqXgND+gfKTvOi7RZaSBrtG8hotXuh81gV3R7zIluC63/V9ho8 +MyDLmaCKF5q+wofkwCh6Glx47oWfwY7CS13qRyAF3lzKxd90YNefBF7E64FS5R3y +rcTcMX167QElsdpQY/rOEjaXm/z4XbOJ +-----END CERTIFICATE----- diff --git a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h index 9dff833e..07103201 100644 --- a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h +++ b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h @@ -31,15 +31,28 @@ namespace webserver { class WebServerService; + + + + class WebResourceHandlerFactory - : public virtual interface::Object { + : public virtual interface::Service { public: - virtual Poco::Net::HTTPRequestHandler* createRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl = false ) noexcept = 0; + + static constexpr const char* SERVICE_NAME = "hos_webresource_factory"; + + virtual Poco::Net::HTTPRequestHandler* createRequestHandler( Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0; }; + + + + + + class WebServerService : public virtual interface::Service { @@ -47,11 +60,7 @@ public: static constexpr const char* SERVICE_NAME = "hos_webserver_service"; - virtual bool registerWebResource(const std::string& path, std::shared_ptr factory) noexcept = 0; - - virtual const Poco::URI& getPrefixPath() noexcept = 0; - - virtual const std::vector& getPrefixPathPieces() noexcept = 0; + virtual bool registerWebResource(const std::string& path, WebResourceHandlerFactory* factory) noexcept = 0; }; diff --git a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h index 3a9f4fd8..194fceca 100644 --- a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h +++ b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h @@ -3,10 +3,12 @@ #include +#include #include #include #include #include +#include #include #include #include @@ -39,23 +41,34 @@ public: }; +/* + +HOS_SERVICE_IMPL_INFO( GenericWebResourceHandlerFactory, // Implementation + service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name + 1, 0 ) // Version +*/ + class GenericWebResourceHandlerFactory - : public virtual WebResourceHandlerFactory { + : public virtual WebResourceHandlerFactory, + public implementation::Service { public: - typedef std::function factory_t; + typedef std::function factory_t; private: factory_t _factory; public: GenericWebResourceHandlerFactory(factory_t factory) : _factory(factory) {} - virtual Poco::Net::HTTPRequestHandler* createRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl = false ) noexcept override { - return _factory(request, ssl); + virtual Poco::Net::HTTPRequestHandler* createRequestHandler( Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override { + return _factory(matchUri, request, ssl); } }; + + + enum class ResponseDataFormat : int { JSON, JSONP, diff --git a/src/hos_cosgi_webserver/package.xml b/src/hos_cosgi_webserver/package.xml index 119010d3..10a58610 100644 --- a/src/hos_cosgi_webserver/package.xml +++ b/src/hos_cosgi_webserver/package.xml @@ -41,13 +41,13 @@ catkin hos_celix - hos_cosgi_devicerepository + hos_cosgi_core hos_cpp_utils hos_deps roscpp hos_celix - hos_cosgi_devicerepository + hos_cosgi_core hos_cpp_utils hos_deps diff --git a/src/hos_cosgi_webserver/src/BundleActivator.cpp b/src/hos_cosgi_webserver/src/BundleActivator.cpp index 30fdc299..95cdac81 100644 --- a/src/hos_cosgi_webserver/src/BundleActivator.cpp +++ b/src/hos_cosgi_webserver/src/BundleActivator.cpp @@ -27,7 +27,7 @@ extern "C" { using namespace hos::cosgi::service::webserver; -using namespace hos::cosgi::service::devicerepository; +//using namespace hos::cosgi::service::devicerepository; using namespace hos::cosgi::service::core; using namespace hos::cosgi; @@ -46,15 +46,33 @@ celix_status_t cb_serviceChanged(void* listener, service_event_pt event) { char* serviceName = nullptr; bool checkReg = false; serviceReference_getProperty(event->reference, (char*)OSGI_FRAMEWORK_OBJECTCLASS, &serviceName); - if (serviceName && std::strcmp(serviceName, DeviceRepositoryService::SERVICE_NAME) == 0) { + if (serviceName && std::strcmp(serviceName, WebResourceHandlerFactory::SERVICE_NAME) == 0) { + ROS_INFO("WebServer: Web resource appeared ..."); + WebResourceHandlerFactory *service = nullptr; + char* path = nullptr; + bundleContext_getService(myUserData->bundleContext, event->reference, (void**)&service); + serviceReference_getProperty(event->reference, (char*)"path", &path); + if (path) { + if (myUserData->serviceImpl->registerWebResource(path, service)) { + ROS_INFO("WebServer: Added web resource"); + } else { + ROS_ERROR("WebServer: ERROR: Path already in use."); + } + } else { + ROS_ERROR("WebServer: Web resource has no path"); + } + checkReg = true; + } + /*if (serviceName && std::strcmp(serviceName, DeviceRepositoryService::SERVICE_NAME) == 0) { DeviceRepositoryService *service = nullptr; bundleContext_getService(myUserData->bundleContext, event->reference, (void**)&service); myUserData->serviceImpl->setRepoService(service); checkReg = true; - } + }*/ // Register hos-devicerepository-service if (checkReg && !myUserData->serviceReg && myUserData->serviceImpl->isInitialized()) { - bundleContext_registerService(myUserData->bundleContext, (char*)WebServerServiceImpl::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); + WebServerService* serviceInterface = dynamic_cast(myUserData->serviceImpl.get()); + bundleContext_registerService(myUserData->bundleContext, (char*)WebServerServiceImpl::SERVICE_NAME, serviceInterface, nullptr, &myUserData->serviceReg); } } else if (event->type == OSGI_FRAMEWORK_SERVICE_EVENT_UNREGISTERING) { } @@ -106,13 +124,14 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) myUserData->serviceImpl->setCoreService(coreService); } // Get HOS Core Service - service_reference_pt ref2 = nullptr; + /*service_reference_pt ref2 = nullptr; bundleContext_getServiceReference(context, (char*)DeviceRepositoryService::SERVICE_NAME, &ref2); DeviceRepositoryService* repoService = nullptr; bundleContext_getService(context, ref2, (void**)&repoService); if (repoService) { myUserData->serviceImpl->setRepoService(repoService); - } + }*/ + // Register hos-devicerepository-service if (myUserData->serviceImpl->isInitialized()) { bundleContext_registerService(context, (char*)WebServerServiceImpl::SERVICE_NAME, myUserData->serviceImpl.get(), nullptr, &myUserData->serviceReg); @@ -121,6 +140,30 @@ celix_status_t bundleActivator_start(void * userData, bundle_context_pt context) myUserData->myServiceListener.handle = myUserData; myUserData->myServiceListener.serviceChanged = cb_serviceChanged; bundleContext_addServiceListener(context, &myUserData->myServiceListener, nullptr); + + // Get web resources + array_list_pt references = NULL; + status = bundleContext_getServiceReferences(context, WebResourceHandlerFactory::SERVICE_NAME, NULL, &references); + if (status == CELIX_SUCCESS) { + for (int i = 0; i < arrayList_size(references); i++) { + ROS_INFO("WebServer: Found a web resource ..."); + WebResourceHandlerFactory *service = nullptr; + char* path = nullptr; + service_reference_pt ref = (service_reference_pt)arrayList_get(references, i); + bundleContext_getService(context, ref, (void**)&service); + serviceReference_getProperty(ref, (char*)"path", &path); + if (path) { + if (myUserData->serviceImpl->registerWebResource(path, service)) { + ROS_INFO("WebServer: Added web resource"); + } else { + ROS_ERROR("WebServer: ERROR: Path already in use."); + } + } else { + ROS_ERROR("WebServer: Web resource has no path"); + } + } + } + ROS_INFO("WebServerService: Bundle successfully started"); } else { ROS_ERROR("WebServerService: Could not start bundle, user data struct is null"); diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp index 4cd7a3fc..1fc8d145 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp @@ -1,10 +1,12 @@ #include "WebServerServiceImpl.h" +#include "resources/SchemaResource.h" +//#include "resources/DevicesResource.h" #include #include -#include "resources/SchemaResource.h" -#include "resources/DevicesResource.h" - +#include +#include +#include namespace hos { @@ -19,9 +21,10 @@ using namespace Poco; -template<> const char* implementation::ServiceInfo::name = service::webserver::WebServerServiceImpl::SERVICE_NAME; -template<> const int implementation::ServiceInfo::major = 1; -template<> const int implementation::ServiceInfo::minor = 0; +// Service information +HOS_SERVICE_IMPL_INFO( WebServerServiceImpl, // Implementation + service::webserver::WebServerServiceImpl::SERVICE_NAME, // Service Name + 1, 0 ) // Version @@ -42,14 +45,14 @@ void WebServerServiceImpl::setCoreService(service::core::CoreService* coreServic int major = 0, minor = 0; coreService->getServiceVersion(major, minor); ROS_INFO_STREAM("WebServerService: Found HOSCoreService Version " << major << "." << minor); - if (repoService) { + //if (repoService) { init(); - } + //} } } -void WebServerServiceImpl::setRepoService(devicerepository::DeviceRepositoryService* repoService) noexcept { +/*void WebServerServiceImpl::setRepoService(devicerepository::DeviceRepositoryService* repoService) noexcept { if (repoService && !this->repoService) { this->repoService = repoService; int major = 0, minor = 0; @@ -59,7 +62,7 @@ void WebServerServiceImpl::setRepoService(devicerepository::DeviceRepositoryServ init(); } } -} +}*/ @@ -73,8 +76,9 @@ Poco::Net::HTTPRequestHandler* WebServerServiceImpl::RequestHandlerFactory::crea // We want a longest prefix match. // Since we have an ordered map, we just need to iterate it in reverse order and return the first match. for (auto i = parent->webResourceFactories.rbegin(); i != parent->webResourceFactories.rend(); i++) { - if (boost::algorithm::starts_with(request.getURI(), i->first)) { - return i->second->createRequestHandler(request, ssl); + if ( boost::algorithm::starts_with(request.getURI(), i->first) + && ( request.getURI().size() == i->first.size() || request.getURI()[i->first.size()] == '/' ) ) { + return i->second->createRequestHandler(Poco::URI(i->first), request, ssl); } } // return default handler @@ -89,48 +93,83 @@ void WebServerServiceImpl::init() noexcept { //serverParams.max_threads(5); //webServer.reset(new webserver::webserver(serverParams)); - Net::ServerSocket webServerSocket(8080); Net::HTTPServerParams::Ptr webServerParams = new Net::HTTPServerParams(); webServerParams->setServerName("Home Operating System"); webServerParams->setSoftwareVersion("Home Operating System alpha"); + webServerParams->setMaxThreads(10); + + Net::ServerSocket webServerSocket(8080); webServer.reset(new Poco::Net::HTTPServer(new RequestHandlerFactory(this), webServerSocket, webServerParams)); webServer->start(); - /*auto& sslManager = Poco::Net::SSLManager::instance(); - sslManager.defaultServerContext(); - Poco::Net::SecureServerSocket webServerSSLSocket(8090); - webServerSSL.reset(new Poco::Net::HTTPServer(new RequestHandlerFactory(), webServerSocket, webServerParams)); - webServerSSL->start();*/ - // Register web resources ros::NodeHandle* nodehandle, utils::SchemaLoader* schemaLoader, webserver::WebServerService* service + try { + + class MyPrivateKeyPassphraseHandler + : public Poco::Net::PrivateKeyPassphraseHandler { + public: + MyPrivateKeyPassphraseHandler(bool onServerSide) + : PrivateKeyPassphraseHandler(onServerSide) {} + void onPrivateKeyRequested(const void* pSender, std::string& privateKey) override { + std::cout << "onPrivateKeyRequested: " << privateKey << std::endl; + } + }; + + class MyInvalidCertificateHandler + : public Poco::Net::InvalidCertificateHandler { + public: + MyInvalidCertificateHandler(bool handleErrorsOnServerSide) + : InvalidCertificateHandler(handleErrorsOnServerSide) {} + void onInvalidCertificate(const void* pSender, Poco::Net::VerificationErrorArgs& errorCert){ + std::cout << "onInvalidCertificate: " << errorCert.errorMessage() << std::endl; + } + }; + + auto& sslManager = Poco::Net::SSLManager::instance(); + //Poco::Net::SSLManager::PrivateKeyPassphraseHandlerPtr ptrConsole = new Poco::Net::KeyConsoleHandler(true); + Poco::Net::SSLManager::PrivateKeyPassphraseHandlerPtr ptrConsole = new MyPrivateKeyPassphraseHandler(true); + //Poco::Net::SSLManager::InvalidCertificateHandlerPtr ptrCert = new Poco::Net::ConsoleCertificateHandler(true); + Poco::Net::SSLManager::InvalidCertificateHandlerPtr ptrCert = new MyInvalidCertificateHandler(true); + Poco::Net::Context::Ptr ptrContext = new Poco::Net::Context( Poco::Net::Context::SERVER_USE, + "/etc/ssl/private/ssl-cert-snakeoil.key", + "/etc/ssl/certs/ssl-cert-snakeoil.pem", + "", + Poco::Net::Context::VERIFY_NONE, + 9, + false, + "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" ); + sslManager.initializeServer(ptrConsole, ptrCert, ptrContext); + + Poco::Net::SecureServerSocket webServerSSLSocket(8090); + webServerSSL.reset(new Poco::Net::HTTPServer(new RequestHandlerFactory(this, true), webServerSSLSocket, webServerParams)); + webServerSSL->start(); + + } catch(Poco::Net::SSLException& e) { + ROS_ERROR_STREAM("Could not start HTTPS server: " << e.displayText()); + } catch(...) { + ROS_ERROR("Unknown exception while starting HTTPS Server"); + } + + // Register web resources registerWebResource("/schemas", - std::make_shared([=](const Net::HTTPServerRequest&, bool)->Net::HTTPRequestHandler* { - return new resources::SchemaResourceHandler(nodehandle, &coreService->getSchemaLoader(), this); + new resources::GenericWebResourceHandlerFactory([=](Poco::URI matchUri, const Net::HTTPServerRequest&, bool)->Net::HTTPRequestHandler* { + return new resources::SchemaResourceHandler(matchUri, nodehandle, &coreService->getSchemaLoader(), this); }) ); - registerWebResource("/devices", + /*registerWebResource("/devices", std::make_shared(this, repoService) - ); + );*/ registerWebResource("/default", - std::make_shared([=](const Net::HTTPServerRequest&, bool)->Net::HTTPRequestHandler* { + new resources::GenericWebResourceHandlerFactory([=](Poco::URI matchUri, const Net::HTTPServerRequest&, bool)->Net::HTTPRequestHandler* { return new resources::WebResourceHandlerImpl(); }) ); - - /*registerWebResource( "/schemas", - std::make_shared(nodehandle, &coreService->getSchemaLoader()) - ); - registerWebResource( "/devices", - std::make_shared(repoService) - );*/ - - //webServer->start(false); initialized = true; ROS_INFO_STREAM("WebServerService: Initialized and Ready."); } -bool WebServerServiceImpl::registerWebResource(const std::string& path, std::shared_ptr factory) noexcept { +bool WebServerServiceImpl::registerWebResource(const std::string& path, WebResourceHandlerFactory* factory) noexcept { auto uri = getAbsoluteURI(path); if (webResourceFactories.find(uri.getPath()) == webResourceFactories.end()) { webResourceFactories[uri.getPath()] = factory; diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h index a21766e9..17b5feca 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h @@ -6,7 +6,7 @@ #include #include #include -#include +//#include #include #include #include @@ -63,25 +63,19 @@ private: }; service::core::CoreService* coreService = nullptr; - devicerepository::DeviceRepositoryService* repoService = nullptr; + //devicerepository::DeviceRepositoryService* repoService = nullptr; ros::NodeHandle* nodehandle = nullptr; bool initialized = false; - Poco::URI prefixPath = Poco::URI("/hos"); - std::vector prefixPathPieces; + Poco::URI prefixPath = Poco::URI("/hos/api"); std::unique_ptr webServer; std::unique_ptr webServerSSL; - std::map> webResourceFactories; + std::map webResourceFactories; void init() noexcept; public: - - WebServerServiceImpl() { - prefixPath.getPathSegments(prefixPathPieces); - } - ~WebServerServiceImpl(); @@ -89,20 +83,20 @@ public: void setCoreService(service::core::CoreService* coreService) noexcept; - void setRepoService(devicerepository::DeviceRepositoryService* repoService) noexcept; + //void setRepoService(devicerepository::DeviceRepositoryService* repoService) noexcept; /* Public Service Implementations */ - virtual bool registerWebResource(const std::string& path, std::shared_ptr factory) noexcept override; + virtual bool registerWebResource(const std::string& path, WebResourceHandlerFactory* factory) noexcept override; - virtual const Poco::URI& getPrefixPath() noexcept override { +/* virtual const Poco::URI& getPrefixPath() noexcept override { return prefixPath; } virtual const std::vector& getPrefixPathPieces() noexcept override { return prefixPathPieces; - } + }*/ /* Internal */ diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.cpp index 466bd39f..b8d0ada7 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.cpp @@ -11,7 +11,7 @@ namespace hos { namespace cosgi { namespace service { -namespace webserver { +namespace webresource { namespace resources { @@ -20,22 +20,21 @@ using namespace Poco; using namespace libvariant; - DevicesResourceHandlerFactory::DevicesResourceHandlerFactory(WebServerService* service, devicerepository::DeviceRepositoryService* repoService) : service(service), repoService(repoService) { - infoCategoryFuncs.insert({"type", [](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + 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", [](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + 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", [](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + 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", [](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + 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()); @@ -43,7 +42,7 @@ DevicesResourceHandlerFactory::DevicesResourceHandlerFactory(WebServerService* s return retval; } }); - infoCategoryFuncs.insert({"last_state", [](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { + 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()); @@ -51,23 +50,22 @@ DevicesResourceHandlerFactory::DevicesResourceHandlerFactory(WebServerService* s return retval; } }); - infoCategoryFuncs.insert({"uri", [=](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant { - auto& baseUri = service->getPrefixPath(); + 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() << baseUri.toString() << "/devices/" << device->getDeviceID(); + ss << req.getHost() << matchUri.toString() << device->getDeviceID(); return ss.str(); } }); } -Net::HTTPRequestHandler* DevicesResourceHandlerFactory::createRequestHandler( const Net::HTTPServerRequest& request, bool ssl ) noexcept { - return new DevicesResourceHandler(this, service, repoService, infoCategoryFuncs, ssl); +Net::HTTPRequestHandler* DevicesResourceHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept { + return new DevicesResourceHandler(matchUri, service, repoService, infoCategoryFuncs, ssl); } @@ -102,13 +100,13 @@ void DevicesResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco: } } - auto& prefixPath = service->getPrefixPathPieces(); + std::vector pathPieces, matchPieces; + matchUri.getPathSegments(matchPieces); Poco::URI uri(req.getURI()); - std::vector pathPieces; uri.getPathSegments(pathPieces); // No device given - if (pathPieces.size() <= prefixPath.size() + 1) { + if (pathPieces.size() <= matchPieces.size()) { if (dataMethod != DevicesAPIMethod::GET) { throw WebResourceHandlerException("Requested API method requires a device", Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); @@ -122,18 +120,18 @@ void DevicesResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco: // Device given } else { - std::string devid = pathPieces[prefixPath.size() + 1]; + std::string devid = pathPieces[matchPieces.size()]; auto device = repoService->getDevice(devid); if (!device.expired()) { switch (dataMethod) { case DevicesAPIMethod::GET: { std::string category; - if (pathPieces.size() > prefixPath.size() + 2) { - category = pathPieces[prefixPath.size() + 2]; + if (pathPieces.size() > matchPieces.size() + 1) { + category = pathPieces[matchPieces.size() + 1]; } std::vector path; - for (int i = prefixPath.size() + 3; i < pathPieces.size(); i++) { + for (int i = matchPieces.size() + 2; i < pathPieces.size(); i++) { path.push_back(pathPieces[i]); } Variant result = getDeviceInfo(device.lock().get(), req, category, path); @@ -143,7 +141,7 @@ void DevicesResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco: case DevicesAPIMethod::COMMAND: { std::vector path; - for (int i = prefixPath.size() + 2; i < pathPieces.size(); i++) { + for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) { path.push_back(pathPieces[i]); } sendDeviceCommand(device.lock().get(), req, methodValue, path); @@ -176,14 +174,14 @@ Variant DevicesResourceHandler::getDeviceInfo(devicerepository::Device* device, // We want all info categories if (category.empty()) { for (auto c : infoCategoryFuncs) { - retval[c.first] = c.second(device, req, category, ssl); + 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(device, req, category, ssl); + retval = c->second(matchUri, device, req, category, ssl); if (path.size() > 0) { std::stringstream ss; for (auto p : path) { diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.h index b3043195..dd4a9b76 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.h +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/DevicesResource.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -18,7 +19,7 @@ namespace hos { namespace cosgi { namespace service { -namespace webserver { +namespace webresource { namespace resources { @@ -31,9 +32,11 @@ enum class DevicesAPIMethod { class DevicesResourceHandlerFactory - : public virtual WebResourceHandlerFactory { + : public virtual WebResourceHandlerFactory, + public implementation::Service { public: - typedef std::function infoCategoryFunc_t; @@ -43,7 +46,7 @@ private: std::map infoCategoryFuncs; public: DevicesResourceHandlerFactory(WebServerService* service, devicerepository::DeviceRepositoryService* repoService); - virtual Poco::Net::HTTPRequestHandler* createRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl = false ) noexcept override; + virtual Poco::Net::HTTPRequestHandler* createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override; }; @@ -75,8 +78,8 @@ class DevicesResourceHandler : public virtual WebResourceHandlerImpl { private: - DevicesResourceHandlerFactory* parent; WebServerService* service; + Poco::URI matchUri; devicerepository::DeviceRepositoryService* repoService; const std::map& infoCategoryFuncs; bool ssl; @@ -94,12 +97,12 @@ private: public: - DevicesResourceHandler( DevicesResourceHandlerFactory* parent, + DevicesResourceHandler( Poco::URI matchUri, WebServerService* service, devicerepository::DeviceRepositoryService* repoService, const std::map& infoCategoryFuncs, bool ssl = false ) - : parent(parent), service(service), repoService(repoService), infoCategoryFuncs(infoCategoryFuncs), ssl(ssl) {} + : matchUri(matchUri), service(service), repoService(repoService), infoCategoryFuncs(infoCategoryFuncs), ssl(ssl) {} virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.cpp index 4285db16..67ea7fe0 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.cpp @@ -20,30 +20,49 @@ namespace resources { using namespace httpserver; using namespace libvariant; +using namespace Poco; + + +HOS_SERVICE_IMPL_INFO( GenericWebResourceHandlerFactory, // Implementation + service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name + 1, 0 ) // Version void SchemaResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { - // Requested URL is too short to contain any schema Poco::URI uri(req.getURI()); - std::vector pathPieces; + std::vector pathPieces, matchPieces; uri.getPathSegments(pathPieces); - size_t prefixPathSize = service->getPrefixPathPieces().size(); - if (pathPieces.size() <= prefixPathSize + 1) { + matchUri.getPathSegments(matchPieces); + + // Requested URL is too short to contain any schema + if (pathPieces.size() <= matchPieces.size()) { throw WebResourceHandlerException("No schema given.", Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST); // Requested URL may contain a valid schema } else { // Get requested schema std::stringstream ss; - for (int i = prefixPathSize + 1; i < pathPieces.size(); i++) { + for (int i = matchPieces.size(); i < pathPieces.size(); i++) { ss << "/" << pathPieces[i]; } auto schemaPath = (boost::format("hos:schema:/%1%") % ss.str()).str(); // Try to retrieve schema try { auto schema = schemaLoader->GetSchema(schemaPath); + Net::HTMLForm form(req); + if (form.has("pointer")) { + std::string pointer = form.get("pointer"); + if (schema.HasPath(pointer)) { + schema = schema.GetPath(pointer); + } else { + throw WebResourceHandlerException((boost::format("Could not resolve pointer \"%1%\"") % pointer ).str(), + Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); + } + } handleResponse(req, resp, schema); + } catch(WebResourceHandlerException& e) { + throw e; } catch(std::exception& e) { throw WebResourceHandlerException((boost::format("Could not find schema \"%1%\": %2%") % schemaPath % e.what()).str(), Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND); diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.h index cc238e43..bc8b7c9c 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.h +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/resources/SchemaResource.h @@ -21,16 +21,17 @@ namespace resources { class SchemaResourceHandler - : public virtual WebResourceHandlerImpl { + : public WebResourceHandlerImpl { private: + Poco::URI matchUri; ros::NodeHandle* nodehandle; utils::SchemaLoader* schemaLoader; WebServerService* service; public: - SchemaResourceHandler(ros::NodeHandle* nodehandle, utils::SchemaLoader* schemaLoader, webserver::WebServerService* service) - : nodehandle(nodehandle), schemaLoader(schemaLoader), service(service) {} + SchemaResourceHandler(Poco::URI matchUri, ros::NodeHandle* nodehandle, utils::SchemaLoader* schemaLoader, webserver::WebServerService* service) + : matchUri(matchUri), nodehandle(nodehandle), schemaLoader(schemaLoader), service(service) {} virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; }; diff --git a/src/hos_deps/cmake/Modules/FindPoco.cmake b/src/hos_deps/cmake/Modules/FindPoco.cmake index 543f21dc..d6d5c1c6 100644 --- a/src/hos_deps/cmake/Modules/FindPoco.cmake +++ b/src/hos_deps/cmake/Modules/FindPoco.cmake @@ -163,11 +163,15 @@ IF(Poco_INCLUDE_DIR) ENDIF(Poco_BINARY_DIR AND EXISTS "${Poco_BINARY_DIR}/bin") ENDIF(Poco_LIBRARY_DIR AND Poco_FOUNDATION_LIB) IF(Poco_FOUNDATION_LIB) - IF ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - SET(DBG "d") - ELSE ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + + # Debug libs are broken on Ubuntu +# IF ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") +# SET(DBG "d") +# ELSE ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") SET(DBG "") - ENDIF ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") +# ENDIF ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + + SET(Comp_List "Foundation${DBG}") FOREACH(COMPONENT ${Poco_FIND_COMPONENTS}) FIND_LIBRARY(LIB${COMPONENT} "Poco${COMPONENT}${DBG}" Poco_LIBRARY_DIR)