add HTTPS support for webserver

This commit is contained in:
Simon Stürz 2015-08-02 22:34:04 +02:00 committed by Michael Zanetti
parent 36fbf27762
commit 660f91b165
10 changed files with 178 additions and 63 deletions

View File

@ -1,13 +1,14 @@
[JSONRPC]
port=1234
interfaces="lo","all"
interfaces="lo", "all"
ip="IPv4", "IPv6"
[Webserver]
port=3000
publicFolder=/usr/share/guh-webinterface/
https=false
certificate=/etc/ssl/certs/guhd-certificate.crt
certificate-key=/etc/ssl/private/guhd-certificate.key
publicFolder=/usr/share/guh-webinterface/public
[GPIO]
rf433rx=27

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
guh (0.5.0) vivid; urgency=medium
* Add webserver and REST API
-- Simon Stürz <simon.stuerz@guh.guru> Sat, 01 Aug 2015 14:52:12 +0200
guh (0.4.0) vivid; urgency=medium
* add EditDevice and notifications

View File

@ -204,7 +204,7 @@ HttpRequest::RequestMethod HttpRequest::getRequestMethodType(const QString &meth
QDebug operator<<(QDebug debug, const HttpRequest &httpRequest)
{
debug << "\n===================================" << "\n";
debug << "===================================" << "\n";
debug << " HTTP version: " << httpRequest.httpVersion() << "\n";
debug << " method: " << httpRequest.methodString() << "\n";
debug << " URL path: " << httpRequest.url().path() << "\n";

View File

@ -209,7 +209,7 @@ void TcpServer::onClientDisconnected()
m_clientList.take(clientId)->deleteLater();
}
void TcpServer::onError(const QAbstractSocket::SocketError &error)
void TcpServer::onError(QAbstractSocket::SocketError error)
{
QTcpServer *server = qobject_cast<QTcpServer *>(sender());
QUuid uuid = m_serverList.key(server);

View File

@ -63,7 +63,7 @@ private slots:
void onClientConnected();
void onClientDisconnected();
void readPackage();
void onError(const QAbstractSocket::SocketError &error);
void onError(QAbstractSocket::SocketError error);
void onTimeout();
public slots:

View File

@ -21,7 +21,7 @@
#ifndef TRANSPORTINTERFACE_H
#define TRANSPORTINTERFACE_H
#include <QObject>
#include <QVariant>
#include <QString>
#include <QList>
#include <QUuid>

View File

@ -27,6 +27,7 @@
#include <QJsonDocument>
#include <QTcpServer>
#include <QTcpSocket>
#include <QSslSocket>
#include <QUrlQuery>
#include <QUuid>
#include <QUrl>
@ -35,36 +36,49 @@
namespace guhserver {
WebServer::WebServer(QObject *parent) :
TransportInterface(parent),
m_enabled(false)
QTcpServer(parent),
m_enabled(false),
m_useSsl(false)
{
// load webserver settings
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
qCDebug(dcTcpServer) << "Loading webserver settings from:" << settings.fileName();
qCDebug(dcWebServer) << "Loading webserver settings from:" << settings.fileName();
settings.beginGroup("Webserver");
m_port = settings.value("port", 3000).toInt();
m_useSsl = settings.value("https", false).toBool();
m_webinterfaceDir = QDir(settings.value("publicFolder", "/usr/share/guh-webinterface/public/").toString());
QString certificateFileName = settings.value("certificate", QVariant("/etc/ssl/certs/guhd-certificate.crt")).toString();
QString keyFileName = settings.value("certificate-key", QVariant("/etc/ssl/private/guhd-certificate.key")).toString();
settings.endGroup();
qCDebug(dcTcpServer) << "Publish webinterface from" << m_webinterfaceDir.path();
// check public directory
qCDebug(dcWebServer) << "Publish webinterface folder" << m_webinterfaceDir.path();
if (!m_webinterfaceDir.exists())
qCWarning(dcWebServer) << "Web interface path" << m_webinterfaceDir.path() << "does not exist.";
qCWarning(dcWebServer) << "Web interface public folder" << m_webinterfaceDir.path() << "does not exist.";
// create webserver
m_server = new QTcpServer(this);
connect(m_server, &QTcpServer::newConnection, this, &WebServer::onNewConnection);
// check SSL
if (m_useSsl && !QSslSocket::supportsSsl()) {
qCWarning(dcWebServer) << "SSL is not supported/installed on this platform.";
m_useSsl = false;
}
if (m_useSsl && !loadCertificate(keyFileName, certificateFileName)) {
qCWarning(dcWebServer) << "SSL encryption disabled";
m_useSsl = false;
return;
}
qCDebug(dcWebServer) << "Using SSL lib version:" << QSslSocket::sslLibraryVersionString();
}
WebServer::~WebServer()
{
m_server->close();
this->close();
}
void WebServer::sendData(const QUuid &clientId, const QVariantMap &data)
{
QTcpSocket *socket = m_clientList.value(clientId);
QSslSocket *socket = m_clientList.value(clientId);
HttpReply reply(HttpReply::Ok);
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
reply.setPayload(QJsonDocument::fromVariant(data).toJson());
@ -75,7 +89,7 @@ void WebServer::sendData(const QUuid &clientId, const QVariantMap &data)
void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
foreach (const QUuid &client, clients) {
QTcpSocket *socket = m_clientList.value(client);
QSslSocket *socket = m_clientList.value(client);
HttpReply reply(HttpReply::Ok);
reply.setHeader(HttpReply::ContentTypeHeader, "application/json; charset=\"utf-8\";");
reply.setPayload(QJsonDocument::fromVariant(data).toJson());
@ -86,7 +100,7 @@ void WebServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
void WebServer::sendHttpReply(HttpReply *reply)
{
QTcpSocket *socket = 0;
QSslSocket *socket = 0;
socket = m_clientList.value(reply->clientId());
if (!socket) {
@ -96,7 +110,7 @@ void WebServer::sendHttpReply(HttpReply *reply)
writeData(socket, reply->data());
}
bool WebServer::verifyFile(QTcpSocket *socket, const QString &fileName)
bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName)
{
QFileInfo file(fileName);
@ -146,40 +160,83 @@ QString WebServer::fileName(const QString &query)
return m_webinterfaceDir.path() + fileName;
}
void WebServer::writeData(QTcpSocket *socket, const QByteArray &data)
bool WebServer::loadCertificate(const QString &keyFileName, const QString &certificateFileName)
{
QTextStream os(socket);
os.setAutoDetectUnicode(true);
os << data;
QByteArray certificateData;
QByteArray certificateKeyData;
QFile certificateKeyFile(keyFileName);
if (!certificateKeyFile.open(QIODevice::ReadOnly)) {
qCWarning(dcWebServer) << "Could not open" << certificateKeyFile.fileName() << ":" << certificateKeyFile.errorString();
return false;
}
certificateKeyData = certificateKeyFile.readAll();
certificateKeyFile.close();
qCDebug(dcWebServer) << "Loaded successfully private certificate key.";
QFile certificateFile(certificateFileName);
if (!certificateFile.open(QIODevice::ReadOnly)) {
qCWarning(dcWebServer) << "Could not open" << certificateFile.fileName() << ":" << certificateFile.errorString();
return false;
}
certificateData = certificateFile.readAll();
certificateFile.close();
qCDebug(dcWebServer) << "Loaded successfully certificate file.";
m_certificate = QSslCertificate(certificateData);
m_certificateKey = QSslKey(certificateKeyData, QSsl::Rsa);
return true;
}
void WebServer::writeData(QSslSocket *socket, const QByteArray &data)
{
socket->write(data);
socket->close();
}
void WebServer::onNewConnection()
void WebServer::incomingConnection(qintptr socketDescriptor)
{
if (!m_enabled)
return;
QTcpSocket* socket = m_server->nextPendingConnection();
QSslSocket *socket = new QSslSocket();
if (!socket->setSocketDescriptor(socketDescriptor)) {
qCWarning(dcConnection) << "Could not set socket descriptor. Rejecting connection.";
delete socket;
return;
}
// append the new client to the client list
QUuid clientId = QUuid::createUuid();
m_clientList.insert(clientId, socket);
connect(socket, &QTcpSocket::readyRead, this, &WebServer::readClient);
connect(socket, &QTcpSocket::disconnected, this, &WebServer::onDisconnected);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
qCDebug(dcConnection) << QString("Webserver client %1:%2 connected").arg(socket->peerAddress().toString()).arg(socket->peerPort());
qCDebug(dcConnection) << "Webserver client connected" << socket->peerName() << socket->peerAddress().toString() << socket->peerPort();
if (m_useSsl) {
// configure client connection
socket->setProtocol(QSsl::TlsV1_2);
socket->setPrivateKey(m_certificateKey);
socket->setLocalCertificate(m_certificate);
connect(socket, SIGNAL(encrypted()), this, SLOT(onEncrypted()));
socket->startServerEncryption();
// wait for encrypted connection before continue
return;
}
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
emit clientConnected(clientId);
}
void WebServer::readClient()
{
if (!m_enabled)
return;
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
QUuid clientId = m_clientList.key(socket);
// check client
@ -243,7 +300,7 @@ void WebServer::readClient()
}
// request for a file...
if (request.method() == HttpRequest::Get) {
if (request.method() == HttpRequest::Get && m_webinterfaceDir.exists()) {
QString path = fileName(request.url().path());
if (!verifyFile(socket, path))
return;
@ -264,13 +321,13 @@ void WebServer::readClient()
// reject everything else...
qCWarning(dcWebServer) << "Unknown message received. Respond client with 501: Not Implemented.";
HttpReply reply(HttpReply::NotImplemented);
reply.setPayload("501 Not implemented.");
reply.setPayload("404 Not found.");
writeData(socket, reply.data());
}
void WebServer::onDisconnected()
{
QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender());
QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCDebug(dcConnection) << "Webserver client disonnected.";
// clean up
@ -282,28 +339,42 @@ void WebServer::onDisconnected()
emit clientDisconnected(clientId);
}
void WebServer::onEncrypted()
{
QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCDebug(dcConnection) << QString("Encrypted connection %1:%2 successfully established.").arg(socket->peerAddress().toString()).arg(socket->peerPort());
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
emit clientConnected(m_clientList.key(socket));
}
void WebServer::onError(QAbstractSocket::SocketError error)
{
QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender());
QSslSocket* socket = static_cast<QSslSocket *>(sender());
qCWarning(dcConnection) << "Client socket error" << socket->peerAddress() << error << socket->errorString();
}
bool WebServer::startServer()
{
if (!m_server->listen(QHostAddress::Any, m_port)) {
qCWarning(dcConnection) << "Webserver could not listen on" << m_server->serverAddress().toString() << m_port;
if (!listen(QHostAddress::Any, m_port)) {
qCWarning(dcConnection) << "Webserver could not listen on" << serverAddress().toString() << m_port;
m_enabled = false;
return false;
}
qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(m_server->serverAddress().toString()).arg(m_port);
if (m_useSsl) {
qCDebug(dcConnection) << "Started webserver on" << QString("https://%1:%2").arg(serverAddress().toString()).arg(m_port);
} else {
qCDebug(dcConnection) << "Started webserver on" << QString("http://%1:%2").arg(serverAddress().toString()).arg(m_port);
}
m_enabled = true;
return true;
}
bool WebServer::stopServer()
{
m_server->close();
close();
m_enabled = false;
qCDebug(dcConnection) << "Webserver closed.";
return true;

View File

@ -22,14 +22,14 @@
#define WEBSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHash>
#include <QDir>
#include <QUuid>
#include <QTcpSocket>
#include <QSslSocket>
#include <QSslCertificate>
#include <QSslKey>
#include "transportinterface.h"
class QTcpServer;
class HttpRequest;
class HttpReply;
@ -38,47 +38,53 @@ class HttpReply;
namespace guhserver {
class WebServer : public TransportInterface
class WebServer : public QTcpServer
{
Q_OBJECT
public:
explicit WebServer(QObject *parent = 0);
~WebServer();
void sendData(const QUuid &clientId, const QVariantMap &data) override;
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
void sendData(const QUuid &clientId, const QVariantMap &data);
void sendData(const QList<QUuid> &clients, const QVariantMap &data);
void sendHttpReply(HttpReply *reply);
private:
QTcpServer *m_server;
QHash<QUuid, QTcpSocket *> m_clientList;
QHash<QTcpSocket *, HttpRequest> m_incompleteRequests;
QHash<QUuid, QSslSocket *> m_clientList;
QHash<QSslSocket *, HttpRequest> m_incompleteRequests;
bool m_enabled;
bool m_useSsl;
qint16 m_port;
QDir m_webinterfaceDir;
QSslCertificate m_certificate;
QSslKey m_certificateKey;
bool verifyFile(QTcpSocket *socket, const QString &fileName);
bool verifyFile(QSslSocket *socket, const QString &fileName);
QString fileName(const QString &query);
void writeData(QTcpSocket *socket, const QByteArray &data);
bool loadCertificate(const QString &keyFileName, const QString &certificateFileName);
void writeData(QSslSocket *socket, const QByteArray &data);
protected:
void incomingConnection(qintptr socketDescriptor) override;
signals:
void httpRequestReady(const QUuid &clientId, const HttpRequest &httpRequest);
void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId);
void dataAvailable(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message);
private slots:
void onNewConnection();
void readClient();
void onDisconnected();
void onEncrypted();
void onError(QAbstractSocket::SocketError error);
public slots:
bool startServer() override;
bool stopServer() override;
bool startServer();
bool stopServer();
};

View File

@ -25,7 +25,32 @@ namespace guhserver {
WebSocketServer::WebSocketServer(QObject *parent) :
TransportInterface(parent)
{
}
WebSocketServer::~WebSocketServer()
{
}
void WebSocketServer::sendData(const QUuid &clientId, const QVariantMap &data)
{
Q_UNUSED(clientId)
Q_UNUSED(data)
}
void WebSocketServer::sendData(const QList<QUuid> &clients, const QVariantMap &data)
{
Q_UNUSED(clients)
Q_UNUSED(data)
}
bool WebSocketServer::startServer()
{
return false;
}
bool WebSocketServer::stopServer()
{
return false;
}
}

View File

@ -22,6 +22,9 @@
#define WEBSOCKETSERVER_H
#include <QObject>
#include <QUuid>
#include <QVariant>
#include <QList>
#include "transportinterface.h"
@ -35,11 +38,14 @@ class WebSocketServer : public TransportInterface
Q_OBJECT
public:
explicit WebSocketServer(QObject *parent = 0);
~WebSocketServer();
signals:
void sendData(const QUuid &clientId, const QVariantMap &data) override;
void sendData(const QList<QUuid> &clients, const QVariantMap &data) override;
public slots:
bool startServer() override;
bool stopServer() override;
};
}