This commit is contained in:
Matthias Janetschek
2015-05-29 15:35:53 +02:00
parent 7905105701
commit 8f844218be
11 changed files with 321 additions and 49 deletions
@@ -53,6 +53,10 @@ public:
this->service = service;
}
virtual bool excludeFromAuthentication() noexcept override {
return false;
}
};
@@ -3,22 +3,27 @@
<head>
<title>WebSocketServer</title>
<script type="text/javascript">
var ws;
function WebSocketTest() {
if ("WebSocket" in window) {
var ws = new WebSocket("ws://127.0.0.1:8080/ws");
ws = new WebSocket("ws://127.0.0.1:8080/hos/api/websocket");
ws.onopen = function() { ws.send("Hello, world!"); };
ws.onmessage = function(evt) {
var msg = evt.data;
alert("Message received: " + msg);
ws.close();
};
ws.onclose = function() {
alert("WebSocket closed.");
};
};
setTimeout(timeoutFunc, 3000);
} else {
alert("This browser does not support WebSockets.");
}
}
function timeoutFunc() {
ws.send("Hello, world2!");
setTimeout(timeoutFunc, 3000);
}
</script>
</head>
+1
View File
@@ -57,6 +57,7 @@ bundle(hos_cosgi_webserver
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/WebServerServiceImpl
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/DirectoryResource
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/SchemaResource
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/WebSocketTest
${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/ph7/ph7
DIRECTORIES
${PROJECT_SOURCE_DIR}/certs
@@ -3,6 +3,7 @@
#include <hos/cosgi/service/Interface.h>
#include <Poco/Net/HTTPResponse.h>
#include <memory>
#include <vector>
#include <string>
@@ -44,6 +45,7 @@ struct DirectoryResourceEntry {
: dir(dir) {}
std::string dir;
bool enablePHP = false;
bool excludeFromAuthentication = false;
};
@@ -59,6 +61,8 @@ public:
virtual Poco::Net::HTTPRequestHandler* createRequestHandler( Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0;
virtual void setWebServerService(WebServerService* service) noexcept = 0;
virtual bool excludeFromAuthentication() noexcept = 0;
};
@@ -80,7 +84,18 @@ public:
virtual void addDirectoryResource(const std::string& baseUri, const DirectoryResourceEntry& entry) = 0;
virtual Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) = 0;
virtual bool isAuthenticationRequired() noexcept = 0;
virtual bool checkAuthentication( const Poco::Net::HTTPServerRequest& request ) = 0;
virtual Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0;
virtual Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0;
virtual Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request,
bool ssl,
const std::string& msg = std::string(),
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR ) noexcept = 0;
};
@@ -66,6 +66,9 @@ public:
virtual void setWebServerService(webserver::WebServerService* service) noexcept override {
this->service = service;
}
virtual bool excludeFromAuthentication() noexcept override {
return false;
}
};
@@ -220,6 +223,19 @@ protected:
addDebugInfo(out, request, resp);
}
void handleRequireAuthenticationResponse( const Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse &resp,
const std::string& realm,
const std::string& body = std::string(),
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED ) {
resp.requireAuthentication(realm);
if (body.empty()) {
resp.send() << "ERROR: " << resp.getReason();
} else {
resp.send() << body;
}
}
void addDebugInfo(std::ostream& out, const Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse &resp) {
out << std::endl << std::endl
<<"========== DEBUG ==========" << std::endl
@@ -7,7 +7,9 @@
#include <Poco/Net/KeyConsoleHandler.h>
#include <Poco/Net/ConsoleCertificateHandler.h>
#include <Poco/Net/SSLException.h>
#include <Poco/Net/HTTPBasicCredentials.h>
#include "webresource/WebSocketTest.h"
namespace hos {
namespace cosgi {
@@ -53,16 +55,33 @@ void WebServerServiceImpl::setCoreService(service::core::CoreService* coreServic
Poco::Net::HTTPRequestHandler* WebServerServiceImpl::RequestHandlerFactory::createRequestHandler( const Poco::Net::HTTPServerRequest& request) {
WebResourceHandlerFactory* factory = nullptr;
std::string matchUri;
// 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()] == '/' ) ) {
return i->second->createRequestHandler(Poco::URI(i->first), request, ssl);
factory = i->second;
matchUri = i->first;
break;
}
}
if (factory) {
if ( parent->requireAuthentication
&& !factory->excludeFromAuthentication()
&& !parent->checkAuthentication(request) ) {
return new RequireAuthenticationRequestHandler();
} else {
return factory->createRequestHandler(Poco::URI(matchUri), request, ssl);
}
} else {
if ( parent->requireAuthentication && !parent->checkAuthentication(request) ) {
return new RequireAuthenticationRequestHandler();
} else {
return new DefaultRequestHandler();
}
}
// return default handler
return new DefaultRequestHandler();
}
@@ -132,6 +151,9 @@ void WebServerServiceImpl::init() noexcept {
return new webresource::SchemaResourceHandler(matchUri, nodehandle, &coreService->getSchemaLoader(), this);
})
);
registerWebResource("/websocket",
new webresource::WebSocketTestFactory()
);
initialized = true;
ROS_INFO_STREAM("WebServerService: Initialized and Ready.");
@@ -176,6 +198,28 @@ Poco::URI WebServerServiceImpl::getAbsoluteURI(const std::string& relpath) {
}
bool WebServerServiceImpl::checkAuthentication( const Poco::Net::HTTPServerRequest& request ) {
if (request.hasCredentials()) {
std::string scheme;
std::string authInfo;
request.getCredentials(scheme, authInfo);
if (scheme == Poco::Net::HTTPBasicCredentials::SCHEME) {
Poco::Net::HTTPBasicCredentials cred(authInfo);
auto user = authorizedUsers.find(cred.getUsername());
if (user != authorizedUsers.end()) {
if (user->second == cred.getPassword()) {
return true;
}
}
} else {
std::stringstream ss;
ss << "Unsupported authentication scheme \"" << scheme << "\"." << std::endl;
throw std::runtime_error(ss.str());
}
}
return false;
}
} // namespace webserver
} // namespace service
@@ -58,6 +58,24 @@ private:
handleErrorResponse(req, resp, 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");
}
};
class ErrorRequestHandler
: public webresource::WebResourceHandlerImpl {
public:
std::string msg;
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);
}
};
public:
RequestHandlerFactory(WebServerServiceImpl* parent, bool ssl = false)
: parent(parent), ssl(ssl) {}
@@ -65,6 +83,12 @@ private:
Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request) {
return new DefaultRequestHandler();
}
Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request) {
return new RequireAuthenticationRequestHandler();
}
Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request, const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus status) {
return new ErrorRequestHandler(msg, status);
}
} handlerFactory, handlerFactorySSL;
std::string installDir;
@@ -79,6 +103,11 @@ private:
std::unique_ptr<Poco::Net::HTTPServer> webServer;
std::unique_ptr<Poco::Net::HTTPServer> webServerSSL;
std::map<std::string, WebResourceHandlerFactory*> webResourceFactories;
bool requireAuthentication = false;
std::map<std::string, std::string> authorizedUsers = {
{"user", "secret"},
{"root", "pass"},
};
void init() noexcept;
@@ -109,6 +138,29 @@ public:
}
virtual Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override {
if (ssl) {
return handlerFactorySSL.createRequireAuthenticationRequestHandler(request);
} else {
return handlerFactory.createRequireAuthenticationRequestHandler(request);
}
}
virtual Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request,
bool ssl,
const std::string& msg = std::string(),
Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR ) noexcept {
if (ssl) {
return handlerFactorySSL.createErrorHandler(request, msg, status);
} else {
return handlerFactory.createErrorHandler(request, msg, status);
}
}
virtual bool isAuthenticationRequired() noexcept {
return requireAuthentication;
}
/* Internal */
@@ -118,6 +170,8 @@ public:
Poco::URI getAbsoluteURI(const std::string& relpath);
bool checkAuthentication( const Poco::Net::HTTPServerRequest& request ) override;
};
@@ -56,9 +56,23 @@ DirectoryResourceHandlerFactory::~DirectoryResourceHandlerFactory() {
Poco::Net::HTTPRequestHandler* DirectoryResourceHandlerFactory::createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept {
auto e = dirMap.find(matchUri.getPath());
if (e != dirMap.end()) {
return new DirectoryResourceHandler(matchUri, this, service, e->second, ssl);
try {
if ( service->isAuthenticationRequired()
&& !e->second.excludeFromAuthentication
&& !service->checkAuthentication(request) ) {
return service->createRequireAuthenticationRequestHandler(request, ssl);
} else {
return new DirectoryResourceHandler(matchUri, this, service, e->second, ssl);
}
} catch (std::exception& e) {
return service->createErrorHandler(request, ssl, e.what(), Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
}
} else {
return service->createDefaultRequestHandler(request, ssl);
if ( service->isAuthenticationRequired() && !service->checkAuthentication(request) ) {
return service->createRequireAuthenticationRequestHandler(request, ssl);
} else {
return service->createDefaultRequestHandler(request, ssl);
}
}
}
@@ -120,46 +134,6 @@ void DirectoryResourceHandler::serveDirectoy(Poco::Net::HTTPServerRequest &req,
void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file) {
std::ostream& ostr = std::cout;
ostr << "<html>";
ostr << "<head>";
ostr << "<title>WebSocketServer</title>";
ostr << "<script type=\"text/javascript\">";
ostr << "function WebSocketTest()";
ostr << "{";
ostr << " if (\"WebSocket\" in window)";
ostr << " {";
ostr << " var ws = new WebSocket(\"ws://" << req.serverAddress().toString() << "/ws\");";
ostr << " ws.onopen = function()";
ostr << " {";
ostr << " ws.send(\"Hello, world!\");";
ostr << " };";
ostr << " ws.onmessage = function(evt)";
ostr << " { ";
ostr << " var msg = evt.data;";
ostr << " alert(\"Message received: \" + msg);";
ostr << " ws.close();";
ostr << " };";
ostr << " ws.onclose = function()";
ostr << " { ";
ostr << " alert(\"WebSocket closed.\");";
ostr << " };";
ostr << " }";
ostr << " else";
ostr << " {";
ostr << " alert(\"This browser does not support WebSockets.\");";
ostr << " }";
ostr << "}";
ostr << "</script>";
ostr << "</head>";
ostr << "<body>";
ostr << " <h1>WebSocket Server</h1>";
ostr << " <p><a href=\"javascript:WebSocketTest()\">Run WebSocket Script</a></p>";
ostr << "</body>";
ostr << "</html>";
auto type = parent->contentTypes.find(file.extension().string());
if (type != parent->contentTypes.end()) {
resp.setContentType(type->second);
@@ -69,6 +69,10 @@ public:
this->service = service;
}
virtual bool excludeFromAuthentication() noexcept override {
return false;
}
ph7* getPH7Engine() {
return pEngine;
}
@@ -0,0 +1,85 @@
#include "WebSocketTest.h"
#include "../WebServerServiceImpl.h"
#include <boost/format.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/range/iterator_range.hpp>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/WebSocket.h>
#include "Poco/Net/NetException.h"
#include <Poco/URI.h>
#include "Poco/Format.h"
// Service Namespace
namespace hos {
namespace cosgi {
namespace service {
namespace webserver {
namespace webresource {
using namespace Poco;
HOS_SERVICE_IMPL_INFO( WebSocketTestFactory, // Implementation
service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name
1, 0 ) // Version
Poco::Net::HTTPRequestHandler* WebSocketTestFactory::createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept {
if(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)
return new WebSocketTestHandler(matchUri, this, service, ssl);
else
return service->createErrorHandler(request, ssl, "Non-Websocket Requests are unsupported.");
}
void WebSocketTestHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) {
try {
Net::WebSocket ws(req, resp);
std::cout << "WebSocket connection established." << std::endl;
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;
ws.sendFrame(buffer, n, flags);
//} while (n > 0 || (flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE);
} while (n > 0 || ((flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE && (flags & Net::WebSocket::FRAME_OP_BITMASK) != 0));
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;
}
}
}
} // namespace resources
} // namespace webserver
} // namespace service
} // namespace cosgi
} // namespace hos
@@ -0,0 +1,70 @@
#pragma once
#include <hos/cosgi/service/webserver/WebServerService.h>
#include <hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h>
#include <Poco/Net/HTTPRequestHandler.h>
#include <boost/filesystem.hpp>
#include <ros/ros.h>
#include <map>
#include <string>
// Service Namespace
namespace hos {
namespace cosgi {
namespace service {
namespace webserver {
namespace webresource {
using namespace boost;
class WebSocketTestFactory
: public virtual webserver::WebResourceHandlerFactory,
public implementation::Service<WebSocketTestFactory> {
private:
WebServerService* service = nullptr;
public:
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;
}
};
class WebSocketTestHandler
: public WebResourceHandlerImpl {
private:
Poco::URI matchUri;
WebSocketTestFactory* parent;
WebServerService* service;
bool ssl;
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;
};
} // namespace resources
} // namespace webserver
} // namespace service
} // namespace cosgi
} // namespace hos