.
This commit is contained in:
+40
-91
@@ -88,9 +88,9 @@ Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::U
|
||||
}
|
||||
|
||||
|
||||
void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) {
|
||||
__handleEvent(req, resp, true);
|
||||
__handleEvent(req, resp, form, true);
|
||||
/*throw webserver::webresource::WebResourceHandlerException("ToDo",
|
||||
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);*/
|
||||
} else {
|
||||
@@ -99,13 +99,13 @@ void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::
|
||||
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);
|
||||
DeviceAPIMethod method = getDeviceAPIMethod(req, form);
|
||||
if (method == DeviceAPIMethod::GET) {
|
||||
__handleInfo(req, resp);
|
||||
__handleInfo(req, resp, form);
|
||||
} else if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) {
|
||||
__handleCommand(req, resp, method);
|
||||
__handleCommand(req, resp, method, form);
|
||||
} else if (method == DeviceAPIMethod::EVENT) {
|
||||
__handleEvent(req, resp, false);
|
||||
__handleEvent(req, resp, form, false);
|
||||
} else {
|
||||
throw webserver::webresource::WebResourceHandlerException("Could not understood request",
|
||||
Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||
@@ -119,51 +119,20 @@ void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::
|
||||
|
||||
|
||||
|
||||
void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
|
||||
void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
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, query mode (there is no real query functionality implemented yet though)
|
||||
if (pathPieces.size() <= matchPieces.size()) {
|
||||
auto devices = repoService->getDevices();
|
||||
|
||||
// Parse HTTP header "Range" parameter (to support dojo/store)
|
||||
// Range header parameter
|
||||
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")) {
|
||||
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 << "\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getRangeHeaderParameter(req, rangeStart, rangeStop, rangeStop);
|
||||
|
||||
// Iterate over devices
|
||||
Variant result = Variant::List();
|
||||
@@ -177,17 +146,15 @@ void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Ne
|
||||
// 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);
|
||||
handleErrorResponse(req, resp, form, "Out of range", Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
|
||||
} else {
|
||||
handleResponse(req, resp, result);
|
||||
handleResponse(req, resp, form, result);
|
||||
}
|
||||
|
||||
// Return devices array
|
||||
} 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);
|
||||
setRangeHeaderParameter(resp, rangeStart, rangeStop, devices.size());
|
||||
handleResponse(req, resp, form, result);
|
||||
}
|
||||
|
||||
// Single device mode
|
||||
@@ -206,17 +173,17 @@ void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Ne
|
||||
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] != '/') {
|
||||
std::string pointer;
|
||||
if (getPointerParameter(pointer, form)) {
|
||||
if (pointer.size() > 0 && pointer[0] != '/') {
|
||||
path << "/";
|
||||
}
|
||||
path << tmp;
|
||||
path << pointer;
|
||||
}
|
||||
// Get device info
|
||||
Variant result = parent->getDeviceInfo(device.lock().get(), category, path.str());
|
||||
// Return device info
|
||||
handleResponse(req, resp, result);
|
||||
handleResponse(req, resp, form, result);
|
||||
} else {
|
||||
throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
|
||||
@@ -225,17 +192,11 @@ void DeviceRepoHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Ne
|
||||
}
|
||||
|
||||
|
||||
void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method) {
|
||||
void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method, Net::HTMLForm& form) {
|
||||
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
|
||||
if (pathPieces.size() <= matchPieces.size()) {
|
||||
@@ -245,8 +206,8 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco:
|
||||
|
||||
// Get message
|
||||
std::string msg;
|
||||
if (form->has("value")) {
|
||||
msg = form->get("value");
|
||||
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);
|
||||
@@ -257,8 +218,9 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco:
|
||||
for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) {
|
||||
path.push_back(pathPieces[i]);
|
||||
}
|
||||
if (form->has("pointer")) {
|
||||
URI pointer(form->get("pointer"));
|
||||
std::string pointer;
|
||||
if (getPointerParameter(pointer, form)) {
|
||||
URI pointer(pointer);
|
||||
pointer.getPathSegments(path);
|
||||
}
|
||||
|
||||
@@ -269,7 +231,7 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco:
|
||||
switch (method) {
|
||||
case DeviceAPIMethod::COMMAND:
|
||||
sendDeviceCommand(device.lock().get(), req, msg, path);
|
||||
handleResponse(req, resp, std::string("OK: Command send"));
|
||||
handleResponse(req, resp, form, std::string("OK: Command send"));
|
||||
break;
|
||||
case DeviceAPIMethod::STATUSUPDATE:
|
||||
throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method.");
|
||||
@@ -285,17 +247,11 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco:
|
||||
|
||||
|
||||
|
||||
void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket) {
|
||||
void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form, bool websocket) {
|
||||
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
|
||||
@@ -314,8 +270,8 @@ void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::N
|
||||
// Get event type
|
||||
std::string eventRaw;
|
||||
DeviceEventType event = DeviceEventType::STATE_CHANGED;
|
||||
if (form->has("onevent")) {
|
||||
eventRaw = form->get("onevent");
|
||||
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")) {
|
||||
@@ -343,8 +299,8 @@ void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::N
|
||||
}
|
||||
// Get client ID
|
||||
std::string clientID;
|
||||
if (form->has("clientid")) {
|
||||
clientID = form->get("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")) {
|
||||
@@ -355,12 +311,12 @@ void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::N
|
||||
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] != '/') {
|
||||
std::string pointer;
|
||||
if (getPointerParameter(pointer, form)) {
|
||||
if (pointer.size() > 0 && pointer[0] != '/') {
|
||||
path << "/";
|
||||
}
|
||||
path << tmp;
|
||||
path << pointer;
|
||||
}
|
||||
// Get event queue
|
||||
auto eventQueue = parent->getEventQueue(clientID, repoService);
|
||||
@@ -375,9 +331,9 @@ void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::N
|
||||
}
|
||||
eventQueue->checkRegistration(event, devptr, path.str());
|
||||
if (websocket) {
|
||||
__handleEventWebSocket(req, resp, eventQueue.get(), parent);
|
||||
__handleEventWebSocket(req, resp, form, eventQueue.get(), parent);
|
||||
} else {
|
||||
__handleEventLongPolling(req, resp, eventQueue.get(), parent);
|
||||
__handleEventLongPolling(req, resp, form, eventQueue.get(), parent);
|
||||
}
|
||||
eventQueue->unsetHandler();
|
||||
} else {
|
||||
@@ -534,18 +490,11 @@ void DeviceRepoHandler::sendDeviceCommand(devicerepository::Device* device, Net:
|
||||
}
|
||||
|
||||
|
||||
DeviceAPIMethod DeviceRepoHandler::getDeviceAPIMethod(Net::HTTPServerRequest &req) noexcept {
|
||||
std::unique_ptr<Net::HTMLForm> form;
|
||||
DeviceAPIMethod DeviceRepoHandler::getDeviceAPIMethod(Net::HTTPServerRequest &req, Net::HTMLForm& form) noexcept {
|
||||
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");
|
||||
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")) {
|
||||
@@ -565,7 +514,7 @@ DeviceAPIMethod DeviceRepoHandler::getDeviceAPIMethod(Net::HTTPServerRequest &re
|
||||
(boost::format("Unknown device api method \"%1%\"") % methodRaw).str(),
|
||||
Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST );
|
||||
}
|
||||
} else if (form->has("onevent") || req.has("On-Event") || req.has("X-On-Event")) {
|
||||
} else if (form.has("onevent") || req.has("On-Event") || req.has("X-On-Event")) {
|
||||
retval = DeviceAPIMethod::EVENT;
|
||||
}
|
||||
return retval;
|
||||
|
||||
+5
-5
@@ -115,20 +115,20 @@ public:
|
||||
bool ssl = false )
|
||||
: parent(parent), matchUri(matchUri), repoService(repoService), ssl(ssl) {}
|
||||
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override;
|
||||
|
||||
void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp);
|
||||
void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form);
|
||||
|
||||
void __handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method);
|
||||
void __handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method, Poco::Net::HTMLForm& form);
|
||||
|
||||
void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket);
|
||||
void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form, bool websocket);
|
||||
|
||||
void sendDeviceCommand( devicerepository::Device* device,
|
||||
Poco::Net::HTTPServerRequest &req,
|
||||
const std::string& cmd,
|
||||
const std::vector<std::string>& path = std::vector<std::string>() );
|
||||
|
||||
static DeviceAPIMethod getDeviceAPIMethod(Poco::Net::HTTPServerRequest &req) noexcept;
|
||||
static DeviceAPIMethod getDeviceAPIMethod(Poco::Net::HTTPServerRequest &req, Poco::Net::HTMLForm& form) noexcept;
|
||||
};
|
||||
|
||||
|
||||
|
||||
-306
@@ -1,306 +0,0 @@
|
||||
|
||||
#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
|
||||
|
||||
|
||||
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
|
||||
#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
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
x) Rule engine infos:
|
||||
======================
|
||||
http[s]://hos/api/<rule-engine-name>/
|
||||
|
||||
x) Rule package infos:
|
||||
======================
|
||||
http[s]://hos/api/<rule-engine-name>/packages
|
||||
http[s]://hos/api/<rule-engine-name>/packages/<package-name>
|
||||
|
||||
x) Rule infos:
|
||||
======================
|
||||
http[s]://hos/api/<rule-engine-name>/packages/<package-name>/rules
|
||||
http[s]://hos/api/<rule-engine-name>/packages/<package-name>/rules/<rule-name>
|
||||
|
||||
|
||||
+2
@@ -129,6 +129,8 @@ public:
|
||||
*/
|
||||
virtual size_t getRulePackageCount() noexcept = 0;
|
||||
|
||||
virtual std::string getName() noexcept = 0;
|
||||
|
||||
/**
|
||||
* Registers a rule condition factory under the given type identifier.
|
||||
*
|
||||
|
||||
+2
@@ -132,6 +132,8 @@ public:
|
||||
virtual void conditionStateChanged(bool newState, const conditions::ConditionTriggerType& trigger,
|
||||
const libvariant::Variant& lcontext = libvariant::Variant()) noexcept = 0;
|
||||
|
||||
virtual libvariant::Variant getConditionJSONRepresentation() = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
+2
@@ -116,6 +116,8 @@ public:
|
||||
*/
|
||||
virtual void setCompositeParent(RuleCompositeCondition* compositeParent) noexcept = 0;
|
||||
|
||||
virtual libvariant::Variant getJSONRepresentation() = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
+21
-4
@@ -55,12 +55,14 @@ protected:
|
||||
RuleCompositeCondition* compositeParent = nullptr;
|
||||
bool enabled = false;
|
||||
|
||||
libvariant::Variant jsonRepresentation;
|
||||
|
||||
void notifyStateChangeToParent(bool newState, ConditionTriggerType category, const libvariant::Variant& lcontext = libvariant::Variant());
|
||||
|
||||
public:
|
||||
|
||||
RuleConditionImpl(rules::Rule* parent, ConditionTriggerType triggerType, const std::string& type) :
|
||||
parent(parent), triggerType(triggerType), type(type) {}
|
||||
RuleConditionImpl(rules::Rule* parent, ConditionTriggerType triggerType, const std::string& type, const libvariant::Variant& jsonRepresentation) :
|
||||
parent(parent), triggerType(triggerType), type(type), jsonRepresentation(jsonRepresentation) {}
|
||||
|
||||
virtual rules::Rule* getParent() noexcept override {
|
||||
return parent;
|
||||
@@ -94,6 +96,10 @@ public:
|
||||
this->enabled;
|
||||
}
|
||||
|
||||
virtual libvariant::Variant getJSONRepresentation() override {
|
||||
return jsonRepresentation;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -110,8 +116,10 @@ protected:
|
||||
|
||||
public:
|
||||
|
||||
RuleCompositeConditionImpl(Rule* parent, ConditionTriggerType category, std::string type) :
|
||||
RuleConditionImpl(parent, category, type) {}
|
||||
RuleCompositeConditionImpl(Rule* parent, ConditionTriggerType category, std::string type, const libvariant::Variant& jsonRepresentation) :
|
||||
RuleConditionImpl(parent, category, type, jsonRepresentation) {
|
||||
this->jsonRepresentation.Erase("conditions");
|
||||
}
|
||||
|
||||
virtual void childStateChanged( RuleCondition* child,
|
||||
bool newState,
|
||||
@@ -133,6 +141,15 @@ public:
|
||||
this->enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
virtual libvariant::Variant getJSONRepresentation() override {
|
||||
libvariant::Variant tmp = jsonRepresentation.Copy();
|
||||
tmp["conditions"] = libvariant::Variant::List();
|
||||
for (auto c : children) {
|
||||
tmp["conditions"].Append(c->getJSONRepresentation());
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@ void RuleEngineServiceImpl::setRepoService(devicerepository::DeviceRepositorySer
|
||||
|
||||
void RuleEngineServiceImpl::init() noexcept {
|
||||
// Get public parameters
|
||||
nodehandle->getParam("ruleengine_prefix", ruleEnginePrefix);
|
||||
nodehandle->getParam("hos_param_prefix", hosParamPrefix);
|
||||
nodehandle->getParam("ruleengine_name", engineName);
|
||||
|
||||
// Register condition factories
|
||||
registerRuleConditionFactory("logical_or", std::make_shared<rules::conditions::LogicalOrConditionFactory>());
|
||||
@@ -77,7 +78,9 @@ void RuleEngineServiceImpl::init() noexcept {
|
||||
|
||||
// Create rule packages
|
||||
XmlRpc::XmlRpcValue rawValue;
|
||||
nodehandle->getParam(ruleEnginePrefix, rawValue);
|
||||
std::stringstream ss;
|
||||
ss << hosParamPrefix << "/" << engineName;
|
||||
nodehandle->getParam(ss.str(), rawValue);
|
||||
if (rawValue.getType() == XmlRpc::XmlRpcValue::TypeStruct) {
|
||||
for (auto pkg : rawValue) {
|
||||
std::string pname = pkg.first;
|
||||
@@ -98,7 +101,7 @@ void RuleEngineServiceImpl::init() noexcept {
|
||||
|
||||
void RuleEngineServiceImpl::addRulePkg(const std::string& name) {
|
||||
std::stringstream ss;
|
||||
ss << ruleEnginePrefix << "/" << name;
|
||||
ss << hosParamPrefix << "/" << engineName << "/" << name;
|
||||
XmlRpc::XmlRpcValue rawValue;
|
||||
nodehandle->getParam(ss.str(), rawValue);
|
||||
addRulePkg(name, static_cast<std::string>(rawValue));
|
||||
|
||||
@@ -74,7 +74,8 @@ private:
|
||||
devicerepository::DeviceRepositoryService* repoService = nullptr;
|
||||
ros::NodeHandle* nodehandle = nullptr;
|
||||
|
||||
std::string ruleEnginePrefix = "/hos/rules/ruleengine0";
|
||||
std::string hosParamPrefix = "/hos/rules";
|
||||
std::string engineName = "ruleengine0";
|
||||
|
||||
rulePkgMap_t rulePkgs;
|
||||
rulePkgMap_t noExecRulePkgs;
|
||||
@@ -110,6 +111,10 @@ public:
|
||||
|
||||
virtual size_t getRulePackageCount() noexcept override;
|
||||
|
||||
virtual std::string getName() noexcept override {
|
||||
return engineName;
|
||||
}
|
||||
|
||||
virtual devicerepository::DeviceRepositoryService* getDeviceRepository() noexcept override {
|
||||
return repoService;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,10 @@ public:
|
||||
|
||||
std::shared_ptr<conditions::RuleCondition> createRuleCondition(const Variant& raw) override;
|
||||
|
||||
virtual Variant getConditionJSONRepresentation() override {
|
||||
return condition->getJSONRepresentation();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
+6
-3
@@ -95,7 +95,8 @@ std::shared_ptr<RuleCondition> DeviceStatusMsgConditionFactory::createCondition(
|
||||
if (raw.HasPath("msgvalue")) {
|
||||
msgValue = raw["msgvalue"];
|
||||
}
|
||||
retval = std::make_shared<DeviceStatusMsgCondition>(parent, deviceID, msgPointer, msgValue);
|
||||
Variant rep = raw.Copy();
|
||||
retval = std::make_shared<DeviceStatusMsgCondition>(parent, deviceID, msgPointer, msgValue, rep);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -154,7 +155,8 @@ std::shared_ptr<RuleCondition> DeviceCommandMsgConditionFactory::createCondition
|
||||
if (raw.HasPath("msgvalue")) {
|
||||
msgValue = raw["msgvalue"];
|
||||
}
|
||||
retval = std::make_shared<DeviceCommandMsgCondition>(parent, deviceID, msgPointer, msgValue);
|
||||
Variant rep = raw.Copy();
|
||||
retval = std::make_shared<DeviceCommandMsgCondition>(parent, deviceID, msgPointer, msgValue, rep);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -217,7 +219,8 @@ std::shared_ptr<RuleCondition> DeviceStateChangeConditionFactory::createConditio
|
||||
if (raw.HasPath("tovalue")) {
|
||||
toValue = raw["tovalue"];
|
||||
}
|
||||
retval = std::make_shared<DeviceStateChangeCondition>(parent, deviceID, statePointer, fromValue, toValue);
|
||||
Variant rep = raw.Copy();
|
||||
retval = std::make_shared<DeviceStateChangeCondition>(parent, deviceID, statePointer, fromValue, toValue, rep);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -41,8 +41,9 @@ public:
|
||||
const std::string& deviceID,
|
||||
const std::string& msgPointer,
|
||||
const Variant& msgValue,
|
||||
const std::set<devicerepository::DeviceEventType> _listenerKeys )
|
||||
: RuleConditionImpl(parent, category, type), deviceID(deviceID),
|
||||
const std::set<devicerepository::DeviceEventType> _listenerKeys,
|
||||
const libvariant::Variant& jsonRepresentation)
|
||||
: RuleConditionImpl(parent, category, type, jsonRepresentation), deviceID(deviceID),
|
||||
msgPointer(msgPointer), msgValue(msgValue), _listenerKeys(_listenerKeys) {}
|
||||
|
||||
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType,
|
||||
@@ -62,9 +63,10 @@ public:
|
||||
DeviceStatusMsgCondition( Rule* parent,
|
||||
const std::string& deviceID,
|
||||
const std::string& msgPointer,
|
||||
const Variant& msgValue)
|
||||
const Variant& msgValue,
|
||||
const libvariant::Variant& jsonRepresentation)
|
||||
: DeviceMsgCondition(parent, ConditionTriggerType::Event, "devicestatusmsg", deviceID,
|
||||
msgPointer, msgValue, {devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED}) {}
|
||||
msgPointer, msgValue, {devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED}, jsonRepresentation) {}
|
||||
|
||||
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType,
|
||||
devicerepository::DeviceEvent>::observable_type* o,
|
||||
@@ -86,9 +88,9 @@ class DeviceCommandMsgCondition
|
||||
: public DeviceMsgCondition {
|
||||
|
||||
public:
|
||||
DeviceCommandMsgCondition(Rule* parent, const std::string deviceID, const std::string msgPointer, const Variant msgValue)
|
||||
DeviceCommandMsgCondition(Rule* parent, const std::string deviceID, const std::string msgPointer, const Variant msgValue, const libvariant::Variant& jsonRepresentation)
|
||||
: DeviceMsgCondition(parent, ConditionTriggerType::Event, "devicecommandmsg", deviceID,
|
||||
msgPointer, msgValue, {devicerepository::DeviceEventType::COMMAND_RECEIVED}) {}
|
||||
msgPointer, msgValue, {devicerepository::DeviceEventType::COMMAND_RECEIVED}, jsonRepresentation) {}
|
||||
|
||||
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType,
|
||||
devicerepository::DeviceEvent>::observable_type* o,
|
||||
@@ -116,9 +118,10 @@ public:
|
||||
const std::string deviceID,
|
||||
const std::string statePointer,
|
||||
const Variant fromValue,
|
||||
const Variant toValue )
|
||||
const Variant toValue,
|
||||
const libvariant::Variant& jsonRepresentation )
|
||||
: DeviceMsgCondition(parent, ConditionTriggerType::Event, "devicestatechange", deviceID, statePointer,
|
||||
toValue, {devicerepository::DeviceEventType::STATE_CHANGED}), fromValue(fromValue) {}
|
||||
toValue, {devicerepository::DeviceEventType::STATE_CHANGED}, jsonRepresentation), fromValue(fromValue) {}
|
||||
|
||||
virtual void notificationReceived(interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>::observable_type* o, const devicerepository::DeviceEventType& key, const devicerepository::DeviceEvent* e) noexcept override;
|
||||
};
|
||||
|
||||
+2
-1
@@ -16,7 +16,8 @@ namespace conditions {
|
||||
std::shared_ptr<RuleCondition> LogicalAndConditionFactory::createCondition(rules::Rule* parent, const Variant& raw) {
|
||||
std::shared_ptr<LogicalAndCondition> retval;
|
||||
if (raw.HasPath("conditions") && raw["conditions"].IsList() && raw["conditions"].Size() > 0) {
|
||||
retval = std::make_shared<LogicalAndCondition>(parent);
|
||||
Variant rep = raw.Copy();
|
||||
retval = std::make_shared<LogicalAndCondition>(parent, rep);
|
||||
for (auto c : raw["conditions"].AsList()) {
|
||||
auto cond = parent->createRuleCondition(c);
|
||||
if (cond) {
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ protected:
|
||||
unsigned trueChildrenCount = 0;
|
||||
|
||||
public:
|
||||
LogicalAndCondition(Rule* parent) :
|
||||
RuleCompositeConditionImpl(parent, ConditionTriggerType::Both, "logical_and") {}
|
||||
LogicalAndCondition(Rule* parent, const libvariant::Variant& jsonRepresentation) :
|
||||
RuleCompositeConditionImpl(parent, ConditionTriggerType::Both, "logical_and", jsonRepresentation) {}
|
||||
|
||||
virtual void addChild(std::shared_ptr<RuleCondition> child) override;
|
||||
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ namespace conditions {
|
||||
std::shared_ptr<RuleCondition> LogicalOrConditionFactory::createCondition(rules::Rule* parent, const Variant& raw) {
|
||||
std::shared_ptr<LogicalOrCondition> retval;
|
||||
if (raw.HasPath("conditions") && raw["conditions"].IsList() && raw["conditions"].Size() > 0) {
|
||||
retval = std::make_shared<LogicalOrCondition>(parent);
|
||||
Variant rep = raw.Copy();
|
||||
retval = std::make_shared<LogicalOrCondition>(parent, rep);
|
||||
for (auto c : raw["conditions"].AsList()) {
|
||||
auto cond = parent->createRuleCondition(c);
|
||||
if (cond) {
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ protected:
|
||||
unsigned trueChildrenCount = 0;
|
||||
|
||||
public:
|
||||
LogicalOrCondition(Rule* parent) :
|
||||
RuleCompositeConditionImpl(parent, ConditionTriggerType::Both, "logical_or") {}
|
||||
LogicalOrCondition(Rule* parent, const libvariant::Variant& jsonRepresentation) :
|
||||
RuleCompositeConditionImpl(parent, ConditionTriggerType::Both, "logical_or", jsonRepresentation) {}
|
||||
|
||||
virtual void addChild(std::shared_ptr<RuleCondition> child) override;
|
||||
virtual void childStateChanged(RuleCondition* child, bool newState, ConditionTriggerType category,
|
||||
|
||||
+26
-83
@@ -52,6 +52,10 @@ RuleEngineHandlerFactory::RuleEngineHandlerFactory(RuleEngineService* ruleServic
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
|
||||
}) {
|
||||
|
||||
engineInfoFuncs.add("id", [](RuleEngineService* engine, const std::string& category)->Variant {
|
||||
return engine->getName();
|
||||
});
|
||||
|
||||
engineInfoFuncs.add("languages", [](RuleEngineService* engine, const std::string& category)->Variant {
|
||||
Variant retval = libvariant::Variant::List();
|
||||
for (auto e : engine->getExecutionBackends()) {
|
||||
@@ -74,6 +78,10 @@ RuleEngineHandlerFactory::RuleEngineHandlerFactory(RuleEngineService* ruleServic
|
||||
return pkg->getName();
|
||||
});
|
||||
|
||||
pkgInfoFuncs.add("parent", [](rules::RulePackage* pkg, const std::string& category)->Variant {
|
||||
return pkg->getParent()->getName();
|
||||
});
|
||||
|
||||
pkgInfoFuncs.add("language", [](rules::RulePackage* pkg, const std::string& category)->Variant {
|
||||
return pkg->getLanguage();
|
||||
});
|
||||
@@ -106,6 +114,10 @@ RuleEngineHandlerFactory::RuleEngineHandlerFactory(RuleEngineService* ruleServic
|
||||
return rule->getName();
|
||||
});
|
||||
|
||||
ruleInfoFuncs.add("parent", [](rules::Rule* rule, const std::string& category)->Variant {
|
||||
return rule->getParent()->getName();
|
||||
});
|
||||
|
||||
ruleInfoFuncs.add("enabled", [](rules::Rule* rule, const std::string& category)->Variant {
|
||||
return rule->isEnabled();
|
||||
});
|
||||
@@ -129,7 +141,7 @@ RuleEngineHandlerFactory::RuleEngineHandlerFactory(RuleEngineService* ruleServic
|
||||
});
|
||||
|
||||
ruleInfoFuncs.add("condition", [](rules::Rule* rule, const std::string& category)->Variant {
|
||||
return "ToDo";
|
||||
return rule->getConditionJSONRepresentation();
|
||||
});
|
||||
|
||||
}
|
||||
@@ -215,24 +227,24 @@ Variant RuleEngineHandlerFactory::getRuleInfo( rules::Rule* rule, const std::str
|
||||
|
||||
|
||||
|
||||
void RuleEngineHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
void RuleEngineHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) {
|
||||
//__handleEvent(req, resp, true);
|
||||
/*throw webserver::webresource::WebResourceHandlerException("ToDo",
|
||||
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);*/
|
||||
throw webserver::webresource::WebResourceHandlerException("ToDo",
|
||||
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);
|
||||
} else {
|
||||
if (req.getMethod() == Net::HTTPRequest::HTTP_PUT) {
|
||||
// Put Command
|
||||
throw webserver::webresource::WebResourceHandlerException("Still a ToDo.",
|
||||
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);
|
||||
} else if (req.getMethod() == Net::HTTPRequest::HTTP_GET || req.getMethod() == Net::HTTPRequest::HTTP_POST) {
|
||||
RuleEngineAPIMethod method = getRuleEngineAPIMethod(req);
|
||||
RuleEngineAPIMethod method = getRuleEngineAPIMethod(req, form);
|
||||
if (method == RuleEngineAPIMethod::GET) {
|
||||
__handleInfo(req, resp);
|
||||
__handleInfo(req, resp, form);
|
||||
} else if (method == RuleEngineAPIMethod::SET) {
|
||||
//__handleCommand(req, resp, method);
|
||||
//__handleCommand(req, resp, method, form.get());
|
||||
} else if (method == RuleEngineAPIMethod::EVENT) {
|
||||
//__handleEvent(req, resp, false);
|
||||
//__handleEvent(req, resp, false, form.get());
|
||||
} else {
|
||||
throw webserver::webresource::WebResourceHandlerException("Could not understood request",
|
||||
Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||
@@ -246,78 +258,16 @@ void RuleEngineHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::
|
||||
|
||||
|
||||
|
||||
void RuleEngineHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
void RuleEngineHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Nothing given, return engine info
|
||||
if (pathPieces.size() <= matchPieces.size()) {
|
||||
handleResponse(req, resp, parent->getEngineInfo(ruleService, "", ""));
|
||||
|
||||
/*auto pkgs = ruleService->getRulePackages();
|
||||
|
||||
// Parse HTTP header "Range" parameter (to support dojo/store)
|
||||
size_t rangeStart = 0;
|
||||
size_t rangeStop = pkgs.size() - 1;
|
||||
std::string rangeRaw;
|
||||
if (req.has("Range")) {
|
||||
rangeRaw = req.get("Range");
|
||||
} else if (req.has("X-Range")) {
|
||||
rangeRaw = req.get("X-Range");
|
||||
}
|
||||
if (!rangeRaw.empty()) {
|
||||
if (boost::algorithm::starts_with(rangeRaw, "items=")) {
|
||||
auto range = rangeRaw.substr(6);
|
||||
std::vector<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 << "\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over packages
|
||||
Variant result = Variant::List();
|
||||
if (pkgs.size() > 0) {
|
||||
for (size_t t = rangeStart; t <= rangeStop; t++) {
|
||||
auto pkg = pkgs[t].lock();
|
||||
result.Append(parent->getRulePkgInfo(pkg.get()));
|
||||
}
|
||||
}
|
||||
|
||||
// There are no devices and a range was given, probably out of range
|
||||
if (result.Size() == 0 && rangeStart > 0) {
|
||||
if (rangeStart > 0) {
|
||||
handleErrorResponse(req, resp, "Out of range", Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
|
||||
} else {
|
||||
handleResponse(req, resp, result);
|
||||
}
|
||||
|
||||
// Return devices array
|
||||
} else {
|
||||
std::string rangeOut = (boost::format("items %1%-%2%/%3%") % rangeStart % rangeStop % pkgs.size()).str();
|
||||
resp.set("Content-Range", rangeOut);
|
||||
resp.set("X-Content-Range", rangeOut);
|
||||
handleResponse(req, resp, result);
|
||||
}*/
|
||||
handleResponse(req, resp, form, parent->getEngineInfo(ruleService, "", ""));
|
||||
|
||||
// rule package given
|
||||
} else if (pathPieces.size() == matchPieces.size() + 1) {
|
||||
@@ -330,18 +280,11 @@ void RuleEngineHandler::__handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Ne
|
||||
|
||||
|
||||
|
||||
RuleEngineAPIMethod RuleEngineHandler::getRuleEngineAPIMethod(Net::HTTPServerRequest &req) noexcept {
|
||||
std::unique_ptr<Net::HTMLForm> form;
|
||||
RuleEngineAPIMethod RuleEngineHandler::getRuleEngineAPIMethod(Net::HTTPServerRequest &req, Net::HTMLForm& form) noexcept {
|
||||
RuleEngineAPIMethod retval = RuleEngineAPIMethod::GET; // default
|
||||
if (req.getMethod() == Net::HTTPRequest::HTTP_POST && req.has("Content-type")
|
||||
&& req.get("Content-type") == Net::HTMLForm::ENCODING_URL) {
|
||||
form.reset(new Net::HTMLForm(req, req.stream()));
|
||||
} else {
|
||||
form.reset(new Net::HTMLForm(req));
|
||||
}
|
||||
std::string methodRaw;
|
||||
if (form->has("method")) {
|
||||
methodRaw = form->get("method");
|
||||
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")) {
|
||||
@@ -359,7 +302,7 @@ RuleEngineAPIMethod RuleEngineHandler::getRuleEngineAPIMethod(Net::HTTPServerReq
|
||||
(boost::format("Unknown rule engine api method \"%1%\"") % methodRaw).str(),
|
||||
Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST );
|
||||
}
|
||||
} else if (form->has("onevent") || req.has("On-Event") || req.has("X-On-Event")) {
|
||||
} else if (form.has("onevent") || req.has("On-Event") || req.has("X-On-Event")) {
|
||||
retval = RuleEngineAPIMethod::EVENT;
|
||||
}
|
||||
return retval;
|
||||
|
||||
+5
-5
@@ -119,15 +119,15 @@ public:
|
||||
bool ssl = false )
|
||||
: parent(parent), matchUri(matchUri), ruleService(ruleService), ssl(ssl) {}
|
||||
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override;
|
||||
|
||||
void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp);
|
||||
void __handleInfo(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form);
|
||||
|
||||
void __handleSet(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, RuleEngineAPIMethod method);
|
||||
void __handleSet(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, RuleEngineAPIMethod method, Poco::Net::HTMLForm& form);
|
||||
|
||||
void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket);
|
||||
void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form, bool websocket);
|
||||
|
||||
static RuleEngineAPIMethod getRuleEngineAPIMethod(Poco::Net::HTTPServerRequest &req) noexcept;
|
||||
static RuleEngineAPIMethod getRuleEngineAPIMethod(Poco::Net::HTTPServerRequest &req, Poco::Net::HTMLForm& form) noexcept;
|
||||
|
||||
/*void sendDeviceCommand( devicerepository::Device* device,
|
||||
Poco::Net::HTTPServerRequest &req,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[Dolphin]
|
||||
Timestamp=2015,6,9,18,8,49
|
||||
Version=3
|
||||
ViewMode=1
|
||||
@@ -5,8 +5,8 @@ define([
|
||||
'dstore/RequestMemory',
|
||||
'dstore/SimpleQuery',
|
||||
'dstore/Trackable',
|
||||
"dojox/socket",
|
||||
"dojo/_base/xhr",
|
||||
"hos/Socket",
|
||||
"dojo/request/xhr",
|
||||
"dojox/uuid/generateRandomUuid"
|
||||
], function(config, declare, Rest, RequestMemory, SimpleQuery, Trackable, Socket, xhr, generateRandomUuid){
|
||||
|
||||
@@ -51,8 +51,9 @@ define([
|
||||
});
|
||||
_store.sendCommand = function(id, cmd) {
|
||||
console.log("ToDo: send command", cmd ,"to " + id);
|
||||
var devurl = config.hos.deviceRepoUrl + "/" + id + "?method=command&value=" + encodeURIComponent(JSON.stringify(cmd));
|
||||
xhr.get({url: devurl}).then(function(data){
|
||||
//var devurl = config.hos.deviceRepoUrl + "/" + id + "?method=command&value=" + encodeURIComponent(JSON.stringify(cmd));
|
||||
var devurl = config.hos.deviceRepoUrl + "/" + id;
|
||||
xhr.post(devurl, {handleAs: 'json', data: {method: 'command', value: JSON.stringify(cmd)}}).then(function(data){
|
||||
// Do something with the handled data
|
||||
}, function(err){
|
||||
// Handle the error condition
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
define([
|
||||
"dojo/_base/array",
|
||||
"dojo/_base/lang",
|
||||
"dojo/request/xhr",
|
||||
"dojo/aspect",
|
||||
"dojo/on",
|
||||
"dojo/Evented",
|
||||
"dojo/_base/url"
|
||||
], function(array, lang, xhr, aspect, on, Evented, dBaseUrl) {
|
||||
|
||||
var WebSocket = window.WebSocket;
|
||||
|
||||
var Socket = function(/*dojo.__XhrArgs*/ argsOrUrl){
|
||||
// summary:
|
||||
// Provides a simple socket connection using WebSocket, or alternate
|
||||
// communication mechanisms in legacy browsers for comet-style communication. This is based
|
||||
// on the WebSocket API and returns an object that implements the WebSocket interface:
|
||||
// http://dev.w3.org/html5/websockets/#websocket
|
||||
// description:
|
||||
// Provides socket connections. This can be used with virtually any Comet protocol.
|
||||
// argsOrUrl:
|
||||
// This uses the same arguments as the other I/O functions in Dojo, or a
|
||||
// URL to connect to. The URL should be a relative URL in order to properly
|
||||
// work with WebSockets (it can still be host relative, like //other-site.org/endpoint)
|
||||
// returns:
|
||||
// An object that implements the WebSocket API
|
||||
// example:
|
||||
// | require(["dojox/socket", "dojo/aspect"], function(socket, aspect) {
|
||||
// | var sock = socket({"url://comet-server/comet");
|
||||
// | // we could also add auto-reconnect support
|
||||
// | // now we can connect to standard HTML5 WebSocket-style events
|
||||
// | aspect.after(socket, "onmessage", function(event){
|
||||
// | var message = event.data;
|
||||
// | // do something with the message
|
||||
// | });
|
||||
// | // send something
|
||||
// | sock.send("hi there");
|
||||
// | ...
|
||||
// | });
|
||||
// You can also use the Reconnect module:
|
||||
// | require["dojox/socket", "dojox/socket/Reconnect"], function(dxSocket, reconnect){
|
||||
// | var socket = dxSocket({url:"/comet"});
|
||||
// | // add auto-reconnect support
|
||||
// | socket = reconnect(socket);
|
||||
if(typeof argsOrUrl == "string"){
|
||||
argsOrUrl = {url: argsOrUrl};
|
||||
}
|
||||
return WebSocket ? Socket.WebSocket(argsOrUrl, true) : Socket.LongPoll(argsOrUrl);
|
||||
};
|
||||
|
||||
Socket.WebSocket = function(args, fallback){
|
||||
// summary:
|
||||
// A wrapper for WebSocket, than handles standard args and relative URLs
|
||||
var baseURI = document.baseURI || window.location.href;
|
||||
var ws = new WebSocket(new dBaseUrl(baseURI.replace(/^http/i,'ws'), args.url));
|
||||
ws.on = function(type, listener){
|
||||
ws.addEventListener(type, listener, true);
|
||||
};
|
||||
var opened;
|
||||
aspect.after(ws, "onopen", function(event){
|
||||
opened = true;
|
||||
}, true);
|
||||
aspect.after(ws, "onclose", function(event){
|
||||
if(opened){
|
||||
return;
|
||||
}
|
||||
if(fallback){
|
||||
Socket.replace(ws, Socket.LongPoll(args), true);
|
||||
}
|
||||
}, true);
|
||||
return ws;
|
||||
};
|
||||
|
||||
Socket.replace = function(socket, newSocket, listenForOpen){
|
||||
// make the original socket a proxy for the new socket
|
||||
socket.send = lang.hitch(newSocket, "send");
|
||||
socket.close = lang.hitch(newSocket, "close");
|
||||
var proxyEvent = function(type){
|
||||
(newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){
|
||||
on.emit(socket, event.type, event);
|
||||
}, true);
|
||||
};
|
||||
|
||||
if(listenForOpen){
|
||||
proxyEvent("open");
|
||||
}
|
||||
// redirect the events as well
|
||||
array.forEach(["message", "close", "error"], proxyEvent);
|
||||
};
|
||||
|
||||
Socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
|
||||
// summary:
|
||||
// Provides a simple long-poll based comet-style socket/connection to a server and returns an
|
||||
// object implementing the WebSocket interface:
|
||||
// http://dev.w3.org/html5/websockets/#websocket
|
||||
// args:
|
||||
// This uses the same arguments as the other I/O functions in Dojo, with this addition:
|
||||
// args.interval:
|
||||
// Indicates the amount of time (in milliseconds) after a response was received
|
||||
// before another request is made. By default, a request is made immediately
|
||||
// after getting a response. The interval can be increased to reduce load on the
|
||||
// server or to do simple time-based polling where the server always responds
|
||||
// immediately.
|
||||
// args.transport:
|
||||
// Provide an alternate transport like dojo.io.script.get
|
||||
// returns:
|
||||
// An object that implements the WebSocket API
|
||||
// example:
|
||||
// | dojo.require("dojox.socket.LongPoll");
|
||||
// | var socket = dojox.socket.LongPoll({url:"/comet"});
|
||||
// or:
|
||||
// | dojo.require("dojox.socket.LongPoll");
|
||||
// | dojox.socket.LongPoll.add();
|
||||
// | var socket = dojox.socket({url:"/comet"});
|
||||
|
||||
var cancelled = false,
|
||||
first = true,
|
||||
timeoutId,
|
||||
connections = [];
|
||||
|
||||
// create the socket object
|
||||
var fire, connect;
|
||||
var socket = {
|
||||
send: function(data){
|
||||
// summary:
|
||||
// Send some data using XHR or provided transport
|
||||
var sendArgs = lang.delegate(args);
|
||||
sendArgs.rawBody = data;
|
||||
clearTimeout(timeoutId);
|
||||
var deferred = first ? (first = false) || socket.firstRequest(sendArgs) :
|
||||
socket.transport(sendArgs.url, sendArgs);
|
||||
connections.push(deferred);
|
||||
deferred.then(function(response){
|
||||
// got a response
|
||||
socket.readyState = 1;
|
||||
// remove the current connection
|
||||
connections.splice(array.indexOf(connections, deferred), 1);
|
||||
// reconnect to listen for the next message if there are no active connections,
|
||||
// we queue it up in case one of the onmessage handlers has a message to send
|
||||
if(!connections.length){
|
||||
timeoutId = setTimeout(connect, args.interval);
|
||||
}
|
||||
if(response){
|
||||
// now send the message along to listeners
|
||||
fire("message", {data: response}, deferred);
|
||||
}
|
||||
}, function(error){
|
||||
connections.splice(array.indexOf(connections, deferred), 1);
|
||||
// an error occurred, fire the appropriate event listeners
|
||||
if(!cancelled){
|
||||
fire("error", {error:error}, deferred);
|
||||
if(!connections.length){
|
||||
socket.readyState = 3;
|
||||
fire("close", {wasClean:false}, deferred);
|
||||
}
|
||||
}
|
||||
});
|
||||
return deferred;
|
||||
},
|
||||
close: function(){
|
||||
// summary:
|
||||
// Close the connection
|
||||
socket.readyState = 2;
|
||||
cancelled = true;
|
||||
var i;
|
||||
for(i = 0; i < connections.length; i++){
|
||||
connections[i].cancel();
|
||||
}
|
||||
socket.readyState = 3;
|
||||
fire("close", {wasClean:true});
|
||||
},
|
||||
transport: args.transport || xhr.post,
|
||||
args: args,
|
||||
url: args.url,
|
||||
readyState: 0,
|
||||
CONNECTING: 0,
|
||||
OPEN: 1,
|
||||
CLOSING: 2,
|
||||
CLOSED: 3,
|
||||
on: Evented.prototype.on,
|
||||
firstRequest: function(args){
|
||||
// summary:
|
||||
// This allows for special handling for the first request. This is useful for
|
||||
// providing information to disambiguate between the first request and
|
||||
// subsequent long-poll requests so the server can properly setup a
|
||||
// connection on the first connection or reject a request for an expired
|
||||
// connection if the request is not expecting to be the first for a connection.
|
||||
// This method can be overriden. The default behavior is to include a Pragma
|
||||
// header with a value of "start-long-poll"
|
||||
var headers = (args.headers || (args.headers = {}));
|
||||
headers.Pragma = "start-long-poll";
|
||||
try{
|
||||
return this.transport(args.url, args);
|
||||
}finally{
|
||||
// cleanup the header so it is not used on subsequent requests
|
||||
delete headers.Pragma;
|
||||
}
|
||||
}
|
||||
};
|
||||
fire = function(type, object, deferred){
|
||||
if(socket["on" + type]){
|
||||
object.ioArgs = deferred && deferred.ioArgs;
|
||||
object.type = type;
|
||||
on.emit(socket, type, object);
|
||||
}
|
||||
};
|
||||
connect = function(){
|
||||
if(socket.readyState == 0){
|
||||
// we fire the open event now because we really don't know when the "socket"
|
||||
// is truly open, and this gives us a to do a send() and get it included in the
|
||||
// HTTP request
|
||||
fire("open",{});
|
||||
}
|
||||
// make the long-poll connection, to wait for response from the server
|
||||
if(!connections.length){
|
||||
socket.send();
|
||||
}
|
||||
};
|
||||
// provide an alias for Dojo's connect method
|
||||
socket.connect = socket.on;
|
||||
// do the initial connection
|
||||
setTimeout(connect);
|
||||
return socket;
|
||||
};
|
||||
|
||||
return Socket;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
define([
|
||||
'dojo/_base/declare',
|
||||
'dstore/Rest',
|
||||
'dstore/RequestMemory',
|
||||
'dstore/SimpleQuery',
|
||||
'dstore/Trackable',
|
||||
'hos/Socket',
|
||||
'dojo/_base/xhr',
|
||||
'dojox/uuid/generateRandomUuid'
|
||||
], function(declare, Rest, RequestMemory, SimpleQuery, Trackable, Socket, xhr, generateRandomUuid){
|
||||
|
||||
return declare([Rest, RequestMemory, SimpleQuery, Trackable], {
|
||||
|
||||
sendDeviceCommand: function(id, cmd) {
|
||||
console.log("ToDo: send command", cmd ,"to " + id);
|
||||
var devurl = config.hos.deviceRepoUrl + "/" + id + "?method=command&value=" + encodeURIComponent(JSON.stringify(cmd));
|
||||
xhr.get({url: devurl}).then(function(data){
|
||||
// Do something with the handled data
|
||||
}, function(err){
|
||||
// Handle the error condition
|
||||
}, function(evt){
|
||||
// Handle a progress event from the request if the
|
||||
// browser supports XHR2
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
+92
-23
@@ -14,6 +14,7 @@
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
@@ -308,9 +309,8 @@ protected:
|
||||
class NotImplementedException
|
||||
: public std::exception {};
|
||||
|
||||
DataFormat getDataFormat(const Poco::Net::HTTPServerRequest& request, bool incoming = false) {
|
||||
DataFormat getDataFormat(const Poco::Net::HTTPServerRequest& request, Poco::Net::HTMLForm& form, bool incoming = false) {
|
||||
DataFormat retval = DataFormat::JSON; // defaults to json
|
||||
Poco::Net::HTMLForm form(request);
|
||||
if (form.has("format")) {
|
||||
std::string formatRaw = form.get("format");
|
||||
if (formatRaw.compare("json") == 0) {
|
||||
@@ -375,16 +375,16 @@ protected:
|
||||
|
||||
std::string encodeVariant( const libvariant::Variant& msg,
|
||||
const Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTMLForm& form,
|
||||
DataFormat format = DataFormat::AUTO_GUESS ) {
|
||||
if (format == DataFormat::AUTO_GUESS) {
|
||||
format = getDataFormat(request);
|
||||
format = getDataFormat(request, form);
|
||||
}
|
||||
switch (format) {
|
||||
case DataFormat::JSON:
|
||||
return libvariant::SerializeJSON(msg, true);
|
||||
case DataFormat::JSONP:
|
||||
{
|
||||
Poco::Net::HTMLForm form(request);
|
||||
std::string padding("callback");
|
||||
if (form.has("callback")) {
|
||||
padding = form.get("callback");
|
||||
@@ -436,24 +436,26 @@ protected:
|
||||
|
||||
void handleResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const libvariant::Variant& msg,
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK,
|
||||
const std::string& mime = "" ) {
|
||||
resp.setStatusAndReason(status);
|
||||
auto format = getDataFormat(request);
|
||||
auto format = getDataFormat(request, form);
|
||||
if (mime.empty()) {
|
||||
setContentType(resp, format);
|
||||
} else {
|
||||
resp.setContentType(mime);
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << encodeVariant(msg, request, format);
|
||||
ss << encodeVariant(msg, request, form, format);
|
||||
std::string buffer = ss.str();
|
||||
resp.sendBuffer(buffer.c_str(), buffer.size());
|
||||
}
|
||||
|
||||
void handleResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const std::string& msg,
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK,
|
||||
const std::string& mime = "text/plain" ) {
|
||||
@@ -464,6 +466,7 @@ protected:
|
||||
|
||||
void handleErrorResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const std::string& msg,
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR,
|
||||
const std::string& mime = "text/plain" ) {
|
||||
@@ -478,6 +481,7 @@ protected:
|
||||
|
||||
void handleErrorResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) {
|
||||
resp.setStatusAndReason(status);
|
||||
resp.setContentType("text/plain");
|
||||
@@ -490,6 +494,7 @@ protected:
|
||||
|
||||
void handleErrorResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const WebResourceHandlerException& e ) {
|
||||
resp.setStatusAndReason(e.getStatusCode());
|
||||
resp.setContentType("text/plain");
|
||||
@@ -502,6 +507,7 @@ protected:
|
||||
|
||||
void handleErrorResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const std::exception& e,
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR ) {
|
||||
resp.setStatusAndReason(status);
|
||||
@@ -515,6 +521,7 @@ protected:
|
||||
|
||||
void handleRequireAuthenticationResponse( Poco::Net::HTTPServerRequest& request,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
const std::string& realm,
|
||||
const std::string& body = "",
|
||||
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED,
|
||||
@@ -544,56 +551,67 @@ protected:
|
||||
<< "======== DEBUG END ========" << std::endl ;
|
||||
}
|
||||
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) {
|
||||
throw NotImplementedException();
|
||||
}
|
||||
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) {
|
||||
throw NotImplementedException();
|
||||
}
|
||||
|
||||
virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) {
|
||||
throw NotImplementedException();
|
||||
}
|
||||
|
||||
virtual void _handlePUT(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
virtual void _handlePUT(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) {
|
||||
throw NotImplementedException();
|
||||
}
|
||||
|
||||
virtual void handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override {
|
||||
std::unique_ptr<Poco::Net::HTMLForm> form;
|
||||
if (req.getMethod() == Poco::Net::HTTPRequest::HTTP_POST && req.has("Content-type")
|
||||
&& boost::starts_with(req.get("Content-type"), Poco::Net::HTMLForm::ENCODING_URL)) {
|
||||
form.reset(new Poco::Net::HTMLForm(req, req.stream()));
|
||||
} else {
|
||||
form.reset(new Poco::Net::HTMLForm(req));
|
||||
}
|
||||
try {
|
||||
try {
|
||||
_handleRequest(req, resp);
|
||||
_handleRequest(req, resp, *form.get());
|
||||
} catch (NotImplementedException& e) {
|
||||
try {
|
||||
if (req.getMethod() == Poco::Net::HTTPRequest::HTTP_GET) {
|
||||
_handleGET(req, resp);
|
||||
_handleGET(req, resp, *form.get());
|
||||
} else if (req.getMethod() == Poco::Net::HTTPRequest::HTTP_POST) {
|
||||
_handlePOST(req, resp);
|
||||
_handlePOST(req, resp, *form.get());
|
||||
} else if (req.getMethod() == Poco::Net::HTTPRequest::HTTP_PUT) {
|
||||
_handlePUT(req, resp);
|
||||
_handlePUT(req, resp, *form.get());
|
||||
} else {
|
||||
std::cout << "Unsupported HTTP Request Method" << std::endl;
|
||||
handleErrorResponse(req, resp, (boost::format("HTTP method %1% not supported") % req.getMethod()).str(),
|
||||
handleErrorResponse(req, resp, *form.get(), (boost::format("HTTP method %1% not supported") % req.getMethod()).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED);
|
||||
}
|
||||
} catch (NotImplementedException& e) {
|
||||
std::cout << "Unsupported HTTP Request Method" << std::endl;
|
||||
handleErrorResponse(req, resp, (boost::format("HTTP method %1% not supported") % req.getMethod()).str(),
|
||||
handleErrorResponse(req, resp, *form.get(), (boost::format("HTTP method %1% not supported") % req.getMethod()).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
} catch (WebResourceHandlerException& e) {
|
||||
handleErrorResponse(req, resp, e);
|
||||
handleErrorResponse(req, resp, *form.get(), e);
|
||||
} catch (std::exception& e) {
|
||||
handleErrorResponse(req, resp, e);
|
||||
handleErrorResponse(req, resp, *form.get(), e);
|
||||
} catch (...) {
|
||||
handleErrorResponse(req, resp, "Unknown exception");
|
||||
handleErrorResponse(req, resp, *form.get(), "Unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
template<class EventQueue, class HandlerFactory>
|
||||
void __handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, EventQueue* eventQueue, HandlerFactory* factory) {
|
||||
void __handleEventWebSocket( Poco::Net::HTTPServerRequest &req,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
EventQueue* eventQueue,
|
||||
HandlerFactory* factory ) {
|
||||
try {
|
||||
Poco::Net::WebSocket ws(req, resp);
|
||||
std::cout << "WebSocket connection established." << std::endl;
|
||||
@@ -627,7 +645,7 @@ protected:
|
||||
while (!breakLoop) {
|
||||
libvariant::Variant msg;
|
||||
msg = eventQueue->popEvent();
|
||||
std::string encoded = encodeVariant(msg, req);
|
||||
std::string encoded = encodeVariant(msg, req, form);
|
||||
ws.sendFrame(encoded.c_str(), encoded.size(), Poco::Net::WebSocket::FRAME_FLAG_FIN | Poco::Net::WebSocket::FRAME_OP_TEXT);
|
||||
}
|
||||
std::cout << "WebSocket connection closed." << std::endl;
|
||||
@@ -651,7 +669,11 @@ protected:
|
||||
}
|
||||
|
||||
template<class EventQueue, class HandlerFactory>
|
||||
void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, EventQueue* eventQueue, HandlerFactory* factory) {
|
||||
void __handleEventLongPolling( Poco::Net::HTTPServerRequest &req,
|
||||
Poco::Net::HTTPServerResponse &resp,
|
||||
Poco::Net::HTMLForm& form,
|
||||
EventQueue* eventQueue,
|
||||
HandlerFactory* factory ) {
|
||||
libvariant::Variant msg;
|
||||
bool breakLoop = false;
|
||||
boost::asio::deadline_timer connCheckTimer(factory->getIOService());
|
||||
@@ -690,8 +712,55 @@ protected:
|
||||
breakLoop = true;
|
||||
connCheckTimer.cancel();
|
||||
}
|
||||
handleResponse(req, resp, msg);
|
||||
handleResponse(req, resp, form, msg);
|
||||
}
|
||||
|
||||
bool getRangeHeaderParameter(Poco::Net::HTTPServerRequest &req, size_t& start, size_t& end, size_t end_max) {
|
||||
std::string rangeRaw;
|
||||
if (req.has("Range")) {
|
||||
rangeRaw = req.get("Range");
|
||||
} else if (req.has("X-Range")) {
|
||||
rangeRaw = req.get("X-Range");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
start = r1;
|
||||
end = std::min(r2, end_max);
|
||||
}
|
||||
} catch (...) {
|
||||
ROS_ERROR_STREAM("Could not parse Range parameter \"" << rangeRaw << "\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void setRangeHeaderParameter(Poco::Net::HTTPServerResponse &resp, size_t start, size_t end, size_t max) {
|
||||
std::string rangeOut = (boost::format("items %1%-%2%/%3%") % start % end % max).str();
|
||||
resp.set("Content-Range", rangeOut);
|
||||
resp.set("X-Content-Range", rangeOut);
|
||||
}
|
||||
|
||||
bool getPointerParameter(std::string& pointer, Poco::Net::HTMLForm& form) {
|
||||
if (form.has("pointer")) {
|
||||
pointer = form.get("pointer");
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -97,9 +97,17 @@ void WebServerServiceImpl::init() noexcept {
|
||||
webServerParams->setSoftwareVersion("Home Operating System alpha");
|
||||
webServerParams->setMaxThreads(10);
|
||||
|
||||
Net::ServerSocket webServerSocket(8080);
|
||||
webServer.reset(new Poco::Net::HTTPServer(&handlerFactory, webServerSocket, webServerParams));
|
||||
webServer->start();
|
||||
try {
|
||||
Net::ServerSocket webServerSocket(8080);
|
||||
webServer.reset(new Poco::Net::HTTPServer(&handlerFactory, webServerSocket, webServerParams));
|
||||
webServer->start();
|
||||
} catch(Poco::Net::NetException& e) {
|
||||
ROS_ERROR_STREAM("Could not start HTTP server: " << e.displayText());
|
||||
webServer.reset();
|
||||
} catch(...) {
|
||||
ROS_ERROR("Unknown exception while starting HTTP Server");
|
||||
webServer.reset();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -143,6 +151,9 @@ void WebServerServiceImpl::init() noexcept {
|
||||
} catch(Poco::Net::SSLException& e) {
|
||||
ROS_ERROR_STREAM("Could not start HTTPS server: " << e.displayText());
|
||||
webServerSSL.reset();
|
||||
} catch(Poco::Net::NetException& e) {
|
||||
ROS_ERROR_STREAM("Could not start HTTPS server: " << e.displayText());
|
||||
webServerSSL.reset();
|
||||
} catch(...) {
|
||||
ROS_ERROR("Unknown exception while starting HTTPS Server");
|
||||
webServerSSL.reset();
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <Poco/Net/HTTPServerRequest.h>
|
||||
#include <Poco/Net/HTTPServerResponse.h>
|
||||
#include <Poco/URI.h>
|
||||
#include <Poco/Net/HTMLForm.h>
|
||||
#include <Poco/Net/SecureServerSocket.h>
|
||||
#include <Poco/Net/SSLManager.h>
|
||||
|
||||
@@ -54,15 +53,15 @@ private:
|
||||
class DefaultRequestHandler
|
||||
: public webresource::WebResourceHandlerImpl {
|
||||
public:
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override {
|
||||
handleErrorResponse(req, resp, Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN);
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override {
|
||||
handleErrorResponse(req, resp, form, Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN);
|
||||
}
|
||||
};
|
||||
class RequireAuthenticationRequestHandler
|
||||
: public webresource::WebResourceHandlerImpl {
|
||||
public:
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override {
|
||||
handleRequireAuthenticationResponse(req, resp, "Home Operating System Login");
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override {
|
||||
handleRequireAuthenticationResponse(req, resp, form, "Home Operating System Login");
|
||||
}
|
||||
};
|
||||
class ErrorRequestHandler
|
||||
@@ -72,8 +71,8 @@ private:
|
||||
Poco::Net::HTTPResponse::HTTPStatus status;
|
||||
ErrorRequestHandler(const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus status)
|
||||
: msg(msg), status(status) {}
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override {
|
||||
handleErrorResponse(req, resp, msg, status);
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override {
|
||||
handleErrorResponse(req, resp, form, msg, status);
|
||||
}
|
||||
};
|
||||
public:
|
||||
|
||||
+13
-12
@@ -79,7 +79,7 @@ Poco::Net::HTTPRequestHandler* DirectoryResourceHandlerFactory::createRequestHan
|
||||
|
||||
|
||||
|
||||
void DirectoryResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
void DirectoryResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
|
||||
// Get "absolute" file path
|
||||
std::vector<std::string> matchPieces, pathPieces;
|
||||
@@ -95,45 +95,46 @@ void DirectoryResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poc
|
||||
if (filesystem::exists(resourcePath)) {
|
||||
// Directory
|
||||
if (filesystem::is_directory(resourcePath)) {
|
||||
serveDirectoy(req, resp, resourcePath);
|
||||
serveDirectoy(req, resp, form, resourcePath);
|
||||
// Regular file
|
||||
} else if (filesystem::is_regular_file(resourcePath)) {
|
||||
// php script
|
||||
if (hasFileExtension(resourcePath, parent->phpExtensions)) {
|
||||
servePhpFile(req, resp, resourcePath);
|
||||
servePhpFile(req, resp, form, resourcePath);
|
||||
// other
|
||||
} else {
|
||||
serveRegularFile(req, resp, resourcePath);
|
||||
serveRegularFile(req, resp, form, resourcePath);
|
||||
}
|
||||
|
||||
} else {
|
||||
handleErrorResponse(req, resp, (boost::format("\"%1%\" has unknown type") % resourcePath).str(),
|
||||
handleErrorResponse(req, resp, form, (boost::format("\"%1%\" has unknown type") % resourcePath).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
handleErrorResponse(req, resp, (boost::format("Could not find \"%1%\"") % resourcePath).str(), Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
|
||||
handleErrorResponse(req, resp, form, (boost::format("Could not find \"%1%\"") % resourcePath).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DirectoryResourceHandler::serveDirectoy(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& dir) {
|
||||
void DirectoryResourceHandler::serveDirectoy(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form, const boost::filesystem::path& dir) {
|
||||
auto index = dirGetFile(dir, parent->dirIndices);
|
||||
if (!index.empty()) {
|
||||
if (hasFileExtension(index, parent->phpExtensions)) {
|
||||
servePhpFile(req, resp, index);
|
||||
servePhpFile(req, resp, form, index);
|
||||
} else {
|
||||
serveRegularFile(req, resp, index);
|
||||
serveRegularFile(req, resp, form, index);
|
||||
}
|
||||
} else {
|
||||
handleErrorResponse(req, resp, (boost::format("Listing of directory \"%1%\" not supported") % dir).str(),
|
||||
handleErrorResponse(req, resp, form, (boost::format("Listing of directory \"%1%\" not supported") % dir).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file) {
|
||||
void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form, const boost::filesystem::path& file) {
|
||||
auto type = parent->contentTypes.find(file.extension().string());
|
||||
if (type != parent->contentTypes.end()) {
|
||||
resp.setContentType(type->second);
|
||||
@@ -148,7 +149,7 @@ void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &re
|
||||
|
||||
|
||||
|
||||
void DirectoryResourceHandler::servePhpFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file) {
|
||||
void DirectoryResourceHandler::servePhpFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form, const boost::filesystem::path& file) {
|
||||
// Compile file (ToDo: implement a cache)
|
||||
ph7_vm* pVm;
|
||||
int rc = ph7_compile_file(parent->getPH7Engine(), file.string().c_str(), &pVm, 0);
|
||||
|
||||
+4
-4
@@ -95,7 +95,7 @@ public:
|
||||
DirectoryResourceHandler(Poco::URI matchUri, DirectoryResourceHandlerFactory* parent, webserver::WebServerService* service, const DirectoryResourceEntry& resource, bool ssl)
|
||||
: matchUri(matchUri), parent(parent), service(service), resource(resource), ssl(ssl) {}
|
||||
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override;
|
||||
|
||||
static int ph7OutputConsumer(const void *pOutput, unsigned int nOutputLen, void *pUserData) {
|
||||
std::ostream& out = *(std::ostream*)pUserData;
|
||||
@@ -106,11 +106,11 @@ public:
|
||||
return PH7_OK;
|
||||
}
|
||||
|
||||
void serveDirectoy(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& dir);
|
||||
void serveDirectoy(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form, const boost::filesystem::path& dir);
|
||||
|
||||
void serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file);
|
||||
void serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form, const boost::filesystem::path& file);
|
||||
|
||||
void servePhpFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file);
|
||||
void servePhpFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form, const boost::filesystem::path& file);
|
||||
|
||||
};
|
||||
|
||||
|
||||
+37
-33
@@ -27,44 +27,48 @@ HOS_SERVICE_IMPL_INFO( GenericWebResourceHandlerFactory, // Implementation
|
||||
|
||||
|
||||
|
||||
void SchemaResourceHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
Poco::URI uri(req.getURI());
|
||||
std::vector<std::string> pathPieces, matchPieces;
|
||||
uri.getPathSegments(pathPieces);
|
||||
matchUri.getPathSegments(matchPieces);
|
||||
void SchemaResourceHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
if (req.getMethod() == Net::HTTPRequest::HTTP_GET || req.getMethod() == Net::HTTPRequest::HTTP_POST) {
|
||||
Poco::URI uri(req.getURI());
|
||||
std::vector<std::string> pathPieces, matchPieces;
|
||||
uri.getPathSegments(pathPieces);
|
||||
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 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 = 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);
|
||||
// Requested URL may contain a valid schema
|
||||
} else {
|
||||
// Get requested schema
|
||||
std::stringstream ss;
|
||||
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);
|
||||
std::string pointer;
|
||||
if (getPointerParameter(pointer, form)) {
|
||||
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, form, 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
handleErrorResponse(req, resp, form, (boost::format("HTTP method %1% not supported") % req.getMethod()).str(),
|
||||
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public:
|
||||
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;
|
||||
virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ Poco::Net::HTTPRequestHandler* WebSocketTestFactory::createRequestHandler(Poco::
|
||||
|
||||
|
||||
|
||||
void WebSocketTestHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||
void WebSocketTestHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Net::HTMLForm& form) {
|
||||
try {
|
||||
Net::WebSocket ws(req, resp);
|
||||
std::cout << "WebSocket connection established." << std::endl;
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
WebSocketTestHandler(Poco::URI matchUri, WebSocketTestFactory* parent, webserver::WebServerService* service, bool ssl)
|
||||
: matchUri(matchUri), parent(parent), service(service), ssl(ssl) {}
|
||||
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
|
||||
virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, Poco::Net::HTMLForm& form) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user