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