Implemented content type header and better file handling
This commit is contained in:
Submodule plugins/fileserverplugin updated: 2d33897c2a...7f5df336e0
Submodule plugins/wifilampplugin updated: e9e18f227f...ec4396f7a0
@@ -2,10 +2,16 @@
|
|||||||
|
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
#include <QFileDevice>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMimeDatabase>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "weblistener.h"
|
#include "weblistener.h"
|
||||||
|
|
||||||
|
const int HttpClientConnection::m_bufferSize(1024*1024*4);
|
||||||
|
|
||||||
HttpClientConnection::HttpClientConnection(QTcpSocket &socket, WebListener &webServer) :
|
HttpClientConnection::HttpClientConnection(QTcpSocket &socket, WebListener &webServer) :
|
||||||
QObject(&webServer),
|
QObject(&webServer),
|
||||||
m_socket(socket),
|
m_socket(socket),
|
||||||
@@ -44,7 +50,7 @@ void HttpClientConnection::sendResponse(HttpResponse response, const QByteArray
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
response.headers.insert(QStringLiteral("Content-Length"), QString::number(byteArray.length()));
|
response.headers.insert(HttpResponse::HEADER_CONTENTLENGTH, QString::number(byteArray.length()));
|
||||||
sendResponse(response);
|
sendResponse(response);
|
||||||
m_socket.write(byteArray);
|
m_socket.write(byteArray);
|
||||||
m_state = RequestLine;
|
m_state = RequestLine;
|
||||||
@@ -73,9 +79,41 @@ void HttpClientConnection::sendResponse(HttpResponse response, std::unique_ptr<Q
|
|||||||
if(device->isSequential())
|
if(device->isSequential())
|
||||||
throw std::runtime_error("sequental device not supported yet");
|
throw std::runtime_error("sequental device not supported yet");
|
||||||
|
|
||||||
|
response.headers.insert(HttpResponse::HEADER_CONTENTLENGTH, QString::number(device->size()));
|
||||||
|
|
||||||
m_sendingDeivce = std::move(device);
|
m_sendingDeivce = std::move(device);
|
||||||
|
|
||||||
response.headers.insert(QStringLiteral("Content-Length"), QString::number(m_sendingDeivce->size()));
|
sendResponse(response);
|
||||||
|
|
||||||
|
connect(&m_socket, &QIODevice::bytesWritten, this, &HttpClientConnection::bytesWritten);
|
||||||
|
bytesWritten();
|
||||||
|
|
||||||
|
m_state = SendingResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClientConnection::sendResponse(HttpResponse response, std::unique_ptr<QFileDevice> &&device)
|
||||||
|
{
|
||||||
|
if(m_state != WaitingForResponse)
|
||||||
|
throw std::runtime_error("sending a response now is not allowed!");
|
||||||
|
|
||||||
|
if(!device->isReadable())
|
||||||
|
throw std::runtime_error("device is not readable");
|
||||||
|
|
||||||
|
if(device->isSequential())
|
||||||
|
throw std::runtime_error("sequental device not supported yet");
|
||||||
|
|
||||||
|
const QFileInfo fileInfo(device->fileName());
|
||||||
|
|
||||||
|
response.headers.insert(HttpResponse::HEADER_CONTENTLENGTH, QString::number(fileInfo.size() - device->pos()));
|
||||||
|
|
||||||
|
{
|
||||||
|
static const QMimeDatabase mimeDatabse;
|
||||||
|
const auto mimeType = mimeDatabse.mimeTypeForFile(fileInfo);
|
||||||
|
if(mimeType.isValid())
|
||||||
|
response.headers.insert(HttpResponse::HEADER_CONTENTTYPE, mimeType.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sendingDeivce = std::move(device);
|
||||||
|
|
||||||
sendResponse(response);
|
sendResponse(response);
|
||||||
|
|
||||||
@@ -104,16 +142,16 @@ void HttpClientConnection::readyRead()
|
|||||||
{
|
{
|
||||||
case RequestLine:
|
case RequestLine:
|
||||||
{
|
{
|
||||||
auto parts = line.split(' ');
|
auto parts = line.split(QLatin1Char(' '));
|
||||||
if(parts.count() != 3)
|
if(parts.count() != 3)
|
||||||
{
|
{
|
||||||
m_socket.close();
|
m_socket.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_request.method = parts.at(0);
|
m_request.method = parts.at(0).toUtf8();
|
||||||
m_request.path = parts.at(1);
|
m_request.path = QUrl::fromPercentEncoding(parts.at(1).toUtf8());
|
||||||
m_request.protocol = parts.at(2);
|
m_request.protocol = parts.at(2).toUtf8();
|
||||||
|
|
||||||
m_state = Headers;
|
m_state = Headers;
|
||||||
continue;
|
continue;
|
||||||
@@ -148,7 +186,9 @@ void HttpClientConnection::readyRead()
|
|||||||
clearRequest();
|
clearRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
default: qt_noop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -169,13 +209,16 @@ void HttpClientConnection::readyRead()
|
|||||||
m_webListener.handleRequest(this, m_request);
|
m_webListener.handleRequest(this, m_request);
|
||||||
clearRequest();
|
clearRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
default: qt_noop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClientConnection::bytesWritten()
|
void HttpClientConnection::bytesWritten()
|
||||||
{
|
{
|
||||||
if(m_socket.bytesToWrite() >= 1024*1024*4)
|
if(m_socket.bytesToWrite() >= m_bufferSize)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(m_socket.bytesToWrite() == 0 && m_sendingDeivce->bytesAvailable() == 0)
|
if(m_socket.bytesToWrite() == 0 && m_sendingDeivce->bytesAvailable() == 0)
|
||||||
@@ -186,7 +229,7 @@ void HttpClientConnection::bytesWritten()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto buffer = m_sendingDeivce->read(1024*1024*4);
|
auto buffer = m_sendingDeivce->read(m_bufferSize);
|
||||||
m_socket.write(buffer);
|
m_socket.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
#include "httprequest.h"
|
#include "httprequest.h"
|
||||||
|
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
|
class QFileDevice;
|
||||||
|
|
||||||
class WebListener;
|
class WebListener;
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ class WEBSERVERLIB_EXPORT HttpClientConnection : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
static const int m_bufferSize;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HttpClientConnection(QTcpSocket &socket, WebListener &webListener);
|
explicit HttpClientConnection(QTcpSocket &socket, WebListener &webListener);
|
||||||
|
|
||||||
@@ -25,6 +28,7 @@ public:
|
|||||||
void sendResponse(HttpResponse response, const QByteArray &byteArray);
|
void sendResponse(HttpResponse response, const QByteArray &byteArray);
|
||||||
void sendResponse(HttpResponse response, const QString &string);
|
void sendResponse(HttpResponse response, const QString &string);
|
||||||
void sendResponse(HttpResponse response, std::unique_ptr<QIODevice> &&device);
|
void sendResponse(HttpResponse response, std::unique_ptr<QIODevice> &&device);
|
||||||
|
void sendResponse(HttpResponse response, std::unique_ptr<QFileDevice> &&device);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void readyRead();
|
void readyRead();
|
||||||
|
21
webserverlib/httpexception.cpp
Normal file
21
webserverlib/httpexception.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "httpexception.h"
|
||||||
|
|
||||||
|
HttpException::HttpException(const HttpRequest &httpRequest, const QString &what_arg) :
|
||||||
|
std::runtime_error(what_arg.toStdString()), m_httpRequest(httpRequest)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpException::HttpException(const HttpRequest &httpRequest, const std::string &what_arg) :
|
||||||
|
std::runtime_error(what_arg), m_httpRequest(httpRequest)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpException::HttpException(const HttpRequest &httpRequest, const char *what_arg) :
|
||||||
|
std::runtime_error(what_arg), m_httpRequest(httpRequest)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const HttpRequest &HttpException::httpRequest() const
|
||||||
|
{
|
||||||
|
return m_httpRequest;
|
||||||
|
}
|
22
webserverlib/httpexception.h
Normal file
22
webserverlib/httpexception.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
|
||||||
|
class HttpException : public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit HttpException(const HttpRequest &httpRequest, const QString &what_arg);
|
||||||
|
explicit HttpException(const HttpRequest &httpRequest, const std::string &what_arg);
|
||||||
|
explicit HttpException(const HttpRequest &httpRequest, const char *what_arg);
|
||||||
|
|
||||||
|
const HttpRequest &httpRequest() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
HttpRequest m_httpRequest;
|
||||||
|
};
|
6
webserverlib/httpnotfoundexception.cpp
Normal file
6
webserverlib/httpnotfoundexception.cpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#include "httpnotfoundexception.h"
|
||||||
|
|
||||||
|
HttpNotFoundException::HttpNotFoundException(const HttpRequest &httpRequest) :
|
||||||
|
HttpException(httpRequest, QString("File not found: %0").arg(httpRequest.path))
|
||||||
|
{
|
||||||
|
}
|
11
webserverlib/httpnotfoundexception.h
Normal file
11
webserverlib/httpnotfoundexception.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "httpexception.h"
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
|
||||||
|
class HttpNotFoundException : public HttpException
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit HttpNotFoundException(const HttpRequest &httpRequest);
|
||||||
|
};
|
@@ -3,5 +3,4 @@
|
|||||||
WebApplication::WebApplication(QObject *parent) :
|
WebApplication::WebApplication(QObject *parent) :
|
||||||
QObject(parent)
|
QObject(parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,8 @@
|
|||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "httpclientconnection.h"
|
#include "httpclientconnection.h"
|
||||||
#include "webapplication.h"
|
#include "webapplication.h"
|
||||||
|
#include "httpnotfoundexception.h"
|
||||||
|
#include "httpexception.h"
|
||||||
|
|
||||||
WebListener::WebListener(const QJsonObject &config, WebServer &webServer) :
|
WebListener::WebListener(const QJsonObject &config, WebServer &webServer) :
|
||||||
QObject(&webServer), m_webServer(webServer)
|
QObject(&webServer), m_webServer(webServer)
|
||||||
@@ -82,17 +84,12 @@ void WebListener::handleRequest(HttpClientConnection *connection, const HttpRequ
|
|||||||
host = hostHeaderIter.value();
|
host = hostHeaderIter.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
if(host.isEmpty())
|
if(host.isEmpty())
|
||||||
{
|
{
|
||||||
const auto iter = m_hosts.find(QStringLiteral("*"));
|
const auto iter = m_hosts.find(QStringLiteral("*"));
|
||||||
if(iter == m_hosts.constEnd())
|
if(iter == m_hosts.constEnd())
|
||||||
{
|
throw HttpException(request, tr("Your request didn't contain a Host header and there is no fallback host configured!"));
|
||||||
HttpResponse response;
|
|
||||||
response.protocol = request.protocol;
|
|
||||||
response.statusCode = HttpResponse::StatusCode::BadRequest;
|
|
||||||
connection->sendResponse(response, tr("Your request didn't contain a Host header and there is no fallback host configured!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter.value()->handleRequest(connection, request);
|
iter.value()->handleRequest(connection, request);
|
||||||
return;
|
return;
|
||||||
@@ -103,16 +100,22 @@ void WebListener::handleRequest(HttpClientConnection *connection, const HttpRequ
|
|||||||
{
|
{
|
||||||
iter = m_hosts.find(QStringLiteral("*"));
|
iter = m_hosts.find(QStringLiteral("*"));
|
||||||
if(iter == m_hosts.constEnd())
|
if(iter == m_hosts.constEnd())
|
||||||
{
|
throw HttpException(request, tr("Your requested Host \"%0\" is unknown and there is no fallback host configured!").arg(host));
|
||||||
HttpResponse response;
|
|
||||||
response.protocol = request.protocol;
|
|
||||||
response.statusCode = HttpResponse::StatusCode::BadRequest;
|
|
||||||
connection->sendResponse(response, tr("Your requested Host \"%0\" is unknown and there is no fallback host configured!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iter.value()->handleRequest(connection, request);
|
iter.value()->handleRequest(connection, request);
|
||||||
|
} catch (const HttpException &ex) {
|
||||||
|
HttpResponse response;
|
||||||
|
response.protocol = ex.httpRequest().protocol;
|
||||||
|
response.statusCode = HttpResponse::StatusCode::BadRequest;
|
||||||
|
connection->sendResponse(response, QByteArray(ex.what()));
|
||||||
|
// this could cause endless loop with exceptions in HttpClientConnection::sendResponse
|
||||||
|
//} catch (const std::exception &ex) {
|
||||||
|
// HttpResponse response;
|
||||||
|
// response.protocol = QStringLiteral("HTTP/1.1");
|
||||||
|
// response.statusCode = HttpResponse::StatusCode::BadRequest;
|
||||||
|
// connection->sendResponse(response, QByteArray(ex.what()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebListener::acceptError(QAbstractSocket::SocketError socketError)
|
void WebListener::acceptError(QAbstractSocket::SocketError socketError)
|
||||||
|
@@ -14,7 +14,9 @@ SOURCES += \
|
|||||||
httpclientconnection.cpp \
|
httpclientconnection.cpp \
|
||||||
httprequest.cpp \
|
httprequest.cpp \
|
||||||
httpresponse.cpp \
|
httpresponse.cpp \
|
||||||
webserver.cpp
|
webserver.cpp \
|
||||||
|
httpexception.cpp \
|
||||||
|
httpnotfoundexception.cpp
|
||||||
|
|
||||||
HEADERS += webserverlib_global.h \
|
HEADERS += webserverlib_global.h \
|
||||||
weblistener.h \
|
weblistener.h \
|
||||||
@@ -23,7 +25,9 @@ HEADERS += webserverlib_global.h \
|
|||||||
httpclientconnection.h \
|
httpclientconnection.h \
|
||||||
httprequest.h \
|
httprequest.h \
|
||||||
httpresponse.h \
|
httpresponse.h \
|
||||||
webserver.h
|
webserver.h \
|
||||||
|
httpexception.h \
|
||||||
|
httpnotfoundexception.h
|
||||||
|
|
||||||
FORMS +=
|
FORMS +=
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user