This commit is contained in:
2015-06-01 09:21:36 +02:00
parent 3902ab14a7
commit 03d71034f8
9 changed files with 262 additions and 24 deletions
@@ -55,7 +55,7 @@ bundle(hos_cosgi_devicerepository
SOURCES
${PROJECT_SOURCE_DIR}/src/BundleActivator
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webresource/DeviceRepoHandler
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler
)
target_link_libraries(hos_cosgi_devicerepository
@@ -13,7 +13,7 @@
#include <hos/cosgi/service/core/CoreService.h>
// Services
#include "hos/cosgi/service/devicerepository/DeviceRepositoryServiceImpl.h"
#include "hos/cosgi/service/webresource/DeviceRepoHandler.h"
#include "hos/cosgi/service/devicerepository/webresource/DeviceRepoHandler.h"
using namespace hos::cosgi;
@@ -5,6 +5,7 @@
#include <boost/algorithm/string.hpp>
#include <Poco/URI.h>
#include <Poco/Timestamp.h>
#include <boost/algorithm/string.hpp>
// Service Namespace
@@ -26,7 +27,11 @@ HOS_SERVICE_IMPL_INFO( DeviceRepoHandlerFactory, // Implementation
DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService)
: repoService(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();
} });
@@ -68,6 +73,9 @@ DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepos
}
DeviceAPIMethod DeviceRepoHandlerFactory::getDeviceAPIMethod() noexcept {
}
Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::URI matchUri, const Net::HTTPServerRequest& request, bool ssl ) noexcept {
return new DeviceRepoHandler(matchUri, repoService, infoCategoryFuncs, ssl);
@@ -76,9 +84,9 @@ Net::HTTPRequestHandler* DeviceRepoHandlerFactory::createRequestHandler( Poco::U
void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
// Get method parameter
// Get API method parameter
Net::HTMLForm form(req);
DevicesAPIMethod dataMethod = DevicesAPIMethod::GET; // Default method is get
DeviceAPIMethod dataMethod = DeviceAPIMethod::GET; // Default method is get
std::string methodValue;
if (form.has("method")) {
std::string methodRaw = form.get("method");
@@ -86,10 +94,10 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net:
if (methodRaw == "get") {
// do nothing
} else if (methodRaw == "command") {
dataMethod = DevicesAPIMethod::COMMAND;
dataMethod = DeviceAPIMethod::COMMAND;
requireValue = true;
} else if (methodRaw.compare("statusupdate") == 0) {
dataMethod = DevicesAPIMethod::STATUSUPDATE;
dataMethod = DeviceAPIMethod::STATUSUPDATE;
requireValue = true;
} else {
throw webserver::webresource::WebResourceHandlerException((boost::format("Unknown API method \"%1%\"") % methodRaw).str(),
@@ -112,16 +120,61 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net:
// No device given
if (pathPieces.size() <= matchPieces.size()) {
if (dataMethod != DevicesAPIMethod::GET) {
if (dataMethod != DeviceAPIMethod::GET) {
throw webserver::webresource::WebResourceHandlerException("Requested API method requires a device",
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
}
Variant result;
for (auto d : repoService->getDevices()) {
auto dev = d.lock();
result[dev->getDeviceID()] = getDeviceInfo(dev.get(), req);
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);
}
handleResponse(req, resp, result);
// Device given
} else {
@@ -129,7 +182,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net:
auto device = repoService->getDevice(devid);
if (!device.expired()) {
switch (dataMethod) {
case DevicesAPIMethod::GET:
case DeviceAPIMethod::GET:
{
std::string category;
if (pathPieces.size() > matchPieces.size() + 1) {
@@ -143,7 +196,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net:
handleResponse(req, resp, result);
}
break;
case DevicesAPIMethod::COMMAND:
case DeviceAPIMethod::COMMAND:
{
std::vector<std::string> path;
for (int i = matchPieces.size()+1; i < pathPieces.size(); i++) {
@@ -153,7 +206,7 @@ void DeviceRepoHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net:
handleResponse(req, resp, std::string("OK: Command send"));
}
break;
case DevicesAPIMethod::STATUSUPDATE:
case DeviceAPIMethod::STATUSUPDATE:
throw webserver::webresource::WebResourceHandlerException("ToDo: Implement method.");
default:
throw webserver::webresource::WebResourceHandlerException("Internal Server Error");
@@ -230,6 +283,22 @@ void DeviceRepoHandler::sendDeviceCommand(devicerepository::Device* device, Net:
device->sendCommand(cmdMsg);
}
void DeviceRepoHandler::notificationReceived(interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>::observable_type* o, const devicerepository::DeviceEventType& key, const devicerepository::DeviceEvent* e) noexcept {
switch (key) {
case devicerepository::DeviceEventType::DEVICE_ADDED:
break;
case devicerepository::DeviceEventType::DEVICE_REMOVED:
break;
case devicerepository::DeviceEventType::COMMAND_RECEIVED:
break;
case devicerepository::DeviceEventType::STATUS_UPDATE_RECEIVED:
break;
case devicerepository::DeviceEventType::STATE_CHANGED:
break;
default:
break;
}
}
@@ -24,7 +24,7 @@ namespace webresource {
enum class DevicesAPIMethod {
enum class DeviceAPIMethod {
GET,
COMMAND,
STATUSUPDATE
@@ -47,6 +47,7 @@ private:
std::map<std::string, infoCategoryFunc_t> infoCategoryFuncs;
public:
DeviceRepoHandlerFactory(devicerepository::DeviceRepositoryService* repoService);
virtual Poco::Net::HTTPRequestHandler* createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override;
virtual void setWebServerService(webserver::WebServerService* service) noexcept override {
@@ -57,6 +58,8 @@ public:
return false;
}
static DeviceAPIMethod getDeviceAPIMethod() noexcept;
};
@@ -83,8 +86,52 @@ public:
* 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 interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>,
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) {}
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 notificationReceived( interface::Observer<devicerepository::DeviceEventType,
devicerepository::DeviceEvent>::observable_type* o,
const devicerepository::DeviceEventType& key,
const devicerepository::DeviceEvent* e) noexcept override;
};
class DeviceRepoHandler
: public virtual webserver::webresource::WebResourceHandlerImpl {
: public interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>,
public webserver::webresource::WebResourceHandlerImpl {
private:
Poco::URI matchUri;
@@ -115,6 +162,11 @@ public:
virtual void _handlePOST(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override;
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType,
devicerepository::DeviceEvent>::observable_type* o,
const devicerepository::DeviceEventType& key,
const devicerepository::DeviceEvent* e) noexcept override;
};
+32
View File
@@ -0,0 +1,32 @@
HTTP - GET / HTTP - POST (application/x-www-form-urlencoded):
==============================================================
x) [OUT] DeviceAPIMethod::GET [Args: format, pointer, query?]
x) [IN] DeviceAPIMethod::COMMAND [Args: format, pointer, msg]
x) [IN] DeviceAPIMethod::STATUS_UPDATE [Args: format, pointer, msg]
x) [EVENT] DeviceAPIMethod::REGISTER_EVENT [Args: format, pointer, event]
- Long-Polling
- WebSocket
API Method Parameter:
-- Query: method = get | command | statusupdate | event
-- Header: API-METHOD | X-API-METHOD
Common Parameters:
x) Format-Parameters:
-- Query: format = json | jsonp | xmlplist | ...
-- Query: json | jsonp [= <callback>] | xmlplist | ...
-- Query: callback = callback - in conjunction with jsonp or format=jsonp, defaults to callback
-- Header: application/[x-]javascript = jsonp | application/json = json | application/yaml = yaml | application/xml = xmlplist
x) Pointer Parameter:
-- Query: pointer = <selector in JSON Pointer format>
-- URL: .../devices/<id>/[.../]<pointer>
COMMAND/STATUS_UPDATE Parameters:
-- Query: body = <command | status-update>
EVENT Parameters:
-- Query: event = command | statusupdate | statechange | added | removed | all
@@ -0,0 +1,32 @@
define([
"dojo/_base/config",
"dojo/store/JsonRest",
"dojox/socket",
"dojo/_base/xhr"
], function(config, JsonRest, Socket, xhr){
var _store = new JsonRest({
target: config.hos.deviceRepoUrl
});
var _updateSocketOpened = false;
var _updateSocket = new Socket({
url: config.hos.deviceRepoUrl,
interval: 5000,
transport: xhr.get });
_updateSocket.on("open", function(event){
_socketOpened = true;
console.log("Socket Opened ", event.message);
});
_updateSocket.on("close", function(event){
_socketOpened = false;
console.log("Socket Closed ", event.message);
});
_updateSocket.on("error", function(event){
_socketOpened = false;
console.log("Socket Error ", event.message);
});
_updateSocket.on("message", function(event) {
var data = event.data;
console.log("Message: " /*+ data*/);
});
return _store;
});
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial: Hello Dojo!</title>
<link rel="stylesheet" href="dojo/dijit/themes/claro/claro.css" media="screen">
<!-- configure Dojo -->
<script>
var dojoConfig = {
async: true,
parseOnLoad: true,
isDebug: true,
deps: [
"dojo/parser"
],
hos: {
deviceRepoUrl: "/hos/api/devices/"
}
};
</script>
<!-- load Dojo -->
<script src="dojo/dojo/dojo.js"
data-dojo-config="async: true"></script>
</head>
<body class="claro">
<script>
require(["hos/DeviceRepo"], function(DeviceRepo){
var self = this;
var devices = DeviceRepo.query()
.then(function(data){
console.log("Data: ", data);
}, function(err){
console.log("Error: ", err);
}, function(update){
console.log("Update: ", update);
}
);
});
</script>
</body>
</html>
@@ -273,13 +273,13 @@ protected:
_handlePOST(req, resp);
} else {
std::cout << "Unsupported HTTP Request Method" << std::endl;
handleErrorResponse(req, resp, (boost::format("Unsupported HTTP Request Method %1%") % req.getMethod()).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
handleErrorResponse(req, resp, (boost::format("HTTP method %1% not implemented") % 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("Unsupported HTTP Request Method %1%") % req.getMethod()).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
handleErrorResponse(req, resp, (boost::format("HTTP method %1% not implemented") % req.getMethod()).str(),
Poco::Net::HTTPResponse::HTTPStatus::HTTP_NOT_IMPLEMENTED);
}
}
} catch (WebResourceHandlerException& e) {
@@ -57,11 +57,12 @@ void WebServerServiceImpl::setCoreService(service::core::CoreService* coreServic
Poco::Net::HTTPRequestHandler* WebServerServiceImpl::RequestHandlerFactory::createRequestHandler( const Poco::Net::HTTPServerRequest& request) {
WebResourceHandlerFactory* factory = nullptr;
std::string matchUri;
Poco::URI uri(request.getURI());
// We want a longest prefix match.
// Since we have an ordered map, we just need to iterate it in reverse order and return the first match.
for (auto i = parent->webResourceFactories.rbegin(); i != parent->webResourceFactories.rend(); i++) {
if ( boost::algorithm::starts_with(request.getURI(), i->first)
&& ( request.getURI().size() == i->first.size() || request.getURI()[i->first.size()] == '/' ) ) {
if ( boost::algorithm::starts_with(uri.getPath(), i->first)
&& ( uri.getPath().size() == i->first.size() || uri.getPath()[i->first.size()] == '/' ) ) {
factory = i->second;
matchUri = i->first;
break;