mirror of https://github.com/nymea/nymea.git
437 lines
14 KiB
C++
437 lines
14 KiB
C++
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* *
|
|
* Copyright (C) 2015 Simon Stürz <simon.stuerz@guh.io> *
|
|
* *
|
|
* This file is part of guh. *
|
|
* *
|
|
* Guh is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation, version 2 of the License. *
|
|
* *
|
|
* Guh is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with guh. If not, see <http://www.gnu.org/licenses/>. *
|
|
* *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
/*!
|
|
\class guhserver::HttpReply
|
|
\brief Represents a reply of the guh webserver to a \l{HttpRequest}.
|
|
|
|
\ingroup api
|
|
\inmodule core
|
|
|
|
This class holds the header and the payload data of a network reply and represents a response
|
|
from the guh webserver to a \l{HttpRequest}.
|
|
|
|
\note RFC 7231 HTTP/1.1 Semantics and Content -> \l{http://tools.ietf.org/html/rfc7231}{http://tools.ietf.org/html/rfc7231}
|
|
*/
|
|
|
|
/*! \enum guhserver::HttpReply::HttpStatusCode
|
|
|
|
This enum type specifies the status code of a HTTP webserver reply.
|
|
|
|
You can find more information here: \l{http://tools.ietf.org/html/rfc7231#section-6.1}{http://tools.ietf.org/html/rfc7231#section-6.1}
|
|
|
|
\value Ok
|
|
The request was understood and everything is Ok.
|
|
\value Created
|
|
The resource was created sucessfully.
|
|
\value Accepted
|
|
The resource was accepted.
|
|
\value NoContent
|
|
The request has no content but it was expected.
|
|
\value Found
|
|
The resource was found.
|
|
\value BadRequest
|
|
The request was bad formatted. Also if a \l{Param} was not understood or the header is not correct.
|
|
\value Forbidden
|
|
The request tries to get access to a forbidden space.
|
|
\value NotFound
|
|
The requested resource could not be found.
|
|
\value MethodNotAllowed
|
|
The request method is not allowed. See "Allowed-Methods" header for the allowed methods.
|
|
\value RequestTimeout
|
|
The request method timed out. Default timeout = 5s.
|
|
\value Conflict
|
|
The request resource conflicts with an other.
|
|
\value InternalServerError
|
|
There was an internal server error.
|
|
\value NotImplemented
|
|
The requestet method call is not implemented.
|
|
\value BadGateway
|
|
The gateway is not correct.
|
|
\value ServiceUnavailable
|
|
The service is not available at the moment.
|
|
\value GatewayTimeout
|
|
The gateway timed out.
|
|
\value HttpVersionNotSupported
|
|
The HTTP version is not supported. The only supported version is HTTP/1.1.
|
|
*/
|
|
|
|
/*! \enum guhserver::HttpReply::HttpHeaderType
|
|
This enum type specifies the known type of a header in a HTTP webserver reply.
|
|
You can find more information here: \l{http://tools.ietf.org/html/rfc7231#section-5}
|
|
|
|
\value ContentTypeHeader
|
|
The content type header i.e. application/json.
|
|
\value ContentLenghtHeader
|
|
The length of the sent content.
|
|
\value ConnectionHeader
|
|
The connection header.
|
|
\value LocationHeader
|
|
The location header.
|
|
\value UserAgentHeader
|
|
The user agent of the client.
|
|
\value CacheControlHeader
|
|
The cache control header.
|
|
\value AllowHeader
|
|
The allowed methods header.
|
|
\value DateHeader
|
|
The server date header.
|
|
\value ServerHeader
|
|
The name of the server i.e. "Server: guh/0.6.0"
|
|
*/
|
|
|
|
/*! \enum guhserver::HttpReply::Type
|
|
|
|
This enum type describes the type of this \l{HttpReply}. There are two types:
|
|
|
|
\value TypeSync
|
|
The \l{HttpReply} can be responded imediatly.
|
|
\value TypeAsync
|
|
The \l{HttpReply} is asynchron and has to be responded later.
|
|
*/
|
|
|
|
/*! \fn void guhserver::HttpReply::finished();
|
|
This signal is emitted when this async \l{HttpReply} is finished.
|
|
*/
|
|
|
|
/*! \fn guhserver::HttpReply::HttpReply(QObject *parent);
|
|
Construct an empty \l{HttpReply} with the given \a parent.
|
|
*/
|
|
|
|
/*! \fn guhserver::HttpReply::HttpReply(const HttpStatusCode &statusCode, const Type &type, QObject *parent);
|
|
Construct a \l{HttpReply} with the given \a statusCode, \a type and \a parent.
|
|
*/
|
|
|
|
/*! \fn void guhserver::HttpReply::setHttpStatusCode(const HttpStatusCode &statusCode);
|
|
Set the \l{HttpStatusCode} of this \l{HttpReply} to the given \a statusCode.
|
|
*/
|
|
|
|
/*! \fn QDebug guhserver::operator<< (QDebug debug, const HttpReply &httpReply);
|
|
Writes the given \l{HttpReply} \a httpReply to the given \a debug. This method gets used just for debugging.
|
|
*/
|
|
|
|
|
|
|
|
#include "httpreply.h"
|
|
#include "loggingcategories.h"
|
|
#include "guhcore.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QPair>
|
|
#include <QDebug>
|
|
|
|
namespace guhserver {
|
|
|
|
HttpReply::HttpReply(QObject *parent) :
|
|
QObject(parent),
|
|
m_statusCode(HttpReply::Ok),
|
|
m_type(HttpReply::TypeSync),
|
|
m_payload(QByteArray()),
|
|
m_closeConnection(false),
|
|
m_timedOut(false)
|
|
{
|
|
m_timer = new QTimer(this);
|
|
connect(m_timer, &QTimer::timeout, this, &HttpReply::timedOut);
|
|
|
|
m_reasonPhrase = getHttpReasonPhrase(m_statusCode);
|
|
|
|
// set known headers
|
|
setHeader(HttpReply::ContentTypeHeader, "text/plain; charset=\"utf-8\";");
|
|
setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
|
|
setHeader(HttpHeaderType::DateHeader, GuhCore::instance()->timeManager()->currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8());
|
|
setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
|
|
setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive");
|
|
setRawHeader("Access-Control-Allow-Origin","*");
|
|
setRawHeader("Keep-Alive", "timeout=12, max=50");
|
|
packReply();
|
|
}
|
|
|
|
HttpReply::HttpReply(const HttpReply::HttpStatusCode &statusCode, const HttpReply::Type &type, QObject *parent):
|
|
QObject(parent),
|
|
m_statusCode(statusCode),
|
|
m_type(type),
|
|
m_payload(QByteArray()),
|
|
m_timedOut(false)
|
|
{
|
|
m_timer = new QTimer(this);
|
|
connect(m_timer, &QTimer::timeout, this, &HttpReply::timeout);
|
|
|
|
m_reasonPhrase = getHttpReasonPhrase(m_statusCode);
|
|
|
|
// set known / default headers
|
|
setHeader(HttpReply::ContentTypeHeader, "text/plain; charset=\"utf-8\";");
|
|
setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
|
|
setHeader(HttpHeaderType::DateHeader, GuhCore::instance()->timeManager()->currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8());
|
|
setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
|
|
setHeader(HttpHeaderType::ConnectionHeader, "Keep-Alive");
|
|
setRawHeader("Access-Control-Allow-Origin","*");
|
|
setRawHeader("Keep-Alive", "timeout=12, max=50");
|
|
packReply();
|
|
}
|
|
|
|
void HttpReply::setHttpStatusCode(const HttpReply::HttpStatusCode &statusCode)
|
|
{
|
|
m_statusCode = statusCode;
|
|
m_reasonPhrase = getHttpReasonPhrase(m_statusCode);
|
|
packReply();
|
|
}
|
|
|
|
/*! Returns the status code of this \l{HttpReply}.*/
|
|
HttpReply::HttpStatusCode HttpReply::httpStatusCode() const
|
|
{
|
|
return m_statusCode;
|
|
}
|
|
|
|
/*! Returns the error code reason phrase for the current \l{HttpStatusCode}.*/
|
|
QByteArray HttpReply::httpReasonPhrase() const
|
|
{
|
|
return m_reasonPhrase;
|
|
}
|
|
|
|
/*! Returns the type of this \l{HttpReply}.
|
|
* \sa Type
|
|
*/
|
|
HttpReply::Type HttpReply::type() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
/*! Set the \a clientId of this \l{HttpReply}.*/
|
|
void HttpReply::setClientId(const QUuid &clientId)
|
|
{
|
|
m_clientId = clientId;
|
|
packReply();
|
|
}
|
|
|
|
/*! Returns the clientId of this \l{HttpReply}.*/
|
|
QUuid HttpReply::clientId() const
|
|
{
|
|
return m_clientId;
|
|
}
|
|
|
|
/*! Set the payload of this \l{HttpReply} to the given \a data.*/
|
|
void HttpReply::setPayload(const QByteArray &data)
|
|
{
|
|
m_payload = data;
|
|
setHeader(HttpHeaderType::ContentLenghtHeader, QByteArray::number(data.length()));
|
|
packReply();
|
|
}
|
|
|
|
/*! Returns the payload of this \l{HttpReply}.*/
|
|
QByteArray HttpReply::payload() const
|
|
{
|
|
return m_payload;
|
|
}
|
|
|
|
/*! This method appends a raw header to the header list of this \l{HttpReply}.
|
|
The Header will be set to \a headerType : \a value.
|
|
*/
|
|
void HttpReply::setRawHeader(const QByteArray headerType, const QByteArray &value)
|
|
{
|
|
// if the header is already set, overwrite it
|
|
if (m_rawHeaderList.keys().contains(headerType)) {
|
|
m_rawHeaderList.remove(headerType);
|
|
}
|
|
m_rawHeaderList.insert(headerType, value);
|
|
packReply();
|
|
}
|
|
|
|
/*! This method appends a known header to the header list of this \l{HttpReply}.
|
|
The Header will be set to \a headerType : \a value.
|
|
*/
|
|
void HttpReply::setHeader(const HttpHeaderType &headerType, const QByteArray &value)
|
|
{
|
|
setRawHeader(getHeaderType(headerType), value);
|
|
}
|
|
|
|
/*! Returns the list of all set headers in this \l{HttpReply}.*/
|
|
QHash<QByteArray, QByteArray> HttpReply::rawHeaderList() const
|
|
{
|
|
return m_rawHeaderList;
|
|
}
|
|
|
|
/*! Returns the raw headers of this \l{HttpReply}.
|
|
\note The header will be empty until the method \l{packReply()} was called.
|
|
\sa packReply()
|
|
*/
|
|
QByteArray HttpReply::rawHeader() const
|
|
{
|
|
return m_rawHeader;
|
|
}
|
|
|
|
/*! Sets the \a close paramter of this \l{HttpReply}. If \a close is true,
|
|
the connection of the client will be closed after this reply was sent.
|
|
*/
|
|
void HttpReply::setCloseConnection(const bool &close)
|
|
{
|
|
m_closeConnection = close;
|
|
}
|
|
|
|
/*! Returns the connection close paramter of this \l{HttpReply}. If close is true, the connection
|
|
of the client will be closed after this reply was sent.
|
|
*/
|
|
bool HttpReply::closeConnection() const
|
|
{
|
|
return m_closeConnection;
|
|
}
|
|
|
|
/*! Returns true if the raw header and the payload of this \l{HttpReply} is empty.*/
|
|
bool HttpReply::isEmpty() const
|
|
{
|
|
return m_rawHeader.isEmpty() && m_payload.isEmpty() && m_rawHeaderList.isEmpty();
|
|
}
|
|
|
|
/*! Clears all data of this \l{HttpReply}. */
|
|
void HttpReply::clear()
|
|
{
|
|
m_closeConnection = false;
|
|
m_type = TypeSync;
|
|
m_statusCode = Ok;
|
|
m_rawHeader.clear();
|
|
m_payload.clear();
|
|
m_rawHeaderList.clear();
|
|
}
|
|
/*! Packs the whole reply data of this \l{HttpReply}. The data can be accessed with \l{HttpReply::data()}.
|
|
\sa data()
|
|
*/
|
|
void HttpReply::packReply()
|
|
{
|
|
// set status code
|
|
m_data.clear();
|
|
m_rawHeader.clear();
|
|
m_rawHeader.append("HTTP/1.1 " + QByteArray::number(m_statusCode) + " " + getHttpReasonPhrase(m_statusCode) + "\r\n");
|
|
|
|
// write header
|
|
foreach (const QByteArray &headerName, m_rawHeaderList.keys()) {
|
|
m_rawHeader.append(headerName + ": " + m_rawHeaderList.value(headerName) + "\r\n" );
|
|
}
|
|
|
|
m_rawHeader.append("\r\n");
|
|
m_data = m_rawHeader.append(m_payload);
|
|
}
|
|
|
|
/*! Returns the current raw data (header + payload) of this \l{HttpReply}.*/
|
|
QByteArray HttpReply::data() const
|
|
{
|
|
return m_data;
|
|
}
|
|
|
|
/*! Return true if the response took to long for the request.*/
|
|
bool HttpReply::timedOut() const
|
|
{
|
|
return m_timedOut;
|
|
}
|
|
|
|
QByteArray HttpReply::getHttpReasonPhrase(const HttpReply::HttpStatusCode &statusCode)
|
|
{
|
|
switch (statusCode) {
|
|
case HttpStatusCode::Ok:
|
|
return "Ok";
|
|
case Created:
|
|
return "Created";
|
|
case Accepted:
|
|
return "Accepted";
|
|
case NoContent:
|
|
return "No Content";
|
|
case Found:
|
|
return "Found";
|
|
case BadRequest:
|
|
return "Bad Request";
|
|
case Forbidden:
|
|
return "Forbidden";
|
|
case NotFound:
|
|
return "NotFound";
|
|
case MethodNotAllowed:
|
|
return "Method Not Allowed";
|
|
case RequestTimeout:
|
|
return "Request Timeout";
|
|
case Conflict:
|
|
return "Conflict";
|
|
case InternalServerError:
|
|
return "Internal Server Error";
|
|
case NotImplemented:
|
|
return "Not Implemented";
|
|
case BadGateway:
|
|
return "Bad Gateway";
|
|
case ServiceUnavailable:
|
|
return "Service Unavailable";
|
|
case GatewayTimeout:
|
|
return "Gateway Timeout";
|
|
case HttpVersionNotSupported:
|
|
return "HTTP Version Not Supported";
|
|
default:
|
|
return QByteArray();
|
|
}
|
|
}
|
|
|
|
QByteArray HttpReply::getHeaderType(const HttpReply::HttpHeaderType &headerType)
|
|
{
|
|
switch (headerType) {
|
|
case ContentTypeHeader:
|
|
return "Content-Type";
|
|
case ContentLenghtHeader:
|
|
return "Content-Length";
|
|
case CacheControlHeader:
|
|
return "Cache-Control";
|
|
case LocationHeader:
|
|
return "Location";
|
|
case ConnectionHeader:
|
|
return "Connection";
|
|
case UserAgentHeader:
|
|
return "User-Agent";
|
|
case AllowHeader:
|
|
return "Allow";
|
|
case DateHeader:
|
|
return "Date";
|
|
case ServerHeader:
|
|
return "Server";
|
|
default:
|
|
return QByteArray();
|
|
}
|
|
}
|
|
|
|
/*! Starts the timer for an async \l{HttpReply}.
|
|
*
|
|
* \sa finished()
|
|
*/
|
|
void HttpReply::startWait()
|
|
{
|
|
m_timer->start(10000);
|
|
}
|
|
|
|
void HttpReply::timeout()
|
|
{
|
|
qCDebug(dcWebServer) << "Http reply timeout";
|
|
m_timedOut = true;
|
|
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;
|
|
}
|
|
|
|
}
|