summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@qt.io>2024-02-15 08:13:45 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2024-03-05 09:40:08 +0000
commitf413e719840c0b68a357e4db278f6dd52a8a7450 (patch)
treedad0188477504485377cf6fb00d0852dfa8b778f
parent6c82802a95ce440015053ef3302f300f3c3cef31 (diff)
Make HTTP header name a variant / union for performance
This saves memory and can speed up performance with well-known headers. The change consists of: - Change the internal type of 'name' to a std::variant capable of holding either WellKnownHeader-enum, or a QBA. - Accordingly, add an equality operator. - When headers are added (append, insert, replace) then use WellKnownHeader as storage type when possible; either use the function parameter directly if a WellKnownHeader overload was used, or check if the provided string can be converted to a WellKnownHeader. - Convert other functions to use a more performant lookup/comparisons. Fixes: QTBUG-122020 Change-Id: If2452f6edc497547246fb4ddbace384e39c26c5e Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> (cherry picked from commit 0c05d2b43ec5ab29efc3db2718289a5600da754c) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/network/access/qhttpheaders.cpp235
1 files changed, 188 insertions, 47 deletions
diff --git a/src/network/access/qhttpheaders.cpp b/src/network/access/qhttpheaders.cpp
index eeecc3e6da..2f2918f867 100644
--- a/src/network/access/qhttpheaders.cpp
+++ b/src/network/access/qhttpheaders.cpp
@@ -5,6 +5,7 @@
#include <private/qoffsetstringarray_p.h>
+#include <QtCore/qcompare.h>
#include <QtCore/qhash.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmap.h>
@@ -13,6 +14,7 @@
#include <q20algorithm.h>
#include <string_view>
+#include <variant>
QT_BEGIN_NAMESPACE
@@ -683,13 +685,86 @@ static QByteArray normalizedName(QAnyStringView name)
return name.visit([](auto name){ return fieldToByteArray(name); }).toLower();
}
+struct HeaderName
+{
+ explicit HeaderName(QHttpHeaders::WellKnownHeader name) : data(name)
+ {
+ }
+
+ explicit HeaderName(QAnyStringView name)
+ {
+ const auto nname = normalizedName(name);
+ if (auto h = HeaderName::toWellKnownHeader(nname))
+ data = *h;
+ else
+ data = std::move(nname);
+ }
+
+ // Returns an enum corresponding with the 'name' if possible. Uses binary search (O(logN)).
+ // The function doesn't normalize the data; needs to be done by the caller if needed
+ static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QByteArrayView name) noexcept
+ {
+ auto indexesBegin = std::cbegin(orderedHeaderNameIndexes);
+ auto indexesEnd = std::cend(orderedHeaderNameIndexes);
+
+ auto result = std::lower_bound(indexesBegin, indexesEnd, name, ByIndirectHeaderName{});
+
+ if (result != indexesEnd && name == headerNames[*result])
+ return static_cast<QHttpHeaders::WellKnownHeader>(*result);
+ return std::nullopt;
+ }
+
+ QByteArrayView asView() const noexcept
+ {
+ return std::visit([](const auto &arg) -> QByteArrayView {
+ using T = decltype(arg);
+ if constexpr (std::is_same_v<T, const QByteArray &>)
+ return arg;
+ else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>)
+ return headerNames.viewAt(qToUnderlying(arg));
+ else
+ static_assert(QtPrivate::type_dependent_false<T>());
+ }, data);
+ }
+
+ QByteArray asByteArray() const noexcept
+ {
+ return std::visit([](const auto &arg) -> QByteArray {
+ using T = decltype(arg);
+ if constexpr (std::is_same_v<T, const QByteArray &>) {
+ return arg;
+ } else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>) {
+ const auto view = headerNames.viewAt(qToUnderlying(arg));
+ return QByteArray::fromRawData(view.constData(), view.size());
+ } else {
+ static_assert(QtPrivate::type_dependent_false<T>());
+ }
+ }, data);
+ }
+
+private:
+ // Store the data as 'enum' whenever possible; more performant, and comparison relies on that
+ std::variant<QHttpHeaders::WellKnownHeader, QByteArray> data;
+
+ friend bool comparesEqual(const HeaderName &lhs, const HeaderName &rhs) noexcept
+ {
+ // Here we compare two std::variants, which will return false if the types don't match.
+ // That is beneficial here because we avoid unnecessary comparisons; but it also means
+ // we must always store the data as WellKnownHeader when possible (in other words, if
+ // we get a string that is mappable to a WellKnownHeader). To guard against accidental
+ // misuse, the 'data' is private and the constructors must be used.
+ return lhs.data == rhs.data;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(HeaderName)
+};
+
// A clarification on case-sensitivity:
// - Header *names* are case-insensitive; Content-Type and content-type are considered equal
// - Header *values* are case-sensitive
// (In addition, the HTTP/2 and HTTP/3 standards mandate that all headers must be lower-cased when
// encoded into transmission)
struct Header {
- QByteArray name;
+ HeaderName name;
QByteArray value;
private:
@@ -699,11 +774,21 @@ private:
}
};
+auto headerNameMatches(const HeaderName &name)
+{
+ return [&name](const Header &header) { return header.name == name; };
+}
+
class QHttpHeadersPrivate : public QSharedData
{
public:
QHttpHeadersPrivate() = default;
+ // The 'Self' is supplied as parameter to static functions so that
+ // we can define common methods which 'detach()' the private itself.
+ using Self = QExplicitlySharedDataPointer<QHttpHeadersPrivate>;
+ static void removeAll(Self &d, const HeaderName &name);
+
QList<Header> headers;
};
@@ -718,6 +803,21 @@ template <> void QExplicitlySharedDataPointer<QHttpHeadersPrivate>::detach()
}
}
+void QHttpHeadersPrivate::removeAll(Self &d, const HeaderName &name)
+{
+ const auto it = std::find_if(d->headers.cbegin(), d->headers.cend(), headerNameMatches(name));
+
+ if (it != d->headers.cend()) {
+ // Found something to remove, calculate offset so we can proceed from the match-location
+ const auto matchOffset = it - d->headers.cbegin();
+ d.detach();
+ // Rearrange all matches to the end and erase them
+ d->headers.erase(std::remove_if(d->headers.begin() + matchOffset, d->headers.end(),
+ headerNameMatches(name)),
+ d->headers.end());
+ }
+}
+
/*!
Creates a new QHttpHeaders object.
*/
@@ -827,7 +927,7 @@ QDebug operator<<(QDebug debug, const QHttpHeaders &headers)
debug << "headers = ";
const char *separator = "";
for (const auto &h : headers.d->headers) {
- debug << separator << h.name << ':' << h.value;
+ debug << separator << h.name.asView() << ':' << h.value;
separator = " | ";
}
}
@@ -985,11 +1085,6 @@ static QByteArray normalizedValue(QAnyStringView value)
return value.visit([](auto value){ return fieldToByteArray(value); }).trimmed();
}
-static bool headerNameIs(const Header &header, QAnyStringView name)
-{
- return header.name == normalizedName(name);
-}
-
/*!
Appends a header entry with \a name and \a value and returns \c true
if successful.
@@ -1003,7 +1098,7 @@ bool QHttpHeaders::append(QAnyStringView name, QAnyStringView value)
return false;
d.detach();
- d->headers.push_back({normalizedName(name), normalizedValue(value)});
+ d->headers.push_back({HeaderName{name}, normalizedValue(value)});
return true;
}
@@ -1016,7 +1111,7 @@ bool QHttpHeaders::append(WellKnownHeader name, QAnyStringView value)
return false;
d.detach();
- d->headers.push_back({headerNames[qToUnderlying(name)], normalizedValue(value)});
+ d->headers.push_back({HeaderName{name}, normalizedValue(value)});
return true;
}
@@ -1035,7 +1130,7 @@ bool QHttpHeaders::insert(qsizetype i, QAnyStringView name, QAnyStringView value
return false;
d.detach();
- d->headers.insert(i, {normalizedName(name), normalizedValue(value)});
+ d->headers.insert(i, {HeaderName{name}, normalizedValue(value)});
return true;
}
@@ -1049,7 +1144,7 @@ bool QHttpHeaders::insert(qsizetype i, WellKnownHeader name, QAnyStringView valu
return false;
d.detach();
- d->headers.insert(i, {headerNames[qToUnderlying(name)], normalizedValue(value)});
+ d->headers.insert(i, {HeaderName{name}, normalizedValue(value)});
return true;
}
@@ -1069,7 +1164,7 @@ bool QHttpHeaders::replace(qsizetype i, QAnyStringView name, QAnyStringView newV
return false;
d.detach();
- d->headers.replace(i, {normalizedName(name), normalizedValue(newValue)});
+ d->headers.replace(i, {HeaderName{name}, normalizedValue(newValue)});
return true;
}
@@ -1083,7 +1178,7 @@ bool QHttpHeaders::replace(qsizetype i, WellKnownHeader name, QAnyStringView new
return false;
d.detach();
- d->headers.replace(i, {headerNames[qToUnderlying(name)], normalizedValue(newValue)});
+ d->headers.replace(i, {HeaderName{name}, normalizedValue(newValue)});
return true;
}
@@ -1094,10 +1189,10 @@ bool QHttpHeaders::replace(qsizetype i, WellKnownHeader name, QAnyStringView new
*/
bool QHttpHeaders::contains(QAnyStringView name) const
{
- if (!d)
+ if (isEmpty())
return false;
- return std::any_of(d->headers.cbegin(), d->headers.cend(),
- [&name](const Header &header) { return headerNameIs(header, name); });
+
+ return std::any_of(d->headers.cbegin(), d->headers.cend(), headerNameMatches(HeaderName{name}));
}
/*!
@@ -1105,7 +1200,10 @@ bool QHttpHeaders::contains(QAnyStringView name) const
*/
bool QHttpHeaders::contains(WellKnownHeader name) const
{
- return contains(headerNames[qToUnderlying(name)]);
+ if (isEmpty())
+ return false;
+
+ return std::any_of(d->headers.cbegin(), d->headers.cend(), headerNameMatches(HeaderName{name}));
}
/*!
@@ -1115,12 +1213,10 @@ bool QHttpHeaders::contains(WellKnownHeader name) const
*/
void QHttpHeaders::removeAll(QAnyStringView name)
{
- if (contains(name)) {
- d.detach();
- d->headers.removeIf([&name](const Header &header){
- return headerNameIs(header, name);
- });
- }
+ if (isEmpty())
+ return;
+
+ return QHttpHeadersPrivate::removeAll(d, HeaderName(name));
}
/*!
@@ -1128,7 +1224,10 @@ void QHttpHeaders::removeAll(QAnyStringView name)
*/
void QHttpHeaders::removeAll(WellKnownHeader name)
{
- removeAll(headerNames[qToUnderlying(name)]);
+ if (isEmpty())
+ return;
+
+ return QHttpHeadersPrivate::removeAll(d, HeaderName(name));
}
/*!
@@ -1153,10 +1252,13 @@ void QHttpHeaders::removeAt(qsizetype i)
*/
QByteArrayView QHttpHeaders::value(QAnyStringView name, QByteArrayView defaultValue) const noexcept
{
- if (!d)
+ if (isEmpty())
return defaultValue;
+
+ const HeaderName hname(name);
+
for (const auto &h : std::as_const(d->headers)) {
- if (headerNameIs(h, name))
+ if (h.name == hname)
return h.value;
}
return defaultValue;
@@ -1167,7 +1269,16 @@ QByteArrayView QHttpHeaders::value(QAnyStringView name, QByteArrayView defaultVa
*/
QByteArrayView QHttpHeaders::value(WellKnownHeader name, QByteArrayView defaultValue) const noexcept
{
- return value(headerNames[qToUnderlying(name)], defaultValue);
+ if (isEmpty())
+ return defaultValue;
+
+ const HeaderName hname(name);
+
+ for (const auto &h : std::as_const(d->headers)) {
+ if (h.name == hname)
+ return h.value;
+ }
+ return defaultValue;
}
/*!
@@ -1178,14 +1289,17 @@ QByteArrayView QHttpHeaders::value(WellKnownHeader name, QByteArrayView defaultV
*/
QList<QByteArray> QHttpHeaders::values(QAnyStringView name) const
{
- QList<QByteArray> values;
- if (!d)
- return values;
+ QList<QByteArray> result;
+ if (isEmpty())
+ return result;
+
+ const HeaderName hname(name);
+
for (const auto &h : std::as_const(d->headers)) {
- if (headerNameIs(h, name))
- values.append(h.value);
+ if (h.name == hname)
+ result.append(h.value);
}
- return values;
+ return result;
}
/*!
@@ -1193,7 +1307,17 @@ QList<QByteArray> QHttpHeaders::values(QAnyStringView name) const
*/
QList<QByteArray> QHttpHeaders::values(WellKnownHeader name) const
{
- return values(headerNames[qToUnderlying(name)]);
+ QList<QByteArray> values;
+ if (isEmpty())
+ return values;
+
+ const HeaderName hname(name);
+
+ for (const auto &h : std::as_const(d->headers)) {
+ if (h.name == hname)
+ values.append(h.value);
+ }
+ return values;
}
/*!
@@ -1219,7 +1343,7 @@ QByteArrayView QHttpHeaders::valueAt(qsizetype i) const noexcept
QLatin1StringView QHttpHeaders::nameAt(qsizetype i) const noexcept
{
verify(i);
- return QLatin1StringView{d->headers.at(i).name};
+ return QLatin1StringView{d->headers.at(i).name.asView()};
}
/*!
@@ -1237,13 +1361,17 @@ QLatin1StringView QHttpHeaders::nameAt(qsizetype i) const noexcept
QByteArray QHttpHeaders::combinedValue(QAnyStringView name) const
{
QByteArray result;
+ if (isEmpty())
+ return result;
+ const HeaderName hname(name);
const char* separator = "";
- auto valueList = values(name);
- for (const auto &v : valueList) {
- result.append(separator);
- result.append(v);
- separator = ", ";
+ for (const auto &h : std::as_const(d->headers)) {
+ if (h.name == hname) {
+ result.append(separator);
+ result.append(h.value);
+ separator = ", ";
+ }
}
return result;
}
@@ -1253,7 +1381,20 @@ QByteArray QHttpHeaders::combinedValue(QAnyStringView name) const
*/
QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const
{
- return combinedValue(headerNames[qToUnderlying(name)]);
+ QByteArray result;
+ if (isEmpty())
+ return result;
+
+ const HeaderName hname(name);
+ const char* separator = "";
+ for (const auto &h : std::as_const(d->headers)) {
+ if (h.name == hname) {
+ result.append(separator);
+ result.append(h.value);
+ separator = ", ";
+ }
+ }
+ return result;
}
/*!
@@ -1302,11 +1443,11 @@ QByteArrayView QHttpHeaders::wellKnownHeaderName(WellKnownHeader name) noexcept
QList<std::pair<QByteArray, QByteArray>> QHttpHeaders::toListOfPairs() const
{
QList<std::pair<QByteArray, QByteArray>> list;
- if (!d)
+ if (isEmpty())
return list;
list.reserve(size());
for (const auto & h : std::as_const(d->headers))
- list.append({h.name, h.value});
+ list.append({h.name.asByteArray(), h.value});
return list;
}
@@ -1317,10 +1458,10 @@ QList<std::pair<QByteArray, QByteArray>> QHttpHeaders::toListOfPairs() const
QMultiMap<QByteArray, QByteArray> QHttpHeaders::toMultiMap() const
{
QMultiMap<QByteArray, QByteArray> map;
- if (!d)
+ if (isEmpty())
return map;
for (const auto &h : std::as_const(d->headers))
- map.insert(h.name, h.value);
+ map.insert(h.name.asByteArray(), h.value);
return map;
}
@@ -1331,11 +1472,11 @@ QMultiMap<QByteArray, QByteArray> QHttpHeaders::toMultiMap() const
QMultiHash<QByteArray, QByteArray> QHttpHeaders::toMultiHash() const
{
QMultiHash<QByteArray, QByteArray> hash;
- if (!d)
+ if (isEmpty())
return hash;
hash.reserve(size());
for (const auto &h : std::as_const(d->headers))
- hash.insert(h.name, h.value);
+ hash.insert(h.name.asByteArray(), h.value);
return hash;
}