add HTTPS support for webserver
This commit is contained in:
parent
36fbf27762
commit
660f91b165
@ -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
6
debian/changelog
vendored
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
#ifndef TRANSPORTINTERFACE_H
|
||||
#define TRANSPORTINTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user