.
This commit is contained in:
+124
-27
@@ -9,8 +9,12 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <Poco/Net/HTTPServerRequestImpl.h>
|
#include <Poco/Net/HTTPServerRequestImpl.h>
|
||||||
#include <Poco/Net/HTTPServerResponseImpl.h>
|
#include <Poco/Net/HTTPServerResponseImpl.h>
|
||||||
|
#include <Poco/Net/WebSocket.h>
|
||||||
|
#include "Poco/Net/NetException.h"
|
||||||
#include <Poco/Net/StreamSocket.h>
|
#include <Poco/Net/StreamSocket.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <stropts.h>
|
#include <stropts.h>
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +87,10 @@ DeviceRepoHandlerFactory::DeviceRepoHandlerFactory(devicerepository::DeviceRepos
|
|||||||
} });
|
} });
|
||||||
io_service_thread.reset(new std::thread([this] {
|
io_service_thread.reset(new std::thread([this] {
|
||||||
try {
|
try {
|
||||||
|
std::cout << "Boost IO service starting" << std::endl;
|
||||||
|
boost::asio::io_service::work work(io_service);
|
||||||
this->io_service.run();
|
this->io_service.run();
|
||||||
|
std::cout << "Boost IO service stopped" << std::endl;
|
||||||
} catch (std::exception& e) {
|
} catch (std::exception& e) {
|
||||||
ROS_ERROR_STREAM("IO-Service thread caught exception: " << e.what());
|
ROS_ERROR_STREAM("IO-Service thread caught exception: " << e.what());
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
@@ -127,6 +134,7 @@ void DeviceRepoHandlerFactory::deleteDeviceEventQueue( const std::string& client
|
|||||||
|
|
||||||
void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
||||||
if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) {
|
if(req.find("Upgrade") != req.end() && Poco::icompare(req["Upgrade"], "websocket") == 0) {
|
||||||
|
//__handleEvent(req, resp, true);
|
||||||
throw webserver::webresource::WebResourceHandlerException("ToDo",
|
throw webserver::webresource::WebResourceHandlerException("ToDo",
|
||||||
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);
|
Net::HTTPResponse::HTTP_NOT_IMPLEMENTED);
|
||||||
} else {
|
} else {
|
||||||
@@ -141,7 +149,7 @@ void DeviceRepoHandler::_handleRequest(Poco::Net::HTTPServerRequest &req, Poco::
|
|||||||
} else if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) {
|
} else if (method == DeviceAPIMethod::COMMAND || method == DeviceAPIMethod::STATUSUPDATE) {
|
||||||
__handleCommand(req, resp, method);
|
__handleCommand(req, resp, method);
|
||||||
} else if (method == DeviceAPIMethod::EVENT) {
|
} else if (method == DeviceAPIMethod::EVENT) {
|
||||||
__handleEventLongPolling(req, resp);
|
__handleEvent(req, resp, false);
|
||||||
} else {
|
} else {
|
||||||
throw webserver::webresource::WebResourceHandlerException("Could not understood request",
|
throw webserver::webresource::WebResourceHandlerException("Could not understood request",
|
||||||
Net::HTTPResponse::HTTP_BAD_REQUEST);
|
Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||||
@@ -319,8 +327,85 @@ void DeviceRepoHandler::__handleCommand(Poco::Net::HTTPServerRequest &req, Poco:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue) {
|
||||||
|
Variant msg;
|
||||||
|
bool breakLoop = false;
|
||||||
|
parent->getIOService().post([&]() {
|
||||||
|
Net::HTTPServerRequestImpl* reqImpl = static_cast<Net::HTTPServerRequestImpl*>(&req);
|
||||||
|
// I don't like the content of this loop at all.
|
||||||
|
while (!breakLoop) {
|
||||||
|
// How to find out whether the client disconnected: Monitor the socket with Posix "select":
|
||||||
|
// If select says that there is data, available() returns 0 then apparently the client has disconnected.
|
||||||
|
// WTF, is this really the only way to notice that a client properly disconnected? Internet says so.
|
||||||
|
auto sockfs = reqImpl->socket().impl()->sockfd();
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sockfs, &fds);
|
||||||
|
struct timeval timeout = {0, 1000};
|
||||||
|
int sel = select(sockfs + 1, &fds, 0, 0, &timeout);
|
||||||
|
if (sel < 0 || (sel > 0 && reqImpl->socket().available() == 0)) {
|
||||||
|
ROS_ERROR_STREAM("Apparently we have a disconnect in device event queue \"" << eventQueue->clientID << "\"");
|
||||||
|
breakLoop = true;
|
||||||
|
eventQueue->cancelPopEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
msg = eventQueue->popEvent();
|
||||||
|
} catch (DeviceEventQueue::DisconnectedException& e) {
|
||||||
|
}
|
||||||
|
breakLoop = true;
|
||||||
|
handleResponse(req, resp, msg);
|
||||||
|
};
|
||||||
|
|
||||||
void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
|
void DeviceRepoHandler::__handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue) {
|
||||||
|
try {
|
||||||
|
Net::WebSocket ws(req, resp);
|
||||||
|
std::cout << "WebSocket connection established." << std::endl;
|
||||||
|
bool error = false;
|
||||||
|
/*std::thread revcThread([&]() {
|
||||||
|
char buffer[1024];
|
||||||
|
int flags;
|
||||||
|
int n;
|
||||||
|
do {
|
||||||
|
n = ws.receiveFrame(buffer, sizeof(buffer), flags);
|
||||||
|
std::cout << Poco::format("Frame received (length=%d, flags=0x%x).", n, unsigned(flags)) << std::endl;
|
||||||
|
} while (!error && n > 0 && (flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE);
|
||||||
|
error= true;
|
||||||
|
});*/
|
||||||
|
char buffer[1024];
|
||||||
|
int flags;
|
||||||
|
int n;
|
||||||
|
auto format = getResponseDataFormat(req);
|
||||||
|
while (!error) {
|
||||||
|
Variant msg;
|
||||||
|
try {
|
||||||
|
msg = eventQueue->popEvent();
|
||||||
|
std::string encoded = encodeVariant(msg, req, format);
|
||||||
|
ws.sendFrame(encoded.c_str(), encoded.size(), Net::WebSocket::FRAME_FLAG_FIN | Net::WebSocket::FRAME_OP_TEXT);
|
||||||
|
} catch (DeviceEventQueue::DisconnectedException& e) {
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "WebSocket connection closed." << std::endl;
|
||||||
|
} catch (Net::WebSocketException& exc) {
|
||||||
|
std::cout << "WebSocketException caught: " << exc.what() << std::endl;
|
||||||
|
switch (exc.code()) {
|
||||||
|
case Net::WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
|
||||||
|
resp.set("Sec-WebSocket-Version", Net::WebSocket::WEBSOCKET_VERSION);
|
||||||
|
// fallthrough
|
||||||
|
case Net::WebSocket::WS_ERR_NO_HANDSHAKE:
|
||||||
|
case Net::WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
|
||||||
|
case Net::WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
|
||||||
|
resp.setStatusAndReason(Net::HTTPResponse::HTTP_BAD_REQUEST);
|
||||||
|
resp.setContentLength(0);
|
||||||
|
resp.send();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void DeviceRepoHandler::__handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket) {
|
||||||
std::vector<std::string> pathPieces, matchPieces;
|
std::vector<std::string> pathPieces, matchPieces;
|
||||||
matchUri.getPathSegments(matchPieces);
|
matchUri.getPathSegments(matchPieces);
|
||||||
Poco::URI uri(req.getURI());
|
Poco::URI uri(req.getURI());
|
||||||
@@ -399,15 +484,21 @@ void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &r
|
|||||||
}
|
}
|
||||||
// Get event queue
|
// Get event queue
|
||||||
auto eventQueue = parent->getDeviceEventQueue(clientID);
|
auto eventQueue = parent->getDeviceEventQueue(clientID);
|
||||||
eventQueue->setHandler(this, &req, &resp);
|
if (!eventQueue->setHandler(this, &req, &resp)) {
|
||||||
|
throw webserver::webresource::WebResourceHandlerException((boost::format("Client id \"%1%\" already in use.") % devid).str(),
|
||||||
|
Poco::Net::HTTPResponse::HTTPStatus::HTTP_BAD_REQUEST);
|
||||||
|
}
|
||||||
std::cout << "Got EventQueue \"" << eventQueue->clientID << "\"" << std::endl;
|
std::cout << "Got EventQueue \"" << eventQueue->clientID << "\"" << std::endl;
|
||||||
devicerepository::Device* devptr = nullptr;
|
devicerepository::Device* devptr = nullptr;
|
||||||
if (singleDeviceMode) {
|
if (singleDeviceMode) {
|
||||||
devptr = device.lock().get();
|
devptr = device.lock().get();
|
||||||
}
|
}
|
||||||
eventQueue->checkRegistration(event, devptr, path.str());
|
eventQueue->checkRegistration(event, devptr, path.str());
|
||||||
auto msg = eventQueue->popEvent();
|
if (websocket) {
|
||||||
handleResponse(req, resp, msg);
|
__handleEventWebSocket(req, resp, eventQueue.get());
|
||||||
|
} else {
|
||||||
|
__handleEventLongPolling(req, resp, eventQueue.get());
|
||||||
|
}
|
||||||
eventQueue->unsetHandler();
|
eventQueue->unsetHandler();
|
||||||
} else {
|
} else {
|
||||||
throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(),
|
throw webserver::webresource::WebResourceHandlerException((boost::format("Could not find device \"%1%\"") % devid).str(),
|
||||||
@@ -418,6 +509,7 @@ void DeviceRepoHandler::__handleEventLongPolling(Poco::Net::HTTPServerRequest &r
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DeviceEventQueue::DeviceEventQueue( const std::string& clientID,
|
DeviceEventQueue::DeviceEventQueue( const std::string& clientID,
|
||||||
DeviceRepositoryService* repoService,
|
DeviceRepositoryService* repoService,
|
||||||
DeviceRepoHandlerFactory* handlerFactory )
|
DeviceRepoHandlerFactory* handlerFactory )
|
||||||
@@ -472,38 +564,43 @@ void DeviceEventQueue::pushEvent(const libvariant::Variant& event) {
|
|||||||
libvariant::Variant DeviceEventQueue::popEvent() {
|
libvariant::Variant DeviceEventQueue::popEvent() {
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
libvariant::Variant retval;
|
libvariant::Variant retval;
|
||||||
|
breakPopLoop = false;
|
||||||
Net::HTTPServerRequestImpl* reqImpl = static_cast<Net::HTTPServerRequestImpl*>(req);
|
Net::HTTPServerRequestImpl* reqImpl = static_cast<Net::HTTPServerRequestImpl*>(req);
|
||||||
while (_eventQueue.empty()) {
|
// I don't like the content of this loop at all.
|
||||||
|
while (_eventQueue.empty() && !breakPopLoop) {
|
||||||
_cv.wait_for(lock, std::chrono::seconds(1));
|
_cv.wait_for(lock, std::chrono::seconds(1));
|
||||||
/*
|
|
||||||
auto addr = reqImpl->socket().peerAddress();
|
|
||||||
int error_code;
|
|
||||||
socklen_t error_code_size = sizeof(error_code);
|
|
||||||
auto error = getsockopt(reqImpl->socket().impl()->sockfd(), SOL_SOCKET, SO_ERROR, &error_code, &error_code_size);
|
|
||||||
int tmp = FLUSHRW;
|
|
||||||
try {
|
|
||||||
reqImpl->socket().impl()->ioctl(I_GWROPT, tmp);
|
|
||||||
} catch (std::exception& e) {
|
|
||||||
std::cout << "Debug01 Exception: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
std::cout << "debug01: " << error << " - " << addr.toString() << " - " << reqImpl->socket().impl()->available() << " - " << errno << std::endl;
|
|
||||||
*/
|
|
||||||
std::cout << "debug01: " << reqImpl->socket().impl()->available() << " - " << errno << std::endl;
|
|
||||||
|
|
||||||
|
// Monitor the socket for a very small time with Posix "select".
|
||||||
|
// If select says that there is data, available() returns 0 then apparently the client has disconnected.
|
||||||
|
// WTF, is this really the only way to notice that a client properly disconnected? Internet says so.
|
||||||
|
/*fd_set fds;
|
||||||
|
auto sockfs = reqImpl->socket().impl()->sockfd();
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sockfs, &fds);
|
||||||
|
struct timeval timeout = {0, 1};
|
||||||
|
int sel = select(sockfs + 1, &fds, 0, 0, &timeout);
|
||||||
|
if (sel > 0 && reqImpl->socket().available() == 0) {
|
||||||
|
ROS_ERROR_STREAM("Apparently we have a disconnect in device event queue \"" << clientID << "\"");
|
||||||
|
throw DisconnectedException();
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
if (!_eventQueue.empty()) {
|
if (!_eventQueue.empty() && !breakPopLoop) {
|
||||||
retval = _eventQueue.front();
|
retval = _eventQueue.front();
|
||||||
_eventQueue.pop();
|
_eventQueue.pop();
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceEventQueue::setHandler(DeviceRepoHandler* handler, Poco::Net::HTTPServerRequest* req, Poco::Net::HTTPServerResponse* resp) {
|
bool DeviceEventQueue::setHandler(DeviceRepoHandler* handler, Poco::Net::HTTPServerRequest* req, Poco::Net::HTTPServerResponse* resp) {
|
||||||
this->handler = handler;
|
if (openConnectionCount <= 0) {
|
||||||
this->req = req;
|
this->handler = handler;
|
||||||
this->resp =resp;
|
this->req = req;
|
||||||
openConnectionCount++;
|
this->resp =resp;
|
||||||
timer.cancel();
|
openConnectionCount++;
|
||||||
|
timer.cancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceEventQueue::unsetHandler() {
|
void DeviceEventQueue::unsetHandler() {
|
||||||
|
|||||||
+23
-2
@@ -54,35 +54,52 @@ enum class DeviceAPIMethod {
|
|||||||
class DeviceEventQueue
|
class DeviceEventQueue
|
||||||
: public virtual interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent> {
|
: public virtual interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent> {
|
||||||
public:
|
public:
|
||||||
|
class DisconnectedException : public std::exception {};
|
||||||
|
|
||||||
const std::string clientID;
|
const std::string clientID;
|
||||||
DeviceEventType eventType = DeviceEventType::ALL_EVENTS;
|
DeviceEventType eventType = DeviceEventType::ALL_EVENTS;
|
||||||
DeviceRepositoryService* const repoService;
|
DeviceRepositoryService* const repoService;
|
||||||
Device* device = nullptr;
|
Device* device = nullptr;
|
||||||
std::string pointer;
|
std::string pointer;
|
||||||
|
|
||||||
bool registeredToEvent = false;
|
bool registeredToEvent = false;
|
||||||
size_t openConnectionCount = 0;
|
size_t openConnectionCount = 0;
|
||||||
|
bool breakPopLoop = false;
|
||||||
|
|
||||||
DeviceRepoHandler* handler = nullptr;
|
DeviceRepoHandler* handler = nullptr;
|
||||||
DeviceRepoHandlerFactory* handlerFactory;
|
DeviceRepoHandlerFactory* handlerFactory;
|
||||||
Poco::Net::HTTPServerRequest* req = nullptr;
|
Poco::Net::HTTPServerRequest* req = nullptr;
|
||||||
Poco::Net::HTTPServerResponse* resp = nullptr;
|
Poco::Net::HTTPServerResponse* resp = nullptr;
|
||||||
|
|
||||||
std::queue<libvariant::Variant> _eventQueue;
|
std::queue<libvariant::Variant> _eventQueue;
|
||||||
std::mutex _mutex;
|
std::mutex _mutex;
|
||||||
std::condition_variable _cv;
|
std::condition_variable _cv;
|
||||||
|
|
||||||
boost::asio::deadline_timer timer;
|
boost::asio::deadline_timer timer;
|
||||||
|
|
||||||
DeviceEventQueue( const std::string& clientID,
|
DeviceEventQueue( const std::string& clientID,
|
||||||
DeviceRepositoryService* repoService,
|
DeviceRepositoryService* repoService,
|
||||||
DeviceRepoHandlerFactory* handlerFactory );
|
DeviceRepoHandlerFactory* handlerFactory );
|
||||||
|
|
||||||
void pushEvent(const libvariant::Variant& event);
|
void pushEvent(const libvariant::Variant& event);
|
||||||
libvariant::Variant popEvent();
|
libvariant::Variant popEvent();
|
||||||
void setHandler(DeviceRepoHandler* handler, Poco::Net::HTTPServerRequest* req, Poco::Net::HTTPServerResponse* resp);
|
void cancelPopEvent() {
|
||||||
|
breakPopLoop = true;
|
||||||
|
_cv.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setHandler(DeviceRepoHandler* handler, Poco::Net::HTTPServerRequest* req, Poco::Net::HTTPServerResponse* resp);
|
||||||
void unsetHandler();
|
void unsetHandler();
|
||||||
|
|
||||||
void checkRegistration(DeviceEventType eventType, Device* device, const std::string& pointer);
|
void checkRegistration(DeviceEventType eventType, Device* device, const std::string& pointer);
|
||||||
void registerEvent(DeviceEventType eventType, Device* device = nullptr);
|
void registerEvent(DeviceEventType eventType, Device* device = nullptr);
|
||||||
void unregisterEvent();
|
void unregisterEvent();
|
||||||
|
|
||||||
~DeviceEventQueue() {
|
~DeviceEventQueue() {
|
||||||
unregisterEvent();
|
unregisterEvent();
|
||||||
std::cout << "DeviceEventQueue \"" << clientID << "\" destroyed" << std::endl;
|
std::cout << "DeviceEventQueue \"" << clientID << "\" destroyed" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>::observable_type* o,
|
virtual void notificationReceived( interface::Observer<devicerepository::DeviceEventType, devicerepository::DeviceEvent>::observable_type* o,
|
||||||
const devicerepository::DeviceEventType& key,
|
const devicerepository::DeviceEventType& key,
|
||||||
const devicerepository::DeviceEvent* e) noexcept override;
|
const devicerepository::DeviceEvent* e) noexcept override;
|
||||||
@@ -157,7 +174,11 @@ public:
|
|||||||
|
|
||||||
void __handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method);
|
void __handleCommand(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceAPIMethod method);
|
||||||
|
|
||||||
void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp);
|
void __handleEvent(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, bool websocket);
|
||||||
|
|
||||||
|
void __handleEventLongPolling(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue);
|
||||||
|
|
||||||
|
void __handleEventWebSocket(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, DeviceEventQueue* eventQueue);
|
||||||
|
|
||||||
void sendDeviceCommand( devicerepository::Device* device,
|
void sendDeviceCommand( devicerepository::Device* device,
|
||||||
Poco::Net::HTTPServerRequest &req,
|
Poco::Net::HTTPServerRequest &req,
|
||||||
|
|||||||
Reference in New Issue
Block a user