This commit is contained in:
Matthias Janetschek
2015-05-27 17:41:04 +02:00
parent 52ae80523c
commit 7a479433d6
9 changed files with 613 additions and 394 deletions
+3 -5
View File
@@ -8,7 +8,6 @@ find_package(catkin REQUIRED COMPONENTS
hos_cosgi_core
hos_cpp_utils
hos_deps
cmake_modules
roscpp
)
@@ -17,7 +16,6 @@ find_package(APR REQUIRED)
find_package(CELIX REQUIRED)
find_package(Boost REQUIRED)
find_package(libvariant REQUIRED)
find_package(libhttpserver REQUIRED)
find_package(Poco REQUIRED COMPONENTS Net NetSSL)
@@ -37,6 +35,8 @@ catkin_package(
add_definitions(
-std=c++11
# We need to explicitly enable boost nanoseconds
# -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
)
include_directories(
@@ -45,7 +45,6 @@ include_directories(
${APR_INCLUDE_DIR}
${APR_INCLUDE_DIR}/apr-1.0
${libvariant_INCLUDE_DIRS}
${libhttpserver_INCLUDE_DIRS}
${Poco_INCLUDE_DIRS}
)
@@ -57,7 +56,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/DevicesHTTPResource
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/resources/DevicesResource
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/resources/SchemaResource
)
@@ -65,7 +64,6 @@ target_link_libraries(hos_cosgi_webserver
${catkin_LIBRARIES}
${APR_LIBRARIES}
${libvariant_LIBRARIES}
${libhttpserver_LIBRARIES}
${Poco_LIBRARIES}
)
@@ -30,7 +30,8 @@ class WebResourceHandlerException
protected:
Poco::Net::HTTPResponse::HTTPStatus statusCode;
public:
WebResourceHandlerException(const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus statusCode)
WebResourceHandlerException( const std::string& msg,
Poco::Net::HTTPResponse::HTTPStatus statusCode = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR)
: std::runtime_error(msg), statusCode(statusCode) {}
Poco::Net::HTTPResponse::HTTPStatus getStatusCode() const {
return statusCode;
@@ -142,8 +143,10 @@ protected:
void handleResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
const libvariant::Variant& msg,
const std::string& mime = "text/plain",
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK ) {
resp.setStatusAndReason(status);
resp.setContentType(mime);
auto format = getResponseDataFormat(request);
resp.send() << encodeVariant(msg, request, format);
}
@@ -151,24 +154,42 @@ protected:
void handleResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
const std::string& msg,
const std::string& mime = "text/plain",
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK ) {
resp.setStatusAndReason(status);
resp.setContentType(mime);
resp.send() << msg;
}
void handleErrorResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
const std::string& msg = "Internal Server Error",
const std::string& msg,
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) {
resp.setStatusAndReason(status);
resp.send() << "ERROR: " << msg;
resp.setContentType("text/plain");
std::ostream& out = resp.send();
out << "ERROR: " << msg;
addDebugInfo(out, request, resp);
}
void handleErrorResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) {
resp.setStatusAndReason(status);
resp.setContentType("text/plain");
std::ostream& out = resp.send();
out << "ERROR: " << resp.getReason();
addDebugInfo(out, request, resp);
}
void handleErrorResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
const WebResourceHandlerException& e ) {
resp.setStatusAndReason(e.getStatusCode());
resp.send() << "ERROR: " << e.what();
resp.setContentType("text/plain");
std::ostream& out = resp.send();
out << "ERROR: " << e.what();
addDebugInfo(out, request, resp);
}
void handleErrorResponse( const Poco::Net::HTTPServerRequest& request,
@@ -176,7 +197,23 @@ protected:
const std::exception& e,
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) {
resp.setStatusAndReason(status);
resp.send() << "ERROR: " << e.what();
resp.setContentType("text/plain");
std::ostream& out = resp.send();
out << "ERROR: " << e.what();
addDebugInfo(out, request, resp);
}
void addDebugInfo(std::ostream& out, const Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse &resp) {
out << std::endl << std::endl
<<"========== DEBUG ==========" << std::endl
<< "getURI: " << req.getURI() << std::endl
<< "getHost: " << req.getHost() << std::endl
<< "getMethod: " << req.getMethod() << std::endl
<< "getTransferEncoding: " << req.getTransferEncoding() << std::endl
<< "getVersion: " << req.getVersion() << std::endl
<< "getContentType: " << req.getContentType() << std::endl
<< "getContentLength: " << req.getContentLength() << std::endl
<< "======== DEBUG END ========" << std::endl ;
}
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
@@ -3,6 +3,7 @@
#include <boost/algorithm/string.hpp>
#include <ros/ros.h>
#include "resources/SchemaResource.h"
#include "resources/DevicesResource.h"
@@ -62,21 +63,9 @@ void WebServerServiceImpl::setRepoService(devicerepository::DeviceRepositoryServ
void WebServerServiceImpl::RequestHandlerFactory::DefaultRequestHandler::handleRequest(
void WebServerServiceImpl::RequestHandlerFactory::DefaultRequestHandler::_handleRequest(
Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp ) {
resp.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
resp.setContentType("text/plain");
resp.send()
<< "ERROR: " << resp.getReason() << std::endl
<< "========== DEBUG ==========" << std::endl
<< "getURI: " << req.getURI() << std::endl
<< "getHost: " << req.getHost() << std::endl
<< "getMethod: " << req.getMethod() << std::endl
<< "getTransferEncoding: " << req.getTransferEncoding() << std::endl
<< "getVersion: " << req.getVersion() << std::endl
<< "getContentType: " << req.getContentType() << std::endl
<< "getContentLength: " << req.getContentLength() << std::endl
<< "======== DEBUG END ========" << std::endl ;
handleErrorResponse(req, resp, Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN);
}
@@ -118,6 +107,9 @@ void WebServerServiceImpl::init() noexcept {
return new resources::SchemaResourceHandler(nodehandle, &coreService->getSchemaLoader(), this);
})
);
registerWebResource("/devices",
std::make_shared<resources::DevicesResourceHandlerFactory>(this, repoService)
);
registerWebResource("/default",
std::make_shared<resources::GenericWebResourceHandlerFactory>([=](const Net::HTTPServerRequest&, bool)->Net::HTTPRequestHandler* {
return new resources::WebResourceHandlerImpl();
@@ -3,6 +3,7 @@
#include <hos/cosgi/service/webserver/WebServerService.h>
#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>
@@ -16,15 +17,9 @@
#include <httpserver.hpp>
//#include "resources/SchemaHTTPResource.h"
//#include "resources/DevicesHTTPResource.h"
#include <Poco/Net/HTTPServer.h>
#include <Poco/Net/HTTPRequestHandlerFactory.h>
#include <Poco/Net/HTTPRequestHandlerFactory.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
@@ -44,22 +39,6 @@ namespace service {
namespace webserver {
/**
* Used for internal error handling.
*/
class WebResourceHandlerException
: public std::runtime_error {
protected:
Poco::Net::HTTPResponse::HTTPStatus statusCode;
public:
WebResourceHandlerException(const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus statusCode)
: std::runtime_error(msg), statusCode(statusCode) {}
Poco::Net::HTTPResponse::HTTPStatus getStatusCode() {
return statusCode;
}
};
class WebServerServiceImpl
: public virtual WebServerService,
@@ -73,9 +52,9 @@ private:
WebServerServiceImpl* parent;
bool ssl;
class DefaultRequestHandler
: public Poco::Net::HTTPRequestHandler {
: public resources::WebResourceHandlerImpl {
public:
virtual void handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
};
public:
RequestHandlerFactory(WebServerServiceImpl* parent, bool ssl = false)
@@ -98,9 +77,11 @@ private:
void init() noexcept;
public:
WebServerServiceImpl() {
prefixPath.getPathSegments(prefixPathPieces);
}
~WebServerServiceImpl();
@@ -1,298 +0,0 @@
#include "DevicesHTTPResource.h"
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
// Service Namespace
namespace hos {
namespace cosgi {
namespace service {
namespace webserver {
namespace resources {
using namespace httpserver;
using namespace libvariant;
/**
* Used for internal error handling.
*/
class ResourceException
: public std::runtime_error {
protected:
int errorCode;
public:
ResourceException(const std::string& msg, int errorCode)
: std::runtime_error(msg), errorCode(errorCode) {}
int getErrorCode() {
return errorCode;
}
};
void DevicesHTTPResource::init( const std::vector<std::string>& prefixPath,
const std::string& registeredPath,
const std::string& absolutePath,
httpserver::webserver& webserver,
WebServerService& service ) {
webResource.prefixPath = prefixPath;
webResource.disallow_all();
webResource.set_allowing(MHD_HTTP_METHOD_GET, true);
webResource.set_allowing(MHD_HTTP_METHOD_POST, true);
webserver.register_resource(absolutePath, &webResource, true);
}
DevicesHTTPResource::WebResource::WebResource(devicerepository::DeviceRepositoryService* repoService)
: repoService(repoService) {
infoCategoryFuncs.insert({"type", [](devicerepository::Device* device, const httpserver::http_request& req, const std::string& category)->Variant {
return device->getDeviceType();
} });
infoCategoryFuncs.insert({"status_topic", [](devicerepository::Device* device, const httpserver::http_request& req, const std::string& category)->Variant {
return device->getStatusTopic();
} });
infoCategoryFuncs.insert({"command_topic", [](devicerepository::Device* device, const httpserver::http_request& req, const std::string& category)->Variant {
return device->getCmdTopic();
} });
infoCategoryFuncs.insert({"current_state", [](devicerepository::Device* device, const httpserver::http_request& req, const std::string& category)->Variant {
return device->getCurrentState();
} });
infoCategoryFuncs.insert({"last_state", [](devicerepository::Device* device, const httpserver::http_request& req, const std::string& category)->Variant {
return device->getLastState();
} });
/*infoCategoryFuncs.insert({"url", [this](devicerepository::Device* device, const std::string& category, const httpserver::http_request& req)->Variant {
std::stringstream ss;
req.
ss << http
return device->getLastState();
} });*/
}
void DevicesHTTPResource::WebResource::render_GET(const httpserver::http_request& req, httpserver::http_response** res) {
std::cout << "DevicesHTTPResource::render_GET()" << std::endl;
std::cout << req;
try {
// Get method parameter
DataMethod dataMethod = DataMethod::GET; // Default method is get
std::string methodValue;
std::string methodRaw = req.get_arg("method");
if (!methodRaw.empty()) {
if (methodRaw.compare("get") == 0) {
// do nothing
} else if (methodRaw.compare("command") == 0) {
dataMethod = DataMethod::COMMAND;
methodValue = req.get_arg("value");
if (methodValue.empty()) {
throw ResourceException((boost::format("Method parameter \"%1%\" requires a value parameter.") % methodRaw).str(), 400);
}
} else if (methodRaw.compare("statusupdate") == 0) {
dataMethod = DataMethod::STATUSUPDATE;
methodValue = req.get_arg("value");
if (methodValue.empty()) {
throw ResourceException((boost::format("Method parameter \"%1%\" requires a value parameter.") % methodRaw).str(), 400);
}
} else {
throw ResourceException((boost::format("Unknown method parameter \"%1%\"") % methodRaw).str(), 400);
}
}
// No device given
if (req.get_path_pieces_size() <= prefixPath.size() + 1) {
if (dataMethod != DataMethod::GET) {
throw ResourceException("Requested method requires a device.", 400);
}
Variant result;
for (auto d : repoService->getDevices()) {
auto dev = d.lock();
result[dev->getDeviceID()] = getDeviceInfo(dev.get(), req);
}
*res = new http_response(http_response_builder(SerializeJSON(result, true), 200).string_response());
// Device given
} else {
std::string devid = req.get_path_piece(prefixPath.size() + 1);
auto device = repoService->getDevice(devid);
if (!device.expired()) {
switch (dataMethod) {
case DataMethod::GET:
{
std::string category = req.get_path_piece(prefixPath.size() + 2);
std::vector<std::string> path;
for (int i = prefixPath.size() + 3; i < req.get_path_pieces_size(); i++) {
path.push_back(req.get_path_piece(i));
}
Variant result = getDeviceInfo(device.lock().get(), req, category, path);
std::string msg;
switch (getDataFormat(req)) {
case DataFormat::JSON:
msg = SerializeJSON(result, true);
break;
case DataFormat::JSONP:
{
std::string padding = req.get_arg("callback");
if (padding.empty()) {
padding = req.get_arg("jsonp");
}
if (padding.empty()) {
throw ResourceException("No callback parameter specified for JSONP.", 500);
}
std::stringstream ss;
ss << padding << "(" << SerializeJSON(result, false) << ")";
msg = ss.str();
}
break;
case DataFormat::YAML:
msg = SerializeYAML(result);
break;
case DataFormat::XMLPLIST:
msg = SerializeXMLPLIST(result, true);
break;
case DataFormat::BUNDLEHDR:
msg = SerializeBundleHdr(result);
break;
case DataFormat::MSGPACK:
msg = Serialize(result, SERIALIZE_MSGPACK);
break;
default:
throw ResourceException("Internal server error", 500);
}
*res = new http_response(http_response_builder(msg, 200).string_response());
}
break;
case DataMethod::COMMAND:
{
std::vector<std::string> path;
for (int i = prefixPath.size() + 2; i < req.get_path_pieces_size(); i++) {
path.push_back(req.get_path_piece(i));
}
sendDeviceCommand(device.lock().get(), req, methodValue, path);
*res = new http_response(http_response_builder("OK: Command send.", 200).string_response());
}
break;
case DataMethod::STATUSUPDATE:
throw ResourceException("ToDo: Implement method.", 500);
default:
throw ResourceException("Internal server error", 500);
}
} else {
throw ResourceException((boost::format("Could not find device \"%1%\"") % devid).str(), 404);
}
}
} catch (ResourceException& e) {
*res = new http_response(http_response_builder((boost::format("ERROR: %1%") % e.what()).str(), e.getErrorCode()).string_response());
} catch (std::exception& e) {
*res = new http_response(http_response_builder((boost::format("ERROR: %1%") % e.what()).str(), 500).string_response());
}
std::cout << **res;
}
void DevicesHTTPResource::WebResource::render_POST(const httpserver::http_request& req, httpserver::http_response** res) {
std::cout << "DevicesHTTPResource::render_POST()" << std::endl;
std::cout << req;
std::cout << "content: " << req.get_content() << std::endl;
*res = new http_response(http_response_builder("ToDo: Implement POST", 500).string_response());
std::cout << **res;
}
DevicesHTTPResource::WebResource::DataFormat DevicesHTTPResource::WebResource::getDataFormat(const httpserver::http_request& req) {
DataFormat retval = DataFormat::JSON;
std::string formatRaw = req.get_arg("format");
if (!formatRaw.empty()) {
if (formatRaw.compare("json") == 0) {
// do nothing
} else if (formatRaw.compare("jsonp") == 0) {
retval = DataFormat::JSONP;
} else if (formatRaw.compare("yaml") == 0) {
retval = DataFormat::YAML;
} else if (formatRaw.compare("xmlplist") == 0) {
retval = DataFormat::XMLPLIST;
} else if (formatRaw.compare("bundlehdr") == 0) {
retval = DataFormat::BUNDLEHDR;
} else if (formatRaw.compare("msgpack") == 0) {
retval = DataFormat::MSGPACK;
} else {
throw ResourceException((boost::format("Unknown format parameter \"%1%\"") % formatRaw).str(), 400);
}
}
return retval;
}
Variant DevicesHTTPResource::WebResource::getDeviceInfo(devicerepository::Device* device, const httpserver::http_request& 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(device, req, category);
}
// We want a single info category
} else {
auto c = infoCategoryFuncs.find(category);
if (c != infoCategoryFuncs.end()) {
retval = c->second(device, req, category);
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 ResourceException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % pathstr % category).str(), 404);
}
}
} else {
throw ResourceException((boost::format("Unknown device info category \"%1%\"") % category).str(), 404);
}
}
return retval;
}
void DevicesHTTPResource::WebResource::sendDeviceCommand(devicerepository::Device* device, const httpserver::http_request& 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,240 @@
#include "DevicesResource.h"
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <Poco/URI.h>
#include <Poco/Timestamp.h>
#include <hos/utils/Timestamp.h>
// Service Namespace
namespace hos {
namespace cosgi {
namespace service {
namespace webserver {
namespace resources {
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 {
return device->getDeviceType();
} });
infoCategoryFuncs.insert({"status_topic", [](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 {
return device->getCmdTopic();
} });
infoCategoryFuncs.insert({"current_state", [](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", [](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", [=](devicerepository::Device* device, Poco::Net::HTTPServerRequest &req, const std::string& category, bool ssl)->Variant {
auto& baseUri = service->getPrefixPath();
std::stringstream ss;
if (ssl) {
ss << "https://";
} else {
ss << "http://";
}
ss << req.getHost() << baseUri.toString() << "/devices/" << device->getDeviceID();
return ss.str();
} });
}
Net::HTTPRequestHandler* DevicesResourceHandlerFactory::createRequestHandler( const Net::HTTPServerRequest& request, bool ssl ) noexcept {
return new DevicesResourceHandler(this, service, repoService, infoCategoryFuncs, ssl);
}
void DevicesResourceHandler::_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 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 WebResourceHandlerException((boost::format("API method \"%1%\" requires a value parameter.") % methodRaw).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
}
}
}
auto& prefixPath = service->getPrefixPathPieces();
Poco::URI uri(req.getURI());
std::vector<std::string> pathPieces;
uri.getPathSegments(pathPieces);
// No device given
if (pathPieces.size() <= prefixPath.size() + 1) {
if (dataMethod != DevicesAPIMethod::GET) {
throw 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[prefixPath.size() + 1];
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];
}
std::vector<std::string> path;
for (int i = prefixPath.size() + 3; 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 = prefixPath.size() + 2; 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 WebResourceHandlerException("ToDo: Implement method.");
default:
throw WebResourceHandlerException("Internal Server Error");
}
} else {
throw WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
}
}
}
void DevicesResourceHandler::_handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
throw WebResourceHandlerException("ToDo: Implement POST.");
}
Variant DevicesResourceHandler::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(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);
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 WebResourceHandlerException((boost::format("Unknown path \"%1%\" for device info category \"%2%\".") % pathstr % category).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
}
}
} else {
throw WebResourceHandlerException((boost::format("Unknown device info category \"%1%\"") % category).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
}
}
return retval;
}
void DevicesResourceHandler::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
@@ -3,10 +3,12 @@
#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 <httpserver.hpp>
#include <vector>
#include <string>
#include <functional>
@@ -20,6 +22,33 @@ namespace webserver {
namespace resources {
enum class DevicesAPIMethod {
GET,
COMMAND,
STATUSUPDATE
};
class DevicesResourceHandlerFactory
: public virtual WebResourceHandlerFactory {
public:
typedef std::function<libvariant::Variant( devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& category,
bool ssl )> infoCategoryFunc_t;
private:
WebServerService* service;
devicerepository::DeviceRepositoryService* repoService;
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;
};
/**
* Implements a REST API for accessing the device repository and getting info about a device.
*
@@ -42,66 +71,47 @@ namespace resources {
* 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 DevicesHTTPResource
: public virtual webserver::WebResource {
class DevicesResourceHandler
: public virtual WebResourceHandlerImpl {
private:
DevicesResourceHandlerFactory* parent;
WebServerService* service;
devicerepository::DeviceRepositoryService* repoService;
const std::map<std::string, DevicesResourceHandlerFactory::infoCategoryFunc_t>& infoCategoryFuncs;
bool ssl;
/**
* libhttpserver does a static_cast with any class registered as http resource. Unfortunately this breaks classes
* using virtual inheritance. Therefore we register this inner class which does not use virtual inheritance.
*/
class WebResource
: public httpserver::http_resource<WebResource> {
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>() );
public:
enum class DataMethod { GET, COMMAND, STATUSUPDATE };
enum class DataFormat { JSON, JSONP, YAML, XMLPLIST, BUNDLEHDR, MSGPACK };
typedef std::function<libvariant::Variant( devicerepository::Device* device,
const httpserver::http_request& req,
const std::string& category )> infoCategoryFunc_t;
devicerepository::DeviceRepositoryService* repoService = nullptr;
std::vector<std::string> prefixPath;
std::map<std::string, infoCategoryFunc_t> infoCategoryFuncs;
WebResource(devicerepository::DeviceRepositoryService* repoService);
void render_GET(const httpserver::http_request& req, httpserver::http_response** res);
void render_POST(const httpserver::http_request& req, httpserver::http_response** res);
DataFormat getDataFormat(const httpserver::http_request& req);
libvariant::Variant getDeviceInfo( devicerepository::Device* device,
const httpserver::http_request& req,
const std::string& category = std::string(),
const std::vector<std::string>& path = std::vector<std::string>() );
void sendDeviceCommand( devicerepository::Device* device,
const httpserver::http_request& req,
const std::string& cmd,
const std::vector<std::string>& path = std::vector<std::string>() );
} webResource;
void sendDeviceCommand( devicerepository::Device* device,
Poco::Net::HTTPServerRequest &req,
const std::string& cmd,
const std::vector<std::string>& path = std::vector<std::string>() );
public:
DevicesHTTPResource(devicerepository::DeviceRepositoryService* repoService)
: webResource(repoService) {}
DevicesResourceHandler( DevicesResourceHandlerFactory* parent,
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) {}
virtual void init( const std::vector<std::string>& prefixPath,
const std::string& registeredPath,
const std::string& absolutePath,
httpserver::webserver& webserver,
webserver::WebServerService& service );
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 resources
} // namespace webserver
} // namespace service
} // namespace cosgi
} // namespace hos
@@ -0,0 +1,52 @@
#pragma once
#include <chrono>
#include <string>
#include <sstream>
#include <cstdio>
#include <ctime>
namespace hos {
namespace utils {
/*
* It took me way too long to come up with this solution (which I only consider as workaround,
* but currently it seems to be the best solution).
* There definitely needs to be much better support for timestamp-to-string conversion in C++,
* the current situation is way too unsatisfying. And boost isn't much of a help either.
*/
template<class _CLOCK>
std::string timestamp_to_ISO8601(const std::chrono::time_point<_CLOCK>& timestamp) {
std::stringstream ss;
std::tm tm_time;
char temp[256];
// First convert timestamp to time_t (loosing precision)
std::time_t time = std::chrono::system_clock::to_time_t(timestamp);
// Then convert time_t to tm_time
localtime_r(&time, &tm_time);
// Print first part of ISO8601 string
std::strftime(temp, sizeof(temp), "%FT%H:%M:", &tm_time);
ss << temp;
// Get lost precision by converting out time_t back to a timestamp and substracting it from our given timestamp,
// and add seconds to avoid the "0.xyz" problem
std::chrono::duration<double> msecs = timestamp - std::chrono::system_clock::from_time_t(time) + std::chrono::seconds(tm_time.tm_sec);
// Print seconds part of ISO8601 string
std::snprintf(temp, sizeof(temp), "%09.6f", msecs.count()); // is more compact than C++ counterpart
std::string secpart(temp);
std::replace(secpart.begin(), secpart.end(), '.', ',' ); // ISO 8601 prefers ',' over '.' as decimal mark.
ss << secpart;
// Get and print timezone part of ISO8601 string
std::strftime(temp, sizeof(temp), "%z", &tm_time);
ss << temp;
return ss.str();
}
} // namespace libvariant
} // namespace hos
+207
View File
@@ -0,0 +1,207 @@
# - Find the Poco includes and libraries.
# The following variables are set if Poco is found. If Poco is not
# found, Poco_FOUND is set to false.
# Poco_FOUND - True when the Poco include directory is found.
# Poco_INCLUDE_DIRS - the path to where the poco include files are.
# Poco_LIBRARY_DIR - The path to where the poco library files are.
# Poco_BINARY_DIRS - The path to where the poco dlls are.
# Poco_LIBRARIES - list of all libs from requested components.
# ----------------------------------------------------------------------------
# If you have installed Poco in a non-standard location.
# Then you have three options.
# In the following comments, it is assumed that <Your Path>
# points to the root directory of the include directory of Poco. e.g
# If you have put poco in C:\development\Poco then <Your Path> is
# "C:/development/Poco" and in this directory there will be two
# directories called "include" and "lib".
# 1) After CMake runs, set Poco_INCLUDE_DIR to <Your Path>/poco<-version>
# 2) Use CMAKE_INCLUDE_PATH to set a path to <Your Path>/poco<-version>. This will allow FIND_PATH()
# to locate Poco_INCLUDE_DIR by utilizing the PATH_SUFFIXES option. e.g.
# SET(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "<Your Path>/include")
# 3) Set an environment variable called ${POCO_ROOT} that points to the root of where you have
# installed Poco, e.g. <Your Path>. It is assumed that there is at least a subdirectory called
# Foundation/include/Poco in this path.
#
# Note:
# 1) If you are just using the poco headers, then you do not need to use
# Poco_LIBRARY_DIR in your CMakeLists.txt file.
# 2) If Poco has not been installed, then when setting Poco_LIBRARY_DIR
# the script will look for /lib first and, if this fails, then for /stage/lib.
#
# Usage:
# In your CMakeLists.txt file do something like this:
# ...
# # Poco
# FIND_PACKAGE(Poco COMPONENTS XML Net Data...)
# ...
# INCLUDE_DIRECTORIES(${Poco_INCLUDE_DIRS})
# LINK_DIRECTORIES(${Poco_LIBRARY_DIR})
#
# In Windows, we make the assumption that, if the Poco files are installed, the default directory
# will be C:\poco or C:\Program Files\Poco or C:\Programme\Poco.
MESSAGE(STATUS "Searching for Poco library...")
SET(POCO_INCLUDE_PATH_DESCRIPTION "top-level directory containing the poco include directories. E.g /usr/local/include/ or c:\\poco\\include\\poco-1.3.2")
SET(POCO_INCLUDE_DIR_MESSAGE "Set the Poco_INCLUDE_DIR cmake cache entry to the ${POCO_INCLUDE_PATH_DESCRIPTION}")
SET(POCO_LIBRARY_PATH_DESCRIPTION "top-level directory containing the poco libraries.")
SET(POCO_LIBRARY_DIR_MESSAGE "Set the Poco_LIBRARY_DIR cmake cache entry to the ${POCO_LIBRARY_PATH_DESCRIPTION}")
SET(POCO_DIR_SEARCH $ENV{POCO_ROOT})
IF(POCO_DIR_SEARCH)
FILE(TO_CMAKE_PATH ${POCO_DIR_SEARCH} POCO_DIR_SEARCH)
ENDIF(POCO_DIR_SEARCH)
IF(WIN32)
SET(POCO_DIR_SEARCH
${POCO_DIR_SEARCH}
C:/poco
D:/poco
"C:/Program Files/poco"
"C:/Programme/poco"
"D:/Program Files/poco"
"D:/Programme/poco"
)
ENDIF(WIN32)
# Add in some path suffixes. These will have to be updated whenever a new Poco version comes out.
SET(SUFFIX_FOR_INCLUDE_PATH
poco-1.3.2
poco-1.3.3
poco-1.3.4
poco-1.3.5
poco-1.3.6
)
SET(SUFFIX_FOR_LIBRARY_PATH
poco-1.3.2/lib
poco-1.3.2/lib/Linux/i686
poco-1.3.2/lib/Linux/x86_64
poco-1.3.3/lib
poco-1.3.3/lib/Linux/i686
poco-1.3.3/lib/Linux/x86_64
poco-1.3.4/lib
poco-1.3.4/lib/Linux/i686
poco-1.3.4/lib/Linux/x86_64
poco-1.3.5/lib
poco-1.3.5/lib/Linux/i686
poco-1.3.5/lib/Linux/x86_64
poco-1.3.6/lib
poco-1.3.6/lib/Linux/i686
poco-1.3.6/lib/Linux/x86_64
lib
lib/Linux/i686
lib/Linux/x86_64
)
#
# Look for an installation.
#
FIND_PATH(Poco_INCLUDE_DIR NAMES Foundation/include/Poco/SharedLibrary.h PATH_SUFFIXES ${SUFFIX_FOR_INCLUDE_PATH} PATHS
# Look in other places.
${POCO_DIR_SEARCH}
# Help the user find it if we cannot.
DOC "The ${POCO_INCLUDE_PATH_DESCRIPTION}"
)
IF(NOT Poco_INCLUDE_DIR)
# Look for standard unix include paths
FIND_PATH(Poco_INCLUDE_DIR Poco/Poco.h DOC "The ${POCO_INCLUDE_PATH_DESCRIPTION}")
ENDIF(NOT Poco_INCLUDE_DIR)
# Assume we didn't find it.
SET(Poco_FOUND 0)
# Now try to get the include and library path.
IF(Poco_INCLUDE_DIR)
IF(EXISTS "${Poco_INCLUDE_DIR}/Foundation/include/Poco/SharedLibrary.h")
SET(Poco_INCLUDE_DIRS
${Poco_INCLUDE_DIR}/CppUnit/include
${Poco_INCLUDE_DIR}/Foundation/include
${Poco_INCLUDE_DIR}/Net/include
${Poco_INCLUDE_DIR}/Util/include
${Poco_INCLUDE_DIR}/XML/include
)
SET(Poco_FOUND 1)
ELSEIF(EXISTS "${Poco_INCLUDE_DIR}/Poco/Poco.h")
SET(Poco_INCLUDE_DIRS
${Poco_INCLUDE_DIR}
)
SET(Poco_FOUND 1)
ENDIF()
IF(NOT Poco_LIBRARY_DIR)
FIND_LIBRARY(Poco_FOUNDATION_LIB NAMES PocoFoundation PocoFoundationd PATH_SUFFIXES ${SUFFIX_FOR_LIBRARY_PATH} PATHS
# Look in other places.
${Poco_INCLUDE_DIR}
${POCO_DIR_SEARCH}
/usr/lib64
/usr/lib
# Help the user find it if we cannot.
DOC "The ${POCO_LIBRARY_PATH_DESCRIPTION}"
)
SET(Poco_LIBRARY_DIR "" CACHE PATH POCO_LIBARARY_PATH_DESCRIPTION)
GET_FILENAME_COMPONENT(Poco_LIBRARY_DIR ${Poco_FOUNDATION_LIB} PATH)
SET(Poco_LIBRARIES "")
SET(Comp_List "")
IF(Poco_LIBRARY_DIR AND Poco_FOUNDATION_LIB)
# Look for the poco binary path.
SET(Poco_BINARY_DIR ${Poco_INCLUDE_DIR})
IF(Poco_BINARY_DIR AND EXISTS "${Poco_BINARY_DIR}/bin")
SET(Poco_BINARY_DIRS ${Poco_BINARY_DIR}/bin)
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")
SET(DBG "")
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)
IF (LIB${COMPONENT})
LIST(APPEND Poco_LIBRARIES "${LIB${COMPONENT}}")
LIST(APPEND Comp_List "${COMPONENT}${DBG}")
ENDIF(LIB${COMPONENT})
ENDFOREACH(COMPONENT)
LIST(REMOVE_DUPLICATES Comp_List)
ENDIF(Poco_FOUNDATION_LIB)
ENDIF(NOT Poco_LIBRARY_DIR)
ENDIF(Poco_INCLUDE_DIR)
IF(NOT Poco_FOUND)
IF(Poco_FIND_QUIETLY)
MESSAGE(STATUS "Poco was not found. ${POCO_INCLUDE_DIR_MESSAGE}")
ELSE(Poco_FIND_QUIETLY)
IF(Poco_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Poco was not found. ${POCO_INCLUDE_DIR_MESSAGE}")
ENDIF(Poco_FIND_REQUIRED)
ENDIF(Poco_FIND_QUIETLY)
ELSE(NOT Poco_FOUND)
MESSAGE(STATUS " Found Poco!")
SET(COMPONENT_STR "components found:")
FOREACH(comp ${Comp_List})
SET(COMPONENT_STR "${COMPONENT_STR}, ${comp}")
ENDFOREACH(comp ${Comp_List})
STRING(REPLACE ":," ":" COMPONENT_LSTR ${COMPONENT_STR})
MESSAGE(STATUS "${COMPONENT_LSTR}.")
ENDIF(NOT Poco_FOUND)
#I added this in to add "libdl" on non-Windows systems. Technically dl is only neded if the "Foundation" component is used,
#but i doesn't hurt to add it in anyway - mas
if(Poco_FOUND AND NOT WIN32)
LIST(APPEND Poco_LIBRARIES "dl")
endif(Poco_FOUND AND NOT WIN32)