add some tests and add documentation to httpreply

This commit is contained in:
Simon Stürz 2015-07-17 14:17:41 +02:00 committed by Michael Zanetti
parent 27a8db73d8
commit 478c832ec8
4 changed files with 150 additions and 25 deletions

View File

@ -18,82 +18,169 @@
* * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*!
\class HttpReply
\brief Represents a reply of the guh webserver.
\ingroup types
\inmodule libguh
This class holds the header and the payload data of a network reply and represents a response
from the guh webserver.
\note RFC 7231 HTTP/1.1 Semantics and Content -> \l{http://tools.ietf.org/html/rfc7231}{http://tools.ietf.org/html/rfc7231}
*/
/*! \enum HttpReply::HttpStatusCode
This enum type specifies the status code of a HTTP webserver reply.
\value Ok
The request was understood and everything is Ok.
\value Created
...
\value Accepted
...
\value NoContent
...
\value Found
...
\value BadRequest
...
\value Forbidden
...
\value NotFound
...
\value MethodNotAllowed
...
\value RequestTimeout
...
\value Conflict
...
\value InternalServerError
...
\value NotImplemented
...
\value BadGateway
...
\value ServiceUnavailable
...
\value GatewayTimeout
...
\value HttpVersionNotSupported
...
*/
/*! \enum HttpReply::HttpHeaderType
This enum type specifies the known type of a header in a HTTP webserver reply.
\value ContentTypeHeader
The request was understood and everything is Ok.
\value ContentLenghtHeader
...
\value ConnectionHeader
...
\value LocationHeader
...
\value UserAgentHeader
...
\value CacheControlHeader
...
\value AllowHeader
...
\value DateHeader
...
\value ServerHeader
...
*/
#include "httpreply.h" #include "httpreply.h"
#include <QDateTime> #include <QDateTime>
#include <QPair> #include <QPair>
// Note: RFC 7231 HTTP/1.1 Semantics and Content -> http://tools.ietf.org/html/rfc7231 /*! Construct a HttpReply with the given \a statusCode. */
HttpReply::HttpReply(const HttpStatusCode &statusCode) : HttpReply::HttpReply(const HttpStatusCode &statusCode) :
m_statusCode(statusCode), m_statusCode(statusCode),
m_payload(QByteArray()) m_payload(QByteArray())
{ {
// set known headers // set known headers
//setHeader(HeaderType::ContentTypeHeader, "application/x-www-form-urlencoded; charset=\"utf-8\""); setHeader(HttpHeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING));
setHeader(HeaderType::ServerHeader, "guh/" + QByteArray(GUH_VERSION_STRING)); setHeader(HttpHeaderType::UserAgentHeader, "guh/" + QByteArray(REST_API_VERSION));
setHeader(HeaderType::UserAgentHeader, "guh/" + QByteArray(REST_API_VERSION)); setHeader(HttpHeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8() + " GMT");
setHeader(HeaderType::DateHeader, QDateTime::currentDateTime().toString("ddd, dd MMM yyyy hh:mm:ss").toUtf8() + " GMT"); setHeader(HttpHeaderType::CacheControlHeader, "no-cache");
setHeader(HeaderType::CacheControlHeader, "no-cache");
} }
/*! Set the \a statusCode for this \l{HttpReply}.*/
void HttpReply::setHttpStatusCode(const HttpReply::HttpStatusCode &statusCode) void HttpReply::setHttpStatusCode(const HttpReply::HttpStatusCode &statusCode)
{ {
m_statusCode = statusCode; m_statusCode = statusCode;
} }
/*! Returns the status code of this \l{HttpReply}.*/
HttpReply::HttpStatusCode HttpReply::httpStatusCode() const HttpReply::HttpStatusCode HttpReply::httpStatusCode() const
{ {
return m_statusCode; return m_statusCode;
} }
/*! Set the payload of this \l{HttpReply} to the given \a data.*/
void HttpReply::setPayload(const QByteArray &data) void HttpReply::setPayload(const QByteArray &data)
{ {
m_payload = data; m_payload = data;
setHeader(HeaderType::ContentLenghtHeader, QByteArray::number(data.length())); setHeader(HttpHeaderType::ContentLenghtHeader, QByteArray::number(data.length()));
} }
/*! Returns the payload of this \l{HttpReply}.*/
QByteArray HttpReply::payload() const QByteArray HttpReply::payload() const
{ {
return m_payload; 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) void HttpReply::setRawHeader(const QByteArray headerType, const QByteArray &value)
{ {
// if the header is already set, overwrite it // if the header is already set, overwrite it
if (m_rawHeaderList.keys().contains(headerType)) { if (m_rawHeaderList.keys().contains(headerType)) {
m_rawHeaderList.remove(headerType); m_rawHeaderList.remove(headerType);
} }
m_rawHeaderList.insert(headerType, value); m_rawHeaderList.insert(headerType, value);
} }
void HttpReply::setHeader(const HttpReply::HeaderType &headerType, const QByteArray &value) /*! 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); setRawHeader(getHeaderType(headerType), value);
} }
/*! Returns the list of all set headers in this \l{HttpReply}.*/
QHash<QByteArray, QByteArray> HttpReply::rawHeaderList() const QHash<QByteArray, QByteArray> HttpReply::rawHeaderList() const
{ {
return m_rawHeaderList; 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 QByteArray HttpReply::rawHeader() const
{ {
return m_rawHeader; return m_rawHeader;
} }
bool HttpReply::isValid() const /*! Returns true if the raw header and the payload of this \l{HttpReply} is empty.*/
{
// TODO: verify if header is valid and payload is valid
return true;
}
bool HttpReply::isEmpty() const bool HttpReply::isEmpty() const
{ {
return m_rawHeader.isEmpty() && m_payload.isEmpty() && m_rawHeaderList.isEmpty(); return m_rawHeader.isEmpty() && m_payload.isEmpty() && m_rawHeaderList.isEmpty();
} }
/*! Clears all data of this \l{HttpReply}. */
void HttpReply::clear() void HttpReply::clear()
{ {
m_rawHeader.clear(); m_rawHeader.clear();
@ -101,6 +188,7 @@ void HttpReply::clear()
m_rawHeaderList.clear(); m_rawHeaderList.clear();
} }
/*! Returns the whole reply data of this \l{HttpReply}. The data contain the HTTP header and the payload. */
QByteArray HttpReply::packReply() QByteArray HttpReply::packReply()
{ {
// set status code // set status code
@ -159,7 +247,7 @@ QByteArray HttpReply::getHttpReasonPhrase(const HttpReply::HttpStatusCode &statu
} }
} }
QByteArray HttpReply::getHeaderType(const HttpReply::HeaderType &headerType) QByteArray HttpReply::getHeaderType(const HttpReply::HttpHeaderType &headerType)
{ {
switch (headerType) { switch (headerType) {
case ContentTypeHeader: case ContentTypeHeader:

View File

@ -50,7 +50,7 @@ public:
HttpVersionNotSupported = 505 HttpVersionNotSupported = 505
}; };
enum HeaderType { enum HttpHeaderType {
ContentTypeHeader, ContentTypeHeader,
ContentLenghtHeader, ContentLenghtHeader,
ConnectionHeader, ConnectionHeader,
@ -71,13 +71,11 @@ public:
QByteArray payload() const; QByteArray payload() const;
void setRawHeader(const QByteArray headerType, const QByteArray &value); void setRawHeader(const QByteArray headerType, const QByteArray &value);
void setHeader(const HeaderType &headerType, const QByteArray &value); void setHeader(const HttpHeaderType &headerType, const QByteArray &value);
QHash<QByteArray, QByteArray> rawHeaderList() const; QHash<QByteArray, QByteArray> rawHeaderList() const;
QByteArray rawHeader() const; QByteArray rawHeader() const;
bool isValid() const;
bool isEmpty() const; bool isEmpty() const;
void clear(); void clear();
QByteArray packReply(); QByteArray packReply();
@ -90,7 +88,7 @@ private:
QHash<QByteArray, QByteArray> m_rawHeaderList; QHash<QByteArray, QByteArray> m_rawHeaderList;
QByteArray getHttpReasonPhrase(const HttpStatusCode &statusCode); QByteArray getHttpReasonPhrase(const HttpStatusCode &statusCode);
QByteArray getHeaderType(const HeaderType &headerType); QByteArray getHeaderType(const HttpHeaderType &headerType);
QByteArray packHeader() const; QByteArray packHeader() const;
}; };

View File

@ -157,7 +157,6 @@ void JsonRPCServer::setup()
void JsonRPCServer::processData(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message) void JsonRPCServer::processData(const QUuid &clientId, const QString &targetNamespace, const QString &method, const QVariantMap &message)
{ {
// Note: id, targetNamespace and method already checked in TcpServer // Note: id, targetNamespace and method already checked in TcpServer
int commandId = message.value("id").toInt(); int commandId = message.value("id").toInt();
QVariantMap params = message.value("params").toMap(); QVariantMap params = message.value("params").toMap();

View File

@ -46,7 +46,8 @@ private slots:
void checkAllowedMethodCall_data(); void checkAllowedMethodCall_data();
void checkAllowedMethodCall(); void checkAllowedMethodCall();
void getFiles_data();
void getFiles();
private: private:
// for debugging // for debugging
@ -113,7 +114,6 @@ void TestWebserver::checkAllowedMethodCall()
QFETCH(QString, method); QFETCH(QString, method);
QFETCH(int, expectedStatusCode); QFETCH(int, expectedStatusCode);
QNetworkAccessManager *nam = new QNetworkAccessManager(this); QNetworkAccessManager *nam = new QNetworkAccessManager(this);
QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*))); QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*)));
@ -149,10 +149,50 @@ void TestWebserver::checkAllowedMethodCall()
reply->deleteLater(); reply->deleteLater();
} }
void TestWebserver::getFiles_data()
{
QTest::addColumn<QString>("query");
QTest::addColumn<int>("expectedStatusCode");
QTest::newRow("get /etc/passwd") << "/etc/passwd" << 404;
QTest::newRow("get /etc/guh/guhd.conf") << "/etc/guh/guhd.conf" << 404;
QTest::newRow("get /etc/sudoers") << "/etc/sudoers" << 404;
QTest::newRow("get /root/.ssh/id_rsa.pub") << "/root/.ssh/id_rsa.pub" << 404;
}
void TestWebserver::getFiles()
{
QFETCH(QString, query);
QFETCH(int, expectedStatusCode);
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
QSignalSpy clientSpy(nam, SIGNAL(finished(QNetworkReply*)));
QNetworkRequest request;
request.setUrl(QUrl("http://localhost:3000" + query));
QNetworkReply *reply = nam->get(request);
clientSpy.wait(200);
QVERIFY2(clientSpy.count() == 1, "expected exactly 1 response from webserver");
printResponse(reply);
bool ok = false;
qDebug() << reply->readAll();
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(&ok);
QVERIFY2(ok, "Could not convert statuscode from response to int");
QCOMPARE(statusCode, expectedStatusCode);
reply->deleteLater();
}
void TestWebserver::printResponse(QNetworkReply *reply) void TestWebserver::printResponse(QNetworkReply *reply)
{ {
qDebug() << "-------------------------------"; qDebug() << "-------------------------------";
qDebug() << "Response header:"; qDebug() << "Response header:";
qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
foreach (const QNetworkReply::RawHeaderPair &headerPair, reply->rawHeaderPairs()) { foreach (const QNetworkReply::RawHeaderPair &headerPair, reply->rawHeaderPairs()) {
qDebug() << headerPair.first << ":" << headerPair.second; qDebug() << headerPair.first << ":" << headerPair.second;
} }