diff options
author | Mikhail Svetkin <mikhail.svetkin@gmail.com> | 2019-07-21 20:52:31 +0200 |
---|---|---|
committer | Mikhail Svetkin <mikhail.svetkin@gmail.com> | 2019-10-03 19:40:22 +0200 |
commit | 73175545e69cc5f07a7a1447a6b8c4c74d9795c8 (patch) | |
tree | c92297b12f8c397e2b0d852894c405519a4b749c | |
parent | b83837076953a22bbe56143da9f626e224501212 (diff) |
QHttpServerResponse: Extend the API
Add new API for HTTP headers manipulations.
Add QHttpServerResponse::write function which will
allow to write custom response objects in a future.
Fixes: QTBUG-76933
Change-Id: I744303be1b517c07f698c4a3dd2c4296f77e3b03
Reviewed-by: Tasuku Suzuki <tasuku.suzuki@tqcs.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
-rw-r--r-- | src/httpserver/qhttpserver.cpp | 5 | ||||
-rw-r--r-- | src/httpserver/qhttpserverresponse.cpp | 225 | ||||
-rw-r--r-- | src/httpserver/qhttpserverresponse.h | 38 | ||||
-rw-r--r-- | src/httpserver/qhttpserverresponse_p.h | 13 | ||||
-rw-r--r-- | tests/auto/qhttpserver/tst_qhttpserver.cpp | 21 | ||||
-rw-r--r-- | tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp | 73 |
6 files changed, 358 insertions, 17 deletions
diff --git a/src/httpserver/qhttpserver.cpp b/src/httpserver/qhttpserver.cpp index 5b9ed05..7cc91db 100644 --- a/src/httpserver/qhttpserver.cpp +++ b/src/httpserver/qhttpserver.cpp @@ -126,10 +126,7 @@ void QHttpServer::sendResponse(const QHttpServerResponse &response, const QHttpServerRequest &request, QTcpSocket *socket) { - auto responder = makeResponder(request, socket); - responder.write(response.data(), - response.mimeType(), - response.statusCode()); + response.write(makeResponder(request, socket)); } /*! diff --git a/src/httpserver/qhttpserverresponse.cpp b/src/httpserver/qhttpserverresponse.cpp index ca2e657..a7ba006 100644 --- a/src/httpserver/qhttpserverresponse.cpp +++ b/src/httpserver/qhttpserverresponse.cpp @@ -29,8 +29,9 @@ #include <QtHttpServer/qhttpserverresponse.h> -#include <private/qhttpserverresponse_p.h> #include <private/qhttpserverliterals_p.h> +#include <private/qhttpserverresponse_p.h> +#include <private/qhttpserverresponder_p.h> #include <QtCore/qfile.h> #include <QtCore/qjsondocument.h> @@ -89,15 +90,16 @@ QHttpServerResponse::QHttpServerResponse(const QJsonArray &data) QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType, const QByteArray &data, const StatusCode status) - : QHttpServerResponse(new QHttpServerResponsePrivate{mimeType, data, status}) + : QHttpServerResponse(mimeType, + new QHttpServerResponsePrivate{data, status, {}}) { } QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType, const QByteArray &data, const StatusCode status) - : QHttpServerResponse( - new QHttpServerResponsePrivate{std::move(mimeType), data, status}) + : QHttpServerResponse(std::move(mimeType), + new QHttpServerResponsePrivate{data, status, {}}) { } @@ -105,7 +107,8 @@ QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType, QByteArray &&data, const StatusCode status) : QHttpServerResponse( - new QHttpServerResponsePrivate{mimeType, std::move(data), status}) + mimeType, + new QHttpServerResponsePrivate{std::move(data), status, {}}) { } @@ -113,8 +116,8 @@ QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType, QByteArray &&data, const StatusCode status) : QHttpServerResponse( - new QHttpServerResponsePrivate{std::move(mimeType), std::move(data), - status}) + std::move(mimeType), + new QHttpServerResponsePrivate{std::move(data), status, {}}) { } @@ -133,27 +136,227 @@ QHttpServerResponse QHttpServerResponse::fromFile(const QString &fileName) return QHttpServerResponse(mimeType, data); } -QHttpServerResponse::QHttpServerResponse(QHttpServerResponsePrivate *d) +QHttpServerResponse::QHttpServerResponse(const QByteArray &mimeType, + QHttpServerResponsePrivate *d) + : d_ptr(d) +{ + setHeader(QHttpServerLiterals::contentTypeHeader(), mimeType); +} + +QHttpServerResponse::QHttpServerResponse(QByteArray &&mimeType, + QHttpServerResponsePrivate *d) : d_ptr(d) { + setHeader(QHttpServerLiterals::contentTypeHeader(), + std::move(mimeType)); } +/*! + Returns response body. +*/ QByteArray QHttpServerResponse::data() const { Q_D(const QHttpServerResponse); return d->data; } +QHttpServerResponse::StatusCode QHttpServerResponse::statusCode() const +{ + Q_D(const QHttpServerResponse); + return d->statusCode; +} + +/*! + Returns HTTP "Content-Type" header. + + \note Default value is "text/html" +*/ QByteArray QHttpServerResponse::mimeType() const { Q_D(const QHttpServerResponse); - return d->mimeType; + const auto res = d->headers.find( + QHttpServerLiterals::contentTypeHeader()); + if (res == d->headers.end()) + return QHttpServerLiterals::contentTypeTextHtml(); + + return res->second; } -QHttpServerResponse::StatusCode QHttpServerResponse::statusCode() const +/*! + Adds the HTTP header with name \a name and value \a value, + does not override any previously set headers. +*/ +void QHttpServerResponse::addHeader(QByteArray &&name, QByteArray &&value) +{ + Q_D(QHttpServerResponse); + d->headers.emplace(std::move(name), std::move(value)); +} + +/*! + Adds the HTTP header with name \a name and value \a value, + does not override any previously set headers. +*/ +void QHttpServerResponse::addHeader(QByteArray &&name, const QByteArray &value) +{ + Q_D(QHttpServerResponse); + d->headers.emplace(std::move(name), value); +} + +/*! + Adds the HTTP header with name \a name and value \a value, + does not override any previously set headers. +*/ +void QHttpServerResponse::addHeader(const QByteArray &name, QByteArray &&value) +{ + Q_D(QHttpServerResponse); + d->headers.emplace(name, std::move(value)); +} + +/*! + Adds the HTTP header with name \a name and value \a value, + does not override any previously set headers. +*/ +void QHttpServerResponse::addHeader(const QByteArray &name, const QByteArray &value) +{ + Q_D(QHttpServerResponse); + d->headers.emplace(name, value); +} + +void QHttpServerResponse::addHeaders(QHttpServerResponder::HeaderList headers) +{ + for (auto &&header : headers) + addHeader(header.first, header.second); +} + +/*! + Removes the HTTP header with name \a name. +*/ +void QHttpServerResponse::clearHeader(const QByteArray &name) +{ + Q_D(QHttpServerResponse); + d->headers.erase(name); +} + +/*! + Removes all HTTP headers. +*/ +void QHttpServerResponse::clearHeaders() +{ + Q_D(QHttpServerResponse); + d->headers.clear(); +} + +/*! + Sets the HTTP header with name \a name and value \a value, + overriding any previously set headers. +*/ +void QHttpServerResponse::setHeader(QByteArray &&name, QByteArray &&value) +{ + Q_D(QHttpServerResponse); + clearHeader(name); + addHeader(std::move(name), std::move(value)); +} + +/*! + Sets the HTTP header with name \a name and value \a value, + overriding any previously set headers. +*/ +void QHttpServerResponse::setHeader(QByteArray &&name, const QByteArray &value) +{ + Q_D(QHttpServerResponse); + clearHeader(name); + addHeader(std::move(name), value); +} + +/*! + Sets the HTTP header with name \a name and value \a value, + overriding any previously set headers. +*/ +void QHttpServerResponse::setHeader(const QByteArray &name, QByteArray &&value) +{ + Q_D(QHttpServerResponse); + clearHeader(name); + addHeader(name, std::move(value)); +} + +/*! + Sets the HTTP header with name \a name and value \a value, + overriding any previously set headers. +*/ +void QHttpServerResponse::setHeader(const QByteArray &name, const QByteArray &value) +{ + Q_D(QHttpServerResponse); + clearHeader(name); + addHeader(name, value); +} + +/*! + Sets the headers \a headers, overriding any previously set headers. +*/ +void QHttpServerResponse::setHeaders(QHttpServerResponder::HeaderList headers) +{ + for (auto &&header : headers) + setHeader(header.first, header.second); +} + +/*! + Returns true if the response contains an HTTP header with name \a name, + otherwise returns false. +*/ +bool QHttpServerResponse::hasHeader(const QByteArray &header) const { Q_D(const QHttpServerResponse); - return d->statusCode; + return d->headers.find(header) != d->headers.end(); +} + +/*! + Returns true if the response contains an HTTP header with name \a name and + with value \a value, otherwise returns false. +*/ +bool QHttpServerResponse::hasHeader(const QByteArray &name, + const QByteArray &value) const +{ + Q_D(const QHttpServerResponse); + auto range = d->headers.equal_range(name); + + auto condition = [&value] (const std::pair<QByteArray, QByteArray> &pair) { + return pair.second == value; + }; + + return std::find_if(range.first, range.second, condition) != range.second; +} + +/*! + Returns values of the HTTP header with name \a name +*/ +QVector<QByteArray> QHttpServerResponse::headers(const QByteArray &name) const +{ + Q_D(const QHttpServerResponse); + + QVector<QByteArray> results; + auto range = d->headers.equal_range(name); + + for (auto it = range.first; it != range.second; ++it) + results.append(it->second); + + return results; +} + +/*! + Writes HTTP response into QHttpServerResponder \a responder. +*/ +void QHttpServerResponse::write(QHttpServerResponder &&responder) const +{ + Q_D(const QHttpServerResponse); + responder.writeStatusLine(d->statusCode); + + for (auto &&header : d->headers) + responder.writeHeader(header.first, header.second); + + responder.writeHeader(QHttpServerLiterals::contentLengthHeader(), + QByteArray::number(d->data.size())); + + responder.writeBody(d->data); } QT_END_NAMESPACE diff --git a/src/httpserver/qhttpserverresponse.h b/src/httpserver/qhttpserverresponse.h index 73fa52d..9f5d3dd 100644 --- a/src/httpserver/qhttpserverresponse.h +++ b/src/httpserver/qhttpserverresponse.h @@ -86,8 +86,44 @@ public: QByteArray mimeType() const; StatusCode statusCode() const; + + void addHeader(QByteArray &&name, QByteArray &&value); + void addHeader(QByteArray &&name, const QByteArray &value); + void addHeader(const QByteArray &name, QByteArray &&value); + void addHeader(const QByteArray &name, const QByteArray &value); + + void addHeaders(QHttpServerResponder::HeaderList headers); + + template<typename Container> + void addHeaders(const Container &headers) + { + for (const auto &header : headers) + addHeader(header.first, header.second); + } + + void clearHeader(const QByteArray &name); + void clearHeaders(); + + void setHeader(QByteArray &&name, QByteArray &&value); + void setHeader(QByteArray &&name, const QByteArray &value); + void setHeader(const QByteArray &name, QByteArray &&value); + void setHeader(const QByteArray &name, const QByteArray &value); + + void setHeaders(QHttpServerResponder::HeaderList headers); + + bool hasHeader(const QByteArray &name) const; + bool hasHeader(const QByteArray &name, const QByteArray &value) const; + + QVector<QByteArray> headers(const QByteArray &name) const; + + virtual void write(QHttpServerResponder &&responder) const; + private: - QHttpServerResponse(QHttpServerResponsePrivate *d); + QHttpServerResponse(const QByteArray &mimeType, + QHttpServerResponsePrivate *d); + + QHttpServerResponse(QByteArray &&mimeType, + QHttpServerResponsePrivate *d); QScopedPointer<QHttpServerResponsePrivate> d_ptr; }; diff --git a/src/httpserver/qhttpserverresponse_p.h b/src/httpserver/qhttpserverresponse_p.h index 021ed25..8b81ad0 100644 --- a/src/httpserver/qhttpserverresponse_p.h +++ b/src/httpserver/qhttpserverresponse_p.h @@ -44,14 +44,25 @@ #include <QtHttpServer/qhttpserverresponse.h> +#include <functional> +#include <unordered_map> + QT_BEGIN_NAMESPACE class QHttpServerResponsePrivate { + struct HashHelper { + std::size_t operator()(const QByteArray& key) const + { + return qHash(key.toLower()); + } + }; + public: - QByteArray mimeType; QByteArray data; QHttpServerResponse::StatusCode statusCode; + + std::unordered_multimap<QByteArray, QByteArray, HashHelper> headers; }; QT_END_NAMESPACE diff --git a/tests/auto/qhttpserver/tst_qhttpserver.cpp b/tests/auto/qhttpserver/tst_qhttpserver.cpp index 3ca4c99..a0a437c 100644 --- a/tests/auto/qhttpserver/tst_qhttpserver.cpp +++ b/tests/auto/qhttpserver/tst_qhttpserver.cpp @@ -91,6 +91,7 @@ private slots: void routePost(); void routeDelete_data(); void routeDelete(); + void routeExtraHeaders(); void invalidRouterArguments(); void checkRouteLambdaCapture(); @@ -235,6 +236,13 @@ void tst_QHttpServer::initTestCase() writeChunk(""); }); + httpserver.route("/extra-headers", [] () { + QHttpServerResponse resp(""); + resp.setHeader("Content-Type", "application/x-empty"); + resp.setHeader("Server", "test server"); + return resp; + }); + urlBase = QStringLiteral("http://localhost:%1%2").arg(httpserver.listen()); } @@ -598,6 +606,19 @@ void tst_QHttpServer::routeDelete() QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), code); } +void tst_QHttpServer::routeExtraHeaders() +{ + QNetworkAccessManager networkAccessManager; + const QUrl requestUrl(urlBase.arg("/extra-headers")); + auto reply = networkAccessManager.get(QNetworkRequest(requestUrl)); + + QTRY_VERIFY(reply->isFinished()); + + QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader), "application/x-empty"); + QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + QCOMPARE(reply->header(QNetworkRequest::ServerHeader), "test server"); +} + struct CustomType { CustomType() {} CustomType(const QString &) {} diff --git a/tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp b/tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp index 53b00ca..cf2c86b 100644 --- a/tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp +++ b/tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp @@ -45,6 +45,7 @@ private slots: void mimeTypeDetection(); void mimeTypeDetectionFromFile_data(); void mimeTypeDetectionFromFile(); + void headers(); }; void tst_QHttpServerResponse::mimeTypeDetection_data() @@ -132,6 +133,78 @@ void tst_QHttpServerResponse::mimeTypeDetectionFromFile() QCOMPARE(QHttpServerResponse::fromFile(content).mimeType(), mimeType); } +void tst_QHttpServerResponse::headers() +{ + QHttpServerResponse resp(""); + + const QByteArray test1 = QByteArrayLiteral("test1"); + const QByteArray test2 = QByteArrayLiteral("test2"); + const QByteArray zero = QByteArrayLiteral("application/x-zerosize"); + const auto &contentTypeHeader = QHttpServerLiterals::contentTypeHeader(); + const auto &contentLengthHeader = QHttpServerLiterals::contentLengthHeader(); + + QVERIFY(!resp.hasHeader(contentLengthHeader)); + QVERIFY(resp.hasHeader(contentTypeHeader, zero)); + QVERIFY(!resp.hasHeader(contentTypeHeader, test1)); + QVERIFY(!resp.hasHeader(contentTypeHeader, test2)); + + resp.addHeader(contentTypeHeader, test1); + resp.addHeader(contentLengthHeader, test2); + QVERIFY(resp.hasHeader(contentLengthHeader, test2)); + QVERIFY(resp.hasHeader(contentTypeHeader, zero)); + QVERIFY(resp.hasHeader(contentTypeHeader, test1)); + QVERIFY(!resp.hasHeader(contentTypeHeader, test2)); + + const auto &typeHeaders = resp.headers(contentTypeHeader); + QCOMPARE(typeHeaders.size(), 2); + QVERIFY(typeHeaders.contains(zero)); + QVERIFY(typeHeaders.contains(test1)); + + const auto &lengthHeaders = resp.headers(contentLengthHeader); + QCOMPARE(lengthHeaders.size(), 1); + QVERIFY(lengthHeaders.contains(test2)); + + resp.setHeader(contentTypeHeader, test2); + + QVERIFY(resp.hasHeader(contentLengthHeader, test2)); + QVERIFY(!resp.hasHeader(contentTypeHeader, zero)); + QVERIFY(!resp.hasHeader(contentTypeHeader, test1)); + QVERIFY(resp.hasHeader(contentTypeHeader, test2)); + + resp.clearHeader(contentTypeHeader); + + QVERIFY(resp.hasHeader(contentLengthHeader, test2)); + + resp.clearHeader(contentLengthHeader); + + QVERIFY(!resp.hasHeader(contentLengthHeader)); + QVERIFY(!resp.hasHeader(contentTypeHeader)); + + resp.addHeaders({ {contentTypeHeader, zero}, {contentLengthHeader, test1} }); + + QVERIFY(resp.hasHeader(contentTypeHeader, zero)); + QVERIFY(resp.hasHeader(contentLengthHeader, test1)); + + resp.clearHeaders(); + + QVERIFY(!resp.hasHeader(contentLengthHeader)); + QVERIFY(!resp.hasHeader(contentTypeHeader)); + + const QList<QPair<QByteArray, QByteArray>> headers = { + {contentTypeHeader, zero}, {contentLengthHeader, test2} + }; + + resp.addHeaders(headers); + + QVERIFY(resp.hasHeader(contentTypeHeader, zero)); + QVERIFY(resp.hasHeader(contentLengthHeader, test2)); + + resp.clearHeaders(); + + QVERIFY(!resp.hasHeader(contentLengthHeader)); + QVERIFY(!resp.hasHeader(contentTypeHeader)); +} + QT_END_NAMESPACE QTEST_MAIN(tst_QHttpServerResponse) |