This commit is contained in:
2015-06-10 20:28:48 +02:00
parent 3451e66f16
commit 632a792f3a
33 changed files with 595 additions and 854 deletions
@@ -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;
@@ -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;
};
@@ -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
@@ -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
+16
View File
@@ -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>
@@ -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.
*
@@ -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;
};
@@ -116,6 +116,8 @@ public:
*/
virtual void setCompositeParent(RuleCompositeCondition* compositeParent) noexcept = 0;
virtual libvariant::Variant getJSONRepresentation() = 0;
};
@@ -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();
}
};
@@ -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;
}
@@ -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;
};
@@ -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) {
@@ -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;
@@ -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) {
@@ -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,
@@ -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;
@@ -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
});
};
});
});
@@ -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:
@@ -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);
@@ -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);
};
@@ -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);
}
}
@@ -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;
};
@@ -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;
};