This commit is contained in:
2015-05-28 10:09:28 +02:00
parent 7a479433d6
commit 8be54f1a40
26 changed files with 709 additions and 128 deletions
@@ -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<ImplClass>::name = ServiceName; \
template<> const int ::hos::cosgi::service::implementation::ServiceInfo<ImplClass>::major = ServiceMajor; \
template<> const int ::hos::cosgi::service::implementation::ServiceInfo<ImplClass>::minor = ServiceMinor;
/**
* Implementation of interface::Object.
*/
+2 -1
View File
@@ -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, &reg);
CoreService* coreInterface = dynamic_cast<CoreService*>(&hosCoreService);
bundleContext_registerService(context, (char*)CoreService::SERVICE_NAME, coreInterface, nullptr, &reg);
ROS_INFO("HOS core service registered");
@@ -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}
)
@@ -46,6 +46,7 @@
<build_depend>roscpp</build_depend>
<build_depend>hos_deps</build_depend>
<build_depend>hos_cpp_utils</build_depend>
<build_depend>hos_cosgi_webserver</build_depend>
<run_depend>hos_celix</run_depend>
<run_depend>hos_core_msgs</run_depend>
<run_depend>hos_cosgi_core</run_depend>
@@ -11,18 +11,25 @@
#include <hos/cosgi/celix.h>
// CoreService
#include <hos/cosgi/service/core/CoreService.h>
// 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<DeviceRepositoryServiceImpl> serviceImpl;
std::unique_ptr<webresource::DeviceRepoHandlerFactory> 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<DeviceRepositoryService*>(myUserData->serviceImpl.get());
bundleContext_registerService(context, (char*)DeviceRepositoryService::SERVICE_NAME, repoInterface, nullptr, &myUserData->serviceReg);
// web resource
WebResourceHandlerFactory* webInterface = dynamic_cast<WebResourceHandlerFactory*>(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");
@@ -17,9 +17,9 @@ namespace devicerepository {
// Service information
template<> const char* implementation::ServiceInfo<DeviceRepositoryServiceImpl>::name = service::devicerepository::DeviceRepositoryService::SERVICE_NAME;
template<> const int implementation::ServiceInfo<DeviceRepositoryServiceImpl>::major = 1;
template<> const int implementation::ServiceInfo<DeviceRepositoryServiceImpl>::minor = 0;
HOS_SERVICE_IMPL_INFO( DeviceRepositoryServiceImpl, // Implementation
service::devicerepository::DeviceRepositoryService::SERVICE_NAME, // Service Name
1, 0 ) // Version
@@ -0,0 +1,243 @@
#include "DeviceRepoHandler.h"
#include <hos/utils/Timestamp.h>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <Poco/URI.h>
#include <Poco/Timestamp.h>
// 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<std::string> 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<std::string> 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<std::string> 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<std::string>& path) {
Variant retval;
// We want all info categories
if (category.empty()) {
for (auto c : infoCategoryFuncs) {
retval[c.first] = c.second(matchUri, device, req, category, ssl);
}
// We want a single info category
} else {
auto c = infoCategoryFuncs.find(category);
if (c != infoCategoryFuncs.end()) {
retval = c->second(matchUri, device, req, category, ssl);
if (path.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<std::string>& 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
@@ -0,0 +1,118 @@
#pragma once
#include <hos/cosgi/service/Implementation.h>
#include <hos/cosgi/service/webserver/WebServerService.h>
#include <hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h>
#include <hos/cosgi/service/devicerepository/DeviceRepositoryService.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Variant/Variant.h>
#include <ros/ros.h>
#include <vector>
#include <string>
#include <functional>
// 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<DeviceRepoHandlerFactory> {
public:
typedef std::function<libvariant::Variant( Poco::URI& matchUri,
devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& category,
bool ssl )> infoCategoryFunc_t;
private:
devicerepository::DeviceRepositoryService* repoService;
std::map<std::string, infoCategoryFunc_t> 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<std::string, DeviceRepoHandlerFactory::infoCategoryFunc_t>& infoCategoryFuncs;
bool ssl;
libvariant::Variant getDeviceInfo( devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& category = std::string(),
const std::vector<std::string>& path = std::vector<std::string>() );
void sendDeviceCommand( devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& cmd,
const std::vector<std::string>& path = std::vector<std::string>() );
public:
DeviceRepoHandler( Poco::URI matchUri,
devicerepository::DeviceRepositoryService* repoService,
const std::map<std::string, DeviceRepoHandlerFactory::infoCategoryFunc_t>& 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
@@ -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<RuleEngineService*>(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<RuleEngineService*>(myUserData->serviceImpl.get());
bundleContext_registerService(context, (char*)RuleEngineServiceImpl::SERVICE_NAME, engineInterface, nullptr, &myUserData->serviceReg);
}
// Register service listener
@@ -27,15 +27,14 @@ namespace service {
namespace ruleengine {
using namespace ::libvariant;
using namespace libvariant;
// Service information
template<> const char* implementation::ServiceInfo<RuleEngineServiceImpl>::name = service::ruleengine::RuleEngineService::SERVICE_NAME;
template<> const int implementation::ServiceInfo<RuleEngineServiceImpl>::major = 1;
template<> const int implementation::ServiceInfo<RuleEngineServiceImpl>::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 {
@@ -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<ScriptingService*>(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<ScriptingService*>(myUserData->serviceImpl.get());
bundleContext_registerService(context, (char*)PyScriptingService::SERVICE_NAME, serviceInterface, nullptr, &myUserData->serviceReg);
}
// Register service listener
myUserData->myServiceListener.handle = myUserData;
@@ -15,9 +15,9 @@ namespace python {
// Service information
template<> const char* implementation::ServiceInfo<PyScriptingService>::name = service::scripting::python::PyScriptingService::SERVICE_NAME;
template<> const int implementation::ServiceInfo<PyScriptingService>::major = 1;
template<> const int implementation::ServiceInfo<PyScriptingService>::minor = 0;
HOS_SERVICE_IMPL_INFO( PyScriptingService, // Implementation
service::scripting::python::PyScriptingService::SERVICE_NAME, // Service Name
1, 0 ) // Version
+1 -2
View File
@@ -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
)
@@ -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-----
@@ -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-----
@@ -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<WebResourceHandlerFactory> factory) noexcept = 0;
virtual const Poco::URI& getPrefixPath() noexcept = 0;
virtual const std::vector<std::string>& getPrefixPathPieces() noexcept = 0;
virtual bool registerWebResource(const std::string& path, WebResourceHandlerFactory* factory) noexcept = 0;
};
@@ -3,10 +3,12 @@
#include <hos/cosgi/service/webserver/WebServerService.h>
#include <hos/cosgi/service/Implementation.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/HTMLForm.h>
#include <Poco/URI.h>
#include <Variant/Variant.h>
#include <boost/format.hpp>
#include <functional>
@@ -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<GenericWebResourceHandlerFactory> {
public:
typedef std::function<Poco::Net::HTTPRequestHandler*(const Poco::Net::HTTPServerRequest&, bool)> factory_t;
typedef std::function<Poco::Net::HTTPRequestHandler*(Poco::URI matchUri, const Poco::Net::HTTPServerRequest&, bool)> 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,
+2 -2
View File
@@ -41,13 +41,13 @@
<!-- <test_depend>gtest</test_depend> -->
<buildtool_depend>catkin</buildtool_depend>
<build_depend>hos_celix</build_depend>
<build_depend>hos_cosgi_devicerepository</build_depend>
<!-- <build_depend>hos_cosgi_devicerepository</build_depend>-->
<build_depend>hos_cosgi_core</build_depend>
<build_depend>hos_cpp_utils</build_depend>
<build_depend>hos_deps</build_depend>
<build_depend>roscpp</build_depend>
<run_depend>hos_celix</run_depend>
<run_depend>hos_cosgi_devicerepository</run_depend>
<!-- <run_depend>hos_cosgi_devicerepository</run_depend>-->
<run_depend>hos_cosgi_core</run_depend>
<run_depend>hos_cpp_utils</run_depend>
<run_depend>hos_deps</run_depend>
@@ -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<WebServerService*>(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");
@@ -1,10 +1,12 @@
#include "WebServerServiceImpl.h"
#include "resources/SchemaResource.h"
//#include "resources/DevicesResource.h"
#include <boost/algorithm/string.hpp>
#include <ros/ros.h>
#include "resources/SchemaResource.h"
#include "resources/DevicesResource.h"
#include <Poco/Net/KeyConsoleHandler.h>
#include <Poco/Net/ConsoleCertificateHandler.h>
#include <Poco/Net/SSLException.h>
namespace hos {
@@ -19,9 +21,10 @@ using namespace Poco;
template<> const char* implementation::ServiceInfo<WebServerServiceImpl>::name = service::webserver::WebServerServiceImpl::SERVICE_NAME;
template<> const int implementation::ServiceInfo<WebServerServiceImpl>::major = 1;
template<> const int implementation::ServiceInfo<WebServerServiceImpl>::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<resources::GenericWebResourceHandlerFactory>([=](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<resources::DevicesResourceHandlerFactory>(this, repoService)
);
);*/
registerWebResource("/default",
std::make_shared<resources::GenericWebResourceHandlerFactory>([=](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<resources::SchemaHTTPResource>(nodehandle, &coreService->getSchemaLoader())
);
registerWebResource( "/devices",
std::make_shared<resources::DevicesHTTPResource>(repoService)
);*/
//webServer->start(false);
initialized = true;
ROS_INFO_STREAM("WebServerService: Initialized and Ready.");
}
bool WebServerServiceImpl::registerWebResource(const std::string& path, std::shared_ptr<WebResourceHandlerFactory> 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;
@@ -6,7 +6,7 @@
#include <hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h>
#include <hos/cosgi/service/Implementation.h>
#include <hos/cosgi/service/core/CoreService.h>
#include <hos/cosgi/service/devicerepository/DeviceRepositoryService.h>
//#include <hos/cosgi/service/devicerepository/DeviceRepositoryService.h>
#include <mutex>
#include <boost/algorithm/string.hpp>
#include <memory>
@@ -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<std::string> prefixPathPieces;
Poco::URI prefixPath = Poco::URI("/hos/api");
std::unique_ptr<Poco::Net::HTTPServer> webServer;
std::unique_ptr<Poco::Net::HTTPServer> webServerSSL;
std::map<std::string, std::shared_ptr<WebResourceHandlerFactory>> webResourceFactories;
std::map<std::string, WebResourceHandlerFactory*> 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<WebResourceHandlerFactory> 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<std::string>& getPrefixPathPieces() noexcept override {
return prefixPathPieces;
}
}*/
/* Internal */
@@ -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<std::string> pathPieces, matchPieces;
matchUri.getPathSegments(matchPieces);
Poco::URI uri(req.getURI());
std::vector<std::string> 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<std::string> 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<std::string> 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) {
@@ -3,6 +3,7 @@
#include <hos/cosgi/service/webserver/WebServerService.h>
#include <hos/cosgi/service/Implementation.h>
#include <hos/cosgi/service/webserver/resources/WebResourceHandlerImpl.h>
#include <hos/cosgi/service/devicerepository/DeviceRepositoryService.h>
#include <Poco/Net/HTTPRequestHandler.h>
@@ -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<GenericWebResourceHandlerFactory> {
public:
typedef std::function<libvariant::Variant( devicerepository::Device* device,
typedef std::function<libvariant::Variant( Poco::URI& matchUri,
devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& category,
bool ssl )> infoCategoryFunc_t;
@@ -43,7 +46,7 @@ private:
std::map<std::string, infoCategoryFunc_t> 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<std::string, DevicesResourceHandlerFactory::infoCategoryFunc_t>& infoCategoryFuncs;
bool ssl;
@@ -94,12 +97,12 @@ private:
public:
DevicesResourceHandler( DevicesResourceHandlerFactory* parent,
DevicesResourceHandler( Poco::URI matchUri,
WebServerService* service,
devicerepository::DeviceRepositoryService* repoService,
const std::map<std::string, DevicesResourceHandlerFactory::infoCategoryFunc_t>& 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;
@@ -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<std::string> pathPieces;
std::vector<std::string> 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);
@@ -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;
};
+8 -4
View File
@@ -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)