add client max connections and connection timeout
fix OPTIONS bug add persistent connection for webserver
This commit is contained in:
parent
ba5aedaaab
commit
8cc576d12e
@ -137,6 +137,7 @@ HttpReply::HttpReply(QObject *parent) :
|
||||
setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
|
||||
setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8() + " GMT");
|
||||
setRawHeader("Access-Control-Allow-Origin","*");
|
||||
setRawHeader("Keep-Alive", "timeout=6, max=50");
|
||||
setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
|
||||
setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive");
|
||||
packReply();
|
||||
@ -159,6 +160,7 @@ HttpReply::HttpReply(const HttpReply::HttpStatusCode &statusCode, const HttpRepl
|
||||
setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
|
||||
setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8() + " GMT");
|
||||
setRawHeader("Access-Control-Allow-Origin","*");
|
||||
setRawHeader("Keep-Alive", "timeout=10, max=50");
|
||||
setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
|
||||
setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive");
|
||||
packReply();
|
||||
@ -195,6 +197,7 @@ HttpReply::Type HttpReply::type() const
|
||||
void HttpReply::setClientId(const QUuid &clientId)
|
||||
{
|
||||
m_clientId = clientId;
|
||||
packReply();
|
||||
}
|
||||
|
||||
/*! Returns the clientId of this \l{HttpReply}.*/
|
||||
@ -391,4 +394,14 @@ void HttpReply::timeout()
|
||||
emit finished();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const HttpReply &httpReply)
|
||||
{
|
||||
debug << "-----------------------------------" << "\n";
|
||||
debug << httpReply.rawHeader() << "\n";
|
||||
debug << "-----------------------------------" << "\n";
|
||||
debug << httpReply.payload() << "\n";
|
||||
debug << "-----------------------------------" << "\n";
|
||||
return debug;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -137,6 +137,8 @@ signals:
|
||||
|
||||
};
|
||||
|
||||
QDebug operator<< (QDebug debug, const HttpReply &httpReply);
|
||||
|
||||
}
|
||||
|
||||
#endif // HTTPREPLY_H
|
||||
|
||||
@ -75,6 +75,9 @@ HttpReply *DevicesResource::proccessRequest(const HttpRequest &request, const QS
|
||||
case HttpRequest::Delete:
|
||||
reply = proccessDeleteRequest(request, urlTokens);
|
||||
break;
|
||||
case HttpRequest::Options:
|
||||
reply = proccessOptionsRequest(request, urlTokens);
|
||||
break;
|
||||
default:
|
||||
reply = createErrorReply(HttpReply::BadRequest);
|
||||
break;
|
||||
@ -192,6 +195,14 @@ HttpReply *DevicesResource::proccessPostRequest(const HttpRequest &request, cons
|
||||
return createErrorReply(HttpReply::NotImplemented);
|
||||
}
|
||||
|
||||
HttpReply *DevicesResource::proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
Q_UNUSED(urlTokens)
|
||||
qCDebug(dcRest) << "process options request\n" << request;
|
||||
return RestResource::createCorsSuccessReply();
|
||||
}
|
||||
|
||||
HttpReply *DevicesResource::getConfiguredDevices() const
|
||||
{
|
||||
qCDebug(dcRest) << "Get all configured devices";
|
||||
|
||||
@ -55,6 +55,7 @@ private:
|
||||
HttpReply *proccessDeleteRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
HttpReply *proccessPutRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
HttpReply *proccessPostRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
HttpReply *proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
|
||||
// Get methods
|
||||
HttpReply *getConfiguredDevices() const;
|
||||
|
||||
@ -60,6 +60,9 @@ HttpReply *PluginsResource::proccessRequest(const HttpRequest &request, const QS
|
||||
case HttpRequest::Put:
|
||||
reply = proccessPutRequest(request, urlTokens);
|
||||
break;
|
||||
case HttpRequest::Options:
|
||||
reply = proccessOptionsRequest(request, urlTokens);
|
||||
break;
|
||||
default:
|
||||
reply = createErrorReply(HttpReply::BadRequest);
|
||||
break;
|
||||
@ -97,6 +100,13 @@ HttpReply *PluginsResource::proccessPutRequest(const HttpRequest &request, const
|
||||
return createErrorReply(HttpReply::NotImplemented);
|
||||
}
|
||||
|
||||
HttpReply *PluginsResource::proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
Q_UNUSED(urlTokens)
|
||||
return RestResource::createCorsSuccessReply();
|
||||
}
|
||||
|
||||
HttpReply *PluginsResource::getPlugins() const
|
||||
{
|
||||
qCDebug(dcRest) << "Get plugins";
|
||||
|
||||
@ -48,6 +48,7 @@ private:
|
||||
// Process method
|
||||
HttpReply *proccessGetRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
HttpReply *proccessPutRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
HttpReply *proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens) override;
|
||||
|
||||
// Get methods
|
||||
HttpReply *getPlugins() const;
|
||||
|
||||
@ -86,6 +86,17 @@ HttpReply *RestResource::createSuccessReply()
|
||||
return reply;
|
||||
}
|
||||
|
||||
HttpReply *RestResource::createCorsSuccessReply()
|
||||
{
|
||||
HttpReply *reply = RestResource::createSuccessReply();
|
||||
reply->setHeader(HttpReply::ContentTypeHeader, "text/plain");
|
||||
reply->setRawHeader("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, OPTIONS");
|
||||
reply->setRawHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept");
|
||||
reply->setRawHeader("Access-Control-Max-Age", "1728000");
|
||||
reply->setCloseConnection(true);
|
||||
return reply;
|
||||
}
|
||||
|
||||
/*! Returns the pointer to a new created error \l{HttpReply} initialized with the given \a statusCode and \l{HttpReply::TypeSync}. */
|
||||
HttpReply *RestResource::createErrorReply(const HttpReply::HttpStatusCode &statusCode)
|
||||
{
|
||||
@ -130,6 +141,13 @@ HttpReply *RestResource::proccessPostRequest(const HttpRequest &request, const Q
|
||||
return createErrorReply(HttpReply::NotImplemented);
|
||||
}
|
||||
|
||||
HttpReply *RestResource::proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
Q_UNUSED(urlTokens)
|
||||
return createErrorReply(HttpReply::NotImplemented);
|
||||
}
|
||||
|
||||
HttpReply *RestResource::proccessPutRequest(const HttpRequest &request, const QStringList &urlTokens)
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
|
||||
@ -43,6 +43,7 @@ public:
|
||||
virtual HttpReply *proccessRequest(const HttpRequest &request, const QStringList &urlTokens) = 0;
|
||||
|
||||
static HttpReply *createSuccessReply();
|
||||
static HttpReply *createCorsSuccessReply();
|
||||
static HttpReply *createErrorReply(const HttpReply::HttpStatusCode &statusCode);
|
||||
static HttpReply *createAsyncReply();
|
||||
static QPair<bool, QVariant> verifyPayload(const QByteArray &payload);
|
||||
@ -52,6 +53,7 @@ private:
|
||||
virtual HttpReply *proccessDeleteRequest(const HttpRequest &request, const QStringList &urlTokens);
|
||||
virtual HttpReply *proccessPutRequest(const HttpRequest &request, const QStringList &urlTokens);
|
||||
virtual HttpReply *proccessPostRequest(const HttpRequest &request, const QStringList &urlTokens);
|
||||
virtual HttpReply *proccessOptionsRequest(const HttpRequest &request, const QStringList &urlTokens);
|
||||
|
||||
QHash<QPair<HttpRequest::RequestMethod, QString>, QString> m_descriptions;
|
||||
QHash<QString, QVariantMap> m_params;
|
||||
|
||||
@ -111,16 +111,12 @@ void RestServer::processHttpRequest(const QUuid &clientId, const HttpRequest &re
|
||||
return;
|
||||
}
|
||||
|
||||
// check CORS call
|
||||
if (request.method() == HttpRequest::Options) {
|
||||
HttpReply *reply = RestResource::createSuccessReply();
|
||||
reply->setHeader(HttpReply::ContentTypeHeader, "text/plain;");
|
||||
reply->setRawHeader("Access-Control-Allow-Methods", "PUT, POST, GET, DELETE, OPTIONS");
|
||||
reply->setRawHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
// check CORS call for main resource
|
||||
if (request.method() == HttpRequest::Options && urlTokens.count() == 3) {
|
||||
qCDebug(dcRest) << "process options request\n" << request;
|
||||
HttpReply *reply = RestResource::createCorsSuccessReply();
|
||||
reply->setClientId(clientId);
|
||||
reply->setCloseConnection(true);
|
||||
m_webserver->sendHttpReply(reply);
|
||||
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -137,8 +137,11 @@ void WebServer::sendHttpReply(HttpReply *reply)
|
||||
return;
|
||||
}
|
||||
|
||||
//qCDebug(dcWebServer()) << "sending header to" << socket->peerAddress().toString() << socket->peerPort() << "\n" << reply->rawHeader();
|
||||
writeData(socket, reply->data());
|
||||
socket->close();
|
||||
if (reply->closeConnection())
|
||||
socket->close();
|
||||
|
||||
}
|
||||
|
||||
bool WebServer::verifyFile(QSslSocket *socket, const QString &fileName)
|
||||
@ -191,7 +194,7 @@ QString WebServer::fileName(const QString &query)
|
||||
|
||||
void WebServer::writeData(QSslSocket *socket, const QByteArray &data)
|
||||
{
|
||||
socket->write(data);
|
||||
socket->write(data + "\r\n");
|
||||
}
|
||||
|
||||
void WebServer::incomingConnection(qintptr socketDescriptor)
|
||||
@ -207,6 +210,28 @@ void WebServer::incomingConnection(qintptr socketDescriptor)
|
||||
return;
|
||||
}
|
||||
|
||||
// check webserver client
|
||||
bool existing = false;
|
||||
foreach (WebServerClient *client, m_webServerClients) {
|
||||
if (client->address() == socket->peerAddress()) {
|
||||
if (client->connections().count() >= 50) {
|
||||
qCWarning(dcConnection) << QString("Maximum connections for this client reached: rejecting connection from client %1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
|
||||
socket->close();
|
||||
delete socket;
|
||||
return;
|
||||
}
|
||||
client->addConnection(socket);
|
||||
existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!existing) {
|
||||
WebServerClient *webServerClient = new WebServerClient(socket->peerAddress());
|
||||
webServerClient->addConnection(socket);
|
||||
m_webServerClients.append(webServerClient);
|
||||
}
|
||||
|
||||
// append the new client to the client list
|
||||
QUuid clientId = QUuid::createUuid();
|
||||
m_clientList.insert(clientId, socket);
|
||||
@ -283,6 +308,14 @@ void WebServer::readClient()
|
||||
qCDebug(dcWebServer) << QString("Got valid request from %1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
|
||||
qCDebug(dcWebServer) << request.methodString() << request.url().path();
|
||||
|
||||
// reset timout
|
||||
foreach (WebServerClient *webserverClient, m_webServerClients) {
|
||||
if (webserverClient->address() == socket->peerAddress()) {
|
||||
webserverClient->resetTimout(socket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// verify method
|
||||
if (request.method() == HttpRequest::Unhandled) {
|
||||
HttpReply reply(HttpReply::MethodNotAllowed);
|
||||
@ -381,4 +414,65 @@ bool WebServer::stopServer()
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
WebServerClient::WebServerClient(const QHostAddress &address, QObject *parent):
|
||||
QObject(parent),
|
||||
m_address(address)
|
||||
{
|
||||
}
|
||||
|
||||
QHostAddress WebServerClient::address() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
QList<QSslSocket *> WebServerClient::connections()
|
||||
{
|
||||
return m_connections;
|
||||
}
|
||||
|
||||
void WebServerClient::addConnection(QSslSocket *socket)
|
||||
{
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
timer->setInterval(6000);
|
||||
connect(timer, &QTimer::timeout, this, &WebServerClient::onTimout);
|
||||
|
||||
m_runningConnections.insert(timer, socket);
|
||||
m_connections.append(socket);
|
||||
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
|
||||
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void WebServerClient::resetTimout(QSslSocket *socket)
|
||||
{
|
||||
QTimer *timer = 0;
|
||||
timer = m_runningConnections.key(socket);
|
||||
if (timer)
|
||||
timer->start();
|
||||
}
|
||||
|
||||
void WebServerClient::onTimout()
|
||||
{
|
||||
QTimer *timer = static_cast<QTimer *>(sender());
|
||||
QSslSocket *socket = m_runningConnections.take(timer);
|
||||
qCDebug(dcWebServer) << QString("Client connection timout %1:%2 -> closing connection").arg(socket->peerAddress().toString()).arg(socket->peerPort());
|
||||
socket->close();
|
||||
|
||||
timer->deleteLater();
|
||||
}
|
||||
|
||||
void WebServerClient::onDisconnected()
|
||||
{
|
||||
QSslSocket *socket = static_cast<QSslSocket *>(sender());
|
||||
if (!m_runningConnections.values().contains(socket))
|
||||
return;
|
||||
|
||||
QTimer *timer = m_runningConnections.key(socket);
|
||||
m_runningConnections.remove(timer);
|
||||
timer->deleteLater();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
#include <QTcpSocket>
|
||||
#include <QHash>
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
#include <QSslSocket>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslConfiguration>
|
||||
@ -39,6 +40,30 @@ namespace guhserver {
|
||||
class HttpRequest;
|
||||
class HttpReply;
|
||||
|
||||
class WebServerClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebServerClient(const QHostAddress &address, QObject *parent = 0);
|
||||
|
||||
QHostAddress address() const;
|
||||
|
||||
QList<QSslSocket *> connections();
|
||||
void addConnection(QSslSocket *socket);
|
||||
|
||||
void resetTimout(QSslSocket *socket);
|
||||
|
||||
private:
|
||||
QHostAddress m_address;
|
||||
QList<QSslSocket *> m_connections;
|
||||
QHash<QTimer *, QSslSocket *> m_runningConnections;
|
||||
|
||||
private slots:
|
||||
void onTimout();
|
||||
void onDisconnected();
|
||||
};
|
||||
|
||||
|
||||
class WebServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -50,6 +75,7 @@ public:
|
||||
|
||||
private:
|
||||
QHash<QUuid, QSslSocket *> m_clientList;
|
||||
QList<WebServerClient *> m_webServerClients;
|
||||
QHash<QSslSocket *, HttpRequest> m_incompleteRequests;
|
||||
|
||||
QSslConfiguration m_sslConfiguration;
|
||||
@ -59,7 +85,6 @@ private:
|
||||
qint16 m_port;
|
||||
QDir m_webinterfaceDir;
|
||||
|
||||
|
||||
bool verifyFile(QSslSocket *socket, const QString &fileName);
|
||||
QString fileName(const QString &query);
|
||||
|
||||
|
||||
@ -191,8 +191,8 @@ void TestWebserver::checkAllowedMethodCall()
|
||||
} else if(method == "CONNECT") {
|
||||
reply = nam->sendCustomRequest(request, "CONNECT");
|
||||
} else if(method == "OPTIONS") {
|
||||
request.setUrl(QUrl("http://localhost:3333/api/v1/devices"));
|
||||
reply = nam->sendCustomRequest(request, "OPTIONS");
|
||||
QNetworkRequest req(QUrl("http://localhost:3333/api/v1/devices"));
|
||||
reply = nam->sendCustomRequest(req, "OPTIONS");
|
||||
} else if(method == "TRACE") {
|
||||
reply = nam->sendCustomRequest(request, "TRACE");
|
||||
} else {
|
||||
@ -211,8 +211,8 @@ void TestWebserver::checkAllowedMethodCall()
|
||||
QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), expectedStatusCode);
|
||||
QVERIFY2(reply->hasRawHeader("Allow"), "405 should contain the allowed methods header");
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
nam->deleteLater();
|
||||
}
|
||||
|
||||
void TestWebserver::badRequests_data()
|
||||
@ -318,7 +318,7 @@ void TestWebserver::printResponse(QNetworkReply *reply)
|
||||
{
|
||||
qDebug() << "-------------------------------";
|
||||
qDebug() << "Response header:";
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
foreach (const QNetworkReply::RawHeaderPair &headerPair, reply->rawHeaderPairs()) {
|
||||
qDebug() << headerPair.first << ":" << headerPair.second;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user