summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikhail Svetkin <mikhail.svetkin@gmail.com>2019-07-21 20:52:31 +0200
committerMikhail Svetkin <mikhail.svetkin@gmail.com>2019-10-03 19:40:22 +0200
commit73175545e69cc5f07a7a1447a6b8c4c74d9795c8 (patch)
treec92297b12f8c397e2b0d852894c405519a4b749c
parentb83837076953a22bbe56143da9f626e224501212 (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.cpp5
-rw-r--r--src/httpserver/qhttpserverresponse.cpp225
-rw-r--r--src/httpserver/qhttpserverresponse.h38
-rw-r--r--src/httpserver/qhttpserverresponse_p.h13
-rw-r--r--tests/auto/qhttpserver/tst_qhttpserver.cpp21
-rw-r--r--tests/auto/qhttpserverresponse/tst_qhttpserverresponse.cpp73
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)