summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>2016-08-22 11:30:00 +0200
committerOswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>2016-08-22 11:30:01 +0200
commitd314819fc02139e05e16c56657898c704f7fb48f (patch)
treea61ba968233634948401c8339f9613844de1c2b5 /src/network/access
parent9f888d2fde9c5413e5519e0914e9b13638760985 (diff)
parente0e9e196a72ffe5457034894eaaadc90ed0d34ef (diff)
Merge dev into 5.8
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/http2/http2frames.cpp444
-rw-r--r--src/network/access/http2/http2frames_p.h101
-rw-r--r--src/network/access/http2/http2streams.cpp4
-rw-r--r--src/network/access/http2/http2streams_p.h3
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp263
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h13
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp13
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp68
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h7
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp5
-rw-r--r--src/network/access/qnetworkrequest.cpp8
11 files changed, 461 insertions, 468 deletions
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
index 55e9f93b19..978bee09b1 100644
--- a/src/network/access/http2/http2frames.cpp
+++ b/src/network/access/http2/http2frames.cpp
@@ -51,46 +51,125 @@ namespace Http2
// HTTP/2 frames are defined by RFC7540, clauses 4 and 6.
-FrameStatus validate_frame_header(FrameType type, FrameFlags flags, quint32 payloadSize)
+Frame::Frame()
+ : buffer(frameHeaderSize)
{
+}
+
+FrameType Frame::type() const
+{
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
+
+ if (int(buffer[3]) >= int(FrameType::LAST_FRAME_TYPE))
+ return FrameType::LAST_FRAME_TYPE;
+
+ return FrameType(buffer[3]);
+}
+
+quint32 Frame::streamID() const
+{
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
+ return qFromBigEndian<quint32>(&buffer[5]);
+}
+
+FrameFlags Frame::flags() const
+{
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
+ return FrameFlags(buffer[4]);
+}
+
+quint32 Frame::payloadSize() const
+{
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
+ return buffer[0] << 16 | buffer[1] << 8 | buffer[2];
+}
+
+uchar Frame::padding() const
+{
+ Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
+
+ if (!flags().testFlag(FrameFlag::PADDED))
+ return 0;
+
+ switch (type()) {
+ case FrameType::DATA:
+ case FrameType::PUSH_PROMISE:
+ case FrameType::HEADERS:
+ Q_ASSERT(buffer.size() > frameHeaderSize);
+ return buffer[frameHeaderSize];
+ default:
+ return 0;
+ }
+}
+
+bool Frame::priority(quint32 *streamID, uchar *weight) const
+{
+ Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
+
+ if (buffer.size() <= frameHeaderSize)
+ return false;
+
+ const uchar *src = &buffer[0] + frameHeaderSize;
+ if (type() == FrameType::HEADERS && flags().testFlag(FrameFlag::PADDED))
+ ++src;
+
+ if ((type() == FrameType::HEADERS && flags().testFlag(FrameFlag::PRIORITY))
+ || type() == FrameType::PRIORITY) {
+ if (streamID)
+ *streamID = qFromBigEndian<quint32>(src);
+ if (weight)
+ *weight = src[4];
+ return true;
+ }
+
+ return false;
+}
+
+FrameStatus Frame::validateHeader() const
+{
+ // Should be called only on a frame with
+ // a complete header.
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
+
+ const auto framePayloadSize = payloadSize();
// 4.2 Frame Size
- if (payloadSize > maxPayloadSize)
+ if (framePayloadSize > maxPayloadSize)
return FrameStatus::sizeError;
- switch (type) {
+ switch (type()) {
case FrameType::SETTINGS:
// SETTINGS ACK can not have any payload.
// The payload of a SETTINGS frame consists of zero
// or more parameters, each consisting of an unsigned
// 16-bit setting identifier and an unsigned 32-bit value.
// Thus the payload size must be a multiple of 6.
- if (flags.testFlag(FrameFlag::ACK) ? payloadSize : payloadSize % 6)
+ if (flags().testFlag(FrameFlag::ACK) ? framePayloadSize : framePayloadSize % 6)
return FrameStatus::sizeError;
break;
case FrameType::PRIORITY:
// 6.3 PRIORITY
- if (payloadSize != 5)
+ if (framePayloadSize != 5)
return FrameStatus::sizeError;
break;
case FrameType::PING:
// 6.7 PING
- if (payloadSize != 8)
+ if (framePayloadSize != 8)
return FrameStatus::sizeError;
break;
case FrameType::GOAWAY:
// 6.8 GOAWAY
- if (payloadSize < 8)
+ if (framePayloadSize < 8)
return FrameStatus::sizeError;
break;
case FrameType::RST_STREAM:
case FrameType::WINDOW_UPDATE:
// 6.4 RST_STREAM, 6.9 WINDOW_UPDATE
- if (payloadSize != 4)
+ if (framePayloadSize != 4)
return FrameStatus::sizeError;
break;
case FrameType::PUSH_PROMISE:
// 6.6 PUSH_PROMISE
- if (payloadSize < 4)
+ if (framePayloadSize < 4)
return FrameStatus::sizeError;
default:
// DATA/HEADERS/CONTINUATION will be verified
@@ -102,31 +181,37 @@ FrameStatus validate_frame_header(FrameType type, FrameFlags flags, quint32 payl
return FrameStatus::goodFrame;
}
-FrameStatus validate_frame_payload(FrameType type, FrameFlags flags,
- quint32 size, const uchar *src)
+FrameStatus Frame::validatePayload() const
{
- Q_ASSERT(!size || src);
+ // Should be called only on a complete frame with a valid header.
+ Q_ASSERT(validateHeader() == FrameStatus::goodFrame);
// Ignored, 5.1
- if (type == FrameType::LAST_FRAME_TYPE)
+ if (type() == FrameType::LAST_FRAME_TYPE)
return FrameStatus::goodFrame;
+ auto size = payloadSize();
+ Q_ASSERT(buffer.size() >= frameHeaderSize && size == buffer.size() - frameHeaderSize);
+
+ const uchar *src = size ? &buffer[0] + frameHeaderSize : nullptr;
+ const auto frameFlags = flags();
+ switch (type()) {
// 6.1 DATA, 6.2 HEADERS
- if (type == FrameType::DATA || type == FrameType::HEADERS) {
- if (flags.testFlag(FrameFlag::PADDED)) {
+ case FrameType::DATA:
+ case FrameType::HEADERS:
+ if (frameFlags.testFlag(FrameFlag::PADDED)) {
if (!size || size < src[0])
return FrameStatus::sizeError;
size -= src[0];
}
- if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY)) {
+ if (type() == FrameType::HEADERS && frameFlags.testFlag(FrameFlag::PRIORITY)) {
if (size < 5)
return FrameStatus::sizeError;
}
- }
-
+ break;
// 6.6 PUSH_PROMISE
- if (type == FrameType::PUSH_PROMISE) {
- if (flags.testFlag(FrameFlag::PADDED)) {
+ case FrameType::PUSH_PROMISE:
+ if (frameFlags.testFlag(FrameFlag::PADDED)) {
if (!size || size < src[0])
return FrameStatus::sizeError;
size -= src[0];
@@ -134,284 +219,158 @@ FrameStatus validate_frame_payload(FrameType type, FrameFlags flags,
if (size < 4)
return FrameStatus::sizeError;
+ break;
+ default:
+ break;
}
return FrameStatus::goodFrame;
}
-FrameStatus validate_frame_payload(FrameType type, FrameFlags flags,
- const std::vector<uchar> &payload)
-{
- const uchar *src = payload.size() ? &payload[0] : nullptr;
- return validate_frame_payload(type, flags, quint32(payload.size()), src);
-}
-
-FrameReader::FrameReader(FrameReader &&rhs)
- : framePayload(std::move(rhs.framePayload))
+quint32 Frame::dataSize() const
{
- type = rhs.type;
- rhs.type = FrameType::LAST_FRAME_TYPE;
-
- flags = rhs.flags;
- rhs.flags = FrameFlag::EMPTY;
-
- streamID = rhs.streamID;
- rhs.streamID = 0;
+ Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
- payloadSize = rhs.payloadSize;
- rhs.payloadSize = 0;
+ quint32 size = payloadSize();
+ if (const uchar pad = padding()) {
+ // + 1 one for a byte with padding number itself:
+ size -= pad + 1;
+ }
- incompleteRead = rhs.incompleteRead;
- rhs.incompleteRead = false;
+ if (priority())
+ size -= 5;
- offset = rhs.offset;
- rhs.offset = 0;
+ return size;
}
-FrameReader &FrameReader::operator = (FrameReader &&rhs)
+const uchar *Frame::dataBegin() const
{
- framePayload = std::move(rhs.framePayload);
-
- type = rhs.type;
- rhs.type = FrameType::LAST_FRAME_TYPE;
-
- flags = rhs.flags;
- rhs.flags = FrameFlag::EMPTY;
-
- streamID = rhs.streamID;
- rhs.streamID = 0;
-
- payloadSize = rhs.payloadSize;
- rhs.payloadSize = 0;
+ Q_ASSERT(validatePayload() == FrameStatus::goodFrame);
+ if (buffer.size() <= frameHeaderSize)
+ return nullptr;
- incompleteRead = rhs.incompleteRead;
- rhs.incompleteRead = false;
+ const uchar *src = &buffer[0] + frameHeaderSize;
+ if (padding())
+ ++src;
- offset = rhs.offset;
- rhs.offset = 0;
+ if (priority())
+ src += 5;
- return *this;
+ return src;
}
FrameStatus FrameReader::read(QAbstractSocket &socket)
{
- if (!incompleteRead) {
+ if (offset < frameHeaderSize) {
if (!readHeader(socket))
return FrameStatus::incompleteFrame;
- const auto status = validate_frame_header(type, flags, payloadSize);
+ const auto status = frame.validateHeader();
if (status != FrameStatus::goodFrame) {
// No need to read any payload.
return status;
}
- if (Http2PredefinedParameters::maxFrameSize < payloadSize)
+ if (Http2PredefinedParameters::maxFrameSize < frame.payloadSize())
return FrameStatus::sizeError;
- framePayload.resize(payloadSize);
- offset = 0;
- }
-
- if (framePayload.size()) {
- if (!readPayload(socket))
- return FrameStatus::incompleteFrame;
- }
-
- return validate_frame_payload(type, flags, framePayload);
-}
-
-bool FrameReader::padded(uchar *pad) const
-{
- Q_ASSERT(pad);
-
- if (!flags.testFlag(FrameFlag::PADDED))
- return false;
-
- if (type == FrameType::DATA
- || type == FrameType::PUSH_PROMISE
- || type == FrameType::HEADERS) {
- Q_ASSERT(framePayload.size() >= 1);
- *pad = framePayload[0];
- return true;
- }
-
- return false;
-}
-
-bool FrameReader::priority(quint32 *streamID, uchar *weight) const
-{
- Q_ASSERT(streamID);
- Q_ASSERT(weight);
-
- if (!framePayload.size())
- return false;
-
- const uchar *src = &framePayload[0];
- if (type == FrameType::HEADERS && flags.testFlag(FrameFlag::PADDED))
- ++src;
-
- if ((type == FrameType::HEADERS && flags.testFlag(FrameFlag::PRIORITY))
- || type == FrameType::PRIORITY) {
- *streamID = qFromBigEndian<quint32>(src);
- *weight = src[4];
- return true;
+ frame.buffer.resize(frame.payloadSize() + frameHeaderSize);
}
- return false;
-}
+ if (offset < frame.buffer.size() && !readPayload(socket))
+ return FrameStatus::incompleteFrame;
-quint32 FrameReader::dataSize() const
-{
- quint32 size = quint32(framePayload.size());
- uchar pad = 0;
- if (padded(&pad)) {
- // + 1 one for a byte with padding number itself:
- size -= pad + 1;
- }
-
- quint32 dummyID = 0;
- uchar dummyW = 0;
- if (priority(&dummyID, &dummyW))
- size -= 5;
+ // Reset the offset, our frame can be re-used
+ // now (re-read):
+ offset = 0;
- return size;
-}
-
-const uchar *FrameReader::dataBegin() const
-{
- if (!framePayload.size())
- return nullptr;
-
- const uchar *src = &framePayload[0];
- uchar dummyPad = 0;
- if (padded(&dummyPad))
- ++src;
-
- quint32 dummyID = 0;
- uchar dummyW = 0;
- if (priority(&dummyID, &dummyW))
- src += 5;
-
- return src;
+ return frame.validatePayload();
}
bool FrameReader::readHeader(QAbstractSocket &socket)
{
- if (socket.bytesAvailable() < frameHeaderSize)
- return false;
-
- uchar src[frameHeaderSize] = {};
- socket.read(reinterpret_cast<char*>(src), frameHeaderSize);
-
- payloadSize = src[0] << 16 | src[1] << 8 | src[2];
+ Q_ASSERT(offset < frameHeaderSize);
- type = FrameType(src[3]);
- if (int(type) >= int(FrameType::LAST_FRAME_TYPE))
- type = FrameType::LAST_FRAME_TYPE; // To be ignored, 5.1
+ auto &buffer = frame.buffer;
+ if (buffer.size() < frameHeaderSize)
+ buffer.resize(frameHeaderSize);
- flags = FrameFlags(src[4]);
- streamID = qFromBigEndian<quint32>(src + 5);
+ const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
+ frameHeaderSize - offset);
+ if (chunkSize > 0)
+ offset += chunkSize;
- return true;
+ return offset == frameHeaderSize;
}
bool FrameReader::readPayload(QAbstractSocket &socket)
{
- Q_ASSERT(offset <= framePayload.size());
+ Q_ASSERT(offset < frame.buffer.size());
+ Q_ASSERT(frame.buffer.size() > frameHeaderSize);
+ auto &buffer = frame.buffer;
// Casts and ugliness - to deal with MSVC. Values are guaranteed to fit into quint32.
- if (const auto residue = std::min(qint64(framePayload.size() - offset), socket.bytesAvailable())) {
- socket.read(reinterpret_cast<char *>(&framePayload[offset]), residue);
- offset += quint32(residue);
- }
+ const auto chunkSize = socket.read(reinterpret_cast<char *>(&buffer[offset]),
+ qint64(buffer.size() - offset));
+ if (chunkSize > 0)
+ offset += quint32(chunkSize);
- incompleteRead = offset < framePayload.size();
- return !incompleteRead;
+ return offset == buffer.size();
}
-
FrameWriter::FrameWriter()
{
- frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize +
- Http2PredefinedParameters::frameHeaderSize);
}
FrameWriter::FrameWriter(FrameType type, FrameFlags flags, quint32 streamID)
{
- frameBuffer.reserve(Http2PredefinedParameters::maxFrameSize +
- Http2PredefinedParameters::frameHeaderSize);
start(type, flags, streamID);
}
void FrameWriter::start(FrameType type, FrameFlags flags, quint32 streamID)
{
- frameBuffer.resize(frameHeaderSize);
+ auto &buffer = frame.buffer;
+
+ buffer.resize(frameHeaderSize);
// The first three bytes - payload size, which is 0 for now.
- frameBuffer[0] = 0;
- frameBuffer[1] = 0;
- frameBuffer[2] = 0;
+ buffer[0] = 0;
+ buffer[1] = 0;
+ buffer[2] = 0;
- frameBuffer[3] = uchar(type);
- frameBuffer[4] = uchar(flags);
+ buffer[3] = uchar(type);
+ buffer[4] = uchar(flags);
- qToBigEndian(streamID, &frameBuffer[5]);
+ qToBigEndian(streamID, &buffer[5]);
}
void FrameWriter::setPayloadSize(quint32 size)
{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ auto &buffer = frame.buffer;
+
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
Q_ASSERT(size < maxPayloadSize);
- frameBuffer[0] = size >> 16;
- frameBuffer[1] = size >> 8;
- frameBuffer[2] = size;
-}
-
-quint32 FrameWriter::payloadSize() const
-{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
- return frameBuffer[0] << 16 | frameBuffer[1] << 8 | frameBuffer[2];
+ buffer[0] = size >> 16;
+ buffer[1] = size >> 8;
+ buffer[2] = size;
}
void FrameWriter::setType(FrameType type)
{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
- frameBuffer[3] = uchar(type);
-}
-
-FrameType FrameWriter::type() const
-{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
- return FrameType(frameBuffer[3]);
+ Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
+ frame.buffer[3] = uchar(type);
}
void FrameWriter::setFlags(FrameFlags flags)
{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
- frameBuffer[4] = uchar(flags);
+ Q_ASSERT(frame.buffer.size() >= frameHeaderSize);
+ frame.buffer[4] = uchar(flags);
}
void FrameWriter::addFlag(FrameFlag flag)
{
- setFlags(flags() | flag);
-}
-
-FrameFlags FrameWriter::flags() const
-{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
- return FrameFlags(frameBuffer[4]);
-}
-
-quint32 FrameWriter::streamID() const
-{
- return qFromBigEndian<quint32>(&frameBuffer[5]);
-}
-
-void FrameWriter::append(uchar val)
-{
- frameBuffer.push_back(val);
- updatePayloadSize();
+ setFlags(frame.flags() | flag);
}
void FrameWriter::append(const uchar *begin, const uchar *end)
@@ -419,64 +378,71 @@ void FrameWriter::append(const uchar *begin, const uchar *end)
Q_ASSERT(begin && end);
Q_ASSERT(begin < end);
- frameBuffer.insert(frameBuffer.end(), begin, end);
+ frame.buffer.insert(frame.buffer.end(), begin, end);
updatePayloadSize();
}
void FrameWriter::updatePayloadSize()
{
- // First, compute size:
- const quint32 payloadSize = quint32(frameBuffer.size() - frameHeaderSize);
- Q_ASSERT(payloadSize <= maxPayloadSize);
- setPayloadSize(payloadSize);
+ const quint32 size = quint32(frame.buffer.size() - frameHeaderSize);
+ Q_ASSERT(size <= maxPayloadSize);
+ setPayloadSize(size);
}
bool FrameWriter::write(QAbstractSocket &socket) const
{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ auto &buffer = frame.buffer;
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
// Do some sanity check first:
- Q_ASSERT(int(type()) < int(FrameType::LAST_FRAME_TYPE));
- Q_ASSERT(validate_frame_header(type(), flags(), payloadSize()) == FrameStatus::goodFrame);
- const auto nWritten = socket.write(reinterpret_cast<const char *>(&frameBuffer[0]),
- frameBuffer.size());
- return nWritten != -1 && size_type(nWritten) == frameBuffer.size();
+ Q_ASSERT(int(frame.type()) < int(FrameType::LAST_FRAME_TYPE));
+ Q_ASSERT(frame.validateHeader() == FrameStatus::goodFrame);
+
+ const auto nWritten = socket.write(reinterpret_cast<const char *>(&buffer[0]),
+ buffer.size());
+ return nWritten != -1 && size_type(nWritten) == buffer.size();
}
bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit)
{
- Q_ASSERT(frameBuffer.size() >= frameHeaderSize);
+ auto &buffer = frame.buffer;
+ Q_ASSERT(buffer.size() >= frameHeaderSize);
if (sizeLimit > quint32(maxPayloadSize))
sizeLimit = quint32(maxPayloadSize);
- if (quint32(frameBuffer.size() - frameHeaderSize) <= sizeLimit) {
+ if (quint32(buffer.size() - frameHeaderSize) <= sizeLimit) {
+ addFlag(FrameFlag::END_HEADERS);
updatePayloadSize();
return write(socket);
}
+ // Our HPACK block does not fit into the size limit, remove
+ // END_HEADERS bit from the first frame, we'll later set
+ // it on the last CONTINUATION frame:
+ setFlags(frame.flags() & ~FrameFlags(FrameFlag::END_HEADERS));
// Write a frame's header (not controlled by sizeLimit) and
// as many bytes of payload as we can within sizeLimit,
// then send CONTINUATION frames, as needed.
setPayloadSize(sizeLimit);
const quint32 firstChunkSize = frameHeaderSize + sizeLimit;
- qint64 written = socket.write(reinterpret_cast<const char *>(&frameBuffer[0]),
+ qint64 written = socket.write(reinterpret_cast<const char *>(&buffer[0]),
firstChunkSize);
if (written != qint64(firstChunkSize))
return false;
- FrameWriter continuationFrame(FrameType::CONTINUATION, FrameFlag::EMPTY, streamID());
+ FrameWriter continuationWriter(FrameType::CONTINUATION, FrameFlag::EMPTY, frame.streamID());
quint32 offset = firstChunkSize;
- while (offset != frameBuffer.size()) {
- const auto chunkSize = std::min(sizeLimit, quint32(frameBuffer.size() - offset));
- if (chunkSize + offset == frameBuffer.size())
- continuationFrame.addFlag(FrameFlag::END_HEADERS);
- continuationFrame.setPayloadSize(chunkSize);
- if (!continuationFrame.write(socket))
+ while (offset != buffer.size()) {
+ const auto chunkSize = std::min(sizeLimit, quint32(buffer.size() - offset));
+ if (chunkSize + offset == buffer.size())
+ continuationWriter.addFlag(FrameFlag::END_HEADERS);
+ continuationWriter.setPayloadSize(chunkSize);
+ if (!continuationWriter.write(socket))
return false;
- written = socket.write(reinterpret_cast<const char *>(&frameBuffer[offset]),
+ written = socket.write(reinterpret_cast<const char *>(&buffer[offset]),
chunkSize);
if (written != qint64(chunkSize))
return false;
@@ -524,6 +490,6 @@ bool FrameWriter::writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
return true;
}
-}
+} // Namespace Http2
QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h
index 6abed315ca..84ba9c3662 100644
--- a/src/network/access/http2/http2frames_p.h
+++ b/src/network/access/http2/http2frames_p.h
@@ -68,52 +68,53 @@ class QAbstractSocket;
namespace Http2
{
-class Q_AUTOTEST_EXPORT FrameReader
+struct Q_AUTOTEST_EXPORT Frame
{
- friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler);
-
-public:
- FrameReader() = default;
-
- FrameReader(const FrameReader &) = default;
- FrameReader(FrameReader &&rhs);
-
- FrameReader &operator = (const FrameReader &) = default;
- FrameReader &operator = (FrameReader &&rhs);
-
- FrameStatus read(QAbstractSocket &socket);
+ Frame();
+ // Reading these values without first forming a valid frame
+ // (either reading it from a socket or building it) will result
+ // in undefined behavior:
+ FrameType type() const;
+ quint32 streamID() const;
+ FrameFlags flags() const;
+ quint32 payloadSize() const;
+ uchar padding() const;
+ // In HTTP/2 a stream's priority is specified by its weight
+ // and a stream (id) it depends on:
+ bool priority(quint32 *streamID = nullptr,
+ uchar *weight = nullptr) const;
- bool padded(uchar *pad) const;
- bool priority(quint32 *streamID, uchar *weight) const;
+ FrameStatus validateHeader() const;
+ FrameStatus validatePayload() const;
- // N of bytes without padding and/or priority
+ // Number of payload bytes without padding and/or priority
quint32 dataSize() const;
// Beginning of payload without priority/padding
// bytes.
const uchar *dataBegin() const;
- FrameType type = FrameType::LAST_FRAME_TYPE;
- FrameFlags flags = FrameFlag::EMPTY;
- quint32 streamID = 0;
- quint32 payloadSize = 0;
+ std::vector<uchar> buffer;
+};
+class Q_AUTOTEST_EXPORT FrameReader
+{
+public:
+ FrameStatus read(QAbstractSocket &socket);
+
+ Frame &inboundFrame()
+ {
+ return frame;
+ }
private:
bool readHeader(QAbstractSocket &socket);
bool readPayload(QAbstractSocket &socket);
- // As soon as we got a header, we
- // know payload size, offset is
- // needed if we do not have enough
- // data and will read the next chunk.
- bool incompleteRead = false;
quint32 offset = 0;
- std::vector<uchar> framePayload;
+ Frame frame;
};
class Q_AUTOTEST_EXPORT FrameWriter
{
- friend class QT_PREPEND_NAMESPACE(QHttp2ProtocolHandler);
-
public:
using payload_type = std::vector<uchar>;
using size_type = payload_type::size_type;
@@ -121,19 +122,17 @@ public:
FrameWriter();
FrameWriter(FrameType type, FrameFlags flags, quint32 streamID);
- void start(FrameType type, FrameFlags flags, quint32 streamID);
+ Frame &outboundFrame()
+ {
+ return frame;
+ }
+ // Frame 'builders':
+ void start(FrameType type, FrameFlags flags, quint32 streamID);
void setPayloadSize(quint32 size);
- quint32 payloadSize() const;
-
void setType(FrameType type);
- FrameType type() const;
-
void setFlags(FrameFlags flags);
void addFlag(FrameFlag flag);
- FrameFlags flags() const;
-
- quint32 streamID() const;
// All append functions also update frame's payload
// length.
@@ -144,7 +143,11 @@ public:
qToBigEndian(val, wired);
append(wired, wired + sizeof val);
}
- void append(uchar val);
+ void append(uchar val)
+ {
+ frame.buffer.push_back(val);
+ updatePayloadSize();
+ }
void append(Settings identifier)
{
append(quint16(identifier));
@@ -156,25 +159,23 @@ public:
void append(const uchar *begin, const uchar *end);
- // Write 'frameBuffer' as a single frame:
+ // Write as a single frame:
bool write(QAbstractSocket &socket) const;
- // Write as a single frame if we can, or write headers and
- // CONTINUATION(s) frame(s).
+ // Two types of frames we are sending are affected by
+ // frame size limits: HEADERS and DATA. HEADERS' payload
+ // (hpacked HTTP headers, following a frame header)
+ // is always in our 'buffer', we send the initial HEADERS
+ // frame first and then CONTINUTATION frame(s) if needed:
bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit);
- // Write either a single DATA frame or several DATA frames
- // depending on 'sizeLimit'. Data itself is 'external' to
- // FrameWriter, since it's a 'readPointer' from QNonContiguousData.
+ // With DATA frames the actual payload is never in our 'buffer',
+ // it's a 'readPointer' from QNonContiguousData. We split
+ // this payload as needed into DATA frames with correct
+ // payload size fitting into frame size limit:
bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
const uchar *src, quint32 size);
-
- std::vector<uchar> &rawFrameBuffer()
- {
- return frameBuffer;
- }
-
private:
void updatePayloadSize();
- std::vector<uchar> frameBuffer;
+ Frame frame;
};
}
diff --git a/src/network/access/http2/http2streams.cpp b/src/network/access/http2/http2streams.cpp
index f57f8d8367..660100f5e4 100644
--- a/src/network/access/http2/http2streams.cpp
+++ b/src/network/access/http2/http2streams.cpp
@@ -49,6 +49,10 @@ QT_BEGIN_NAMESPACE
namespace Http2
{
+Stream::Stream()
+{
+}
+
Stream::Stream(const HttpMessagePair &message, quint32 id, qint32 sendSize, qint32 recvSize)
: httpPair(message),
streamID(id),
diff --git a/src/network/access/http2/http2streams_p.h b/src/network/access/http2/http2streams_p.h
index 5d9a6ab512..8a825a5457 100644
--- a/src/network/access/http2/http2streams_p.h
+++ b/src/network/access/http2/http2streams_p.h
@@ -73,11 +73,10 @@ struct Q_AUTOTEST_EXPORT Stream
closed
};
- Stream() = default;
+ Stream();
Stream(const HttpMessagePair &message, quint32 streamID, qint32 sendSize,
qint32 recvSize);
- // TODO: check includes!!!
QHttpNetworkReply *reply() const;
const QHttpNetworkRequest &request() const;
QHttpNetworkRequest &request();
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index 937686920c..68a00c6837 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -40,7 +40,7 @@
#include "qhttpnetworkconnection_p.h"
#include "qhttp2protocolhandler_p.h"
-#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+#if !defined(QT_NO_HTTP)
#include "http2/bitstreams_p.h"
@@ -170,64 +170,63 @@ void QHttp2ProtocolHandler::_q_receiveReply()
Q_ASSERT(m_socket);
Q_ASSERT(m_channel);
- const auto result = inboundFrame.read(*m_socket);
- switch (result) {
- case FrameStatus::incompleteFrame:
- return;
- case FrameStatus::protocolError:
- return connectionError(PROTOCOL_ERROR, "invalid frame");
- case FrameStatus::sizeError:
- return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
- default:
- break;
- }
+ do {
+ const auto result = frameReader.read(*m_socket);
+ switch (result) {
+ case FrameStatus::incompleteFrame:
+ return;
+ case FrameStatus::protocolError:
+ return connectionError(PROTOCOL_ERROR, "invalid frame");
+ case FrameStatus::sizeError:
+ return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
+ default:
+ break;
+ }
- Q_ASSERT(result == FrameStatus::goodFrame);
+ Q_ASSERT(result == FrameStatus::goodFrame);
- if (continuationExpected && inboundFrame.type != FrameType::CONTINUATION)
- return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
+ inboundFrame = std::move(frameReader.inboundFrame());
- switch (inboundFrame.type) {
- case FrameType::DATA:
- handleDATA();
- break;
- case FrameType::HEADERS:
- handleHEADERS();
- break;
- case FrameType::PRIORITY:
- handlePRIORITY();
- break;
- case FrameType::RST_STREAM:
- handleRST_STREAM();
- break;
- case FrameType::SETTINGS:
- handleSETTINGS();
- break;
- case FrameType::PUSH_PROMISE:
- handlePUSH_PROMISE();
- break;
- case FrameType::PING:
- handlePING();
- break;
- case FrameType::GOAWAY:
- handleGOAWAY();
- break;
- case FrameType::WINDOW_UPDATE:
- handleWINDOW_UPDATE();
- break;
- case FrameType::CONTINUATION:
- handleCONTINUATION();
- break;
- case FrameType::LAST_FRAME_TYPE:
- // 5.1 - ignore unknown frames.
- break;
- }
-
- if (goingAway && !activeStreams.size())
- return;
+ const auto frameType = inboundFrame.type();
+ if (continuationExpected && frameType != FrameType::CONTINUATION)
+ return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
- if (m_socket->bytesAvailable())
- QMetaObject::invokeMethod(m_channel, "_q_receiveReply", Qt::QueuedConnection);
+ switch (frameType) {
+ case FrameType::DATA:
+ handleDATA();
+ break;
+ case FrameType::HEADERS:
+ handleHEADERS();
+ break;
+ case FrameType::PRIORITY:
+ handlePRIORITY();
+ break;
+ case FrameType::RST_STREAM:
+ handleRST_STREAM();
+ break;
+ case FrameType::SETTINGS:
+ handleSETTINGS();
+ break;
+ case FrameType::PUSH_PROMISE:
+ handlePUSH_PROMISE();
+ break;
+ case FrameType::PING:
+ handlePING();
+ break;
+ case FrameType::GOAWAY:
+ handleGOAWAY();
+ break;
+ case FrameType::WINDOW_UPDATE:
+ handleWINDOW_UPDATE();
+ break;
+ case FrameType::CONTINUATION:
+ handleCONTINUATION();
+ break;
+ case FrameType::LAST_FRAME_TYPE:
+ // 5.1 - ignore unknown frames.
+ break;
+ }
+ } while (!goingAway || activeStreams.size());
}
bool QHttp2ProtocolHandler::sendRequest()
@@ -293,14 +292,14 @@ bool QHttp2ProtocolHandler::sendClientPreface()
return false;
// 6.5 SETTINGS
- outboundFrame.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID);
+ frameWriter.start(FrameType::SETTINGS, FrameFlag::EMPTY, Http2::connectionStreamID);
// MAX frame size (16 kb), disable PUSH
- outboundFrame.append(Settings::MAX_FRAME_SIZE_ID);
- outboundFrame.append(quint32(Http2::maxFrameSize));
- outboundFrame.append(Settings::ENABLE_PUSH_ID);
- outboundFrame.append(quint32(0));
+ frameWriter.append(Settings::MAX_FRAME_SIZE_ID);
+ frameWriter.append(quint32(Http2::maxFrameSize));
+ frameWriter.append(Settings::ENABLE_PUSH_ID);
+ frameWriter.append(quint32(0));
- if (!outboundFrame.write(*m_socket))
+ if (!frameWriter.write(*m_socket))
return false;
sessionRecvWindowSize = sessionMaxRecvWindowSize;
@@ -323,38 +322,38 @@ bool QHttp2ProtocolHandler::sendSETTINGS_ACK()
if (!prefaceSent && !sendClientPreface())
return false;
- outboundFrame.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
+ frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
- return outboundFrame.write(*m_socket);
+ return frameWriter.write(*m_socket);
}
bool QHttp2ProtocolHandler::sendHEADERS(Stream &stream)
{
using namespace HPack;
- outboundFrame.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
- stream.streamID);
+ frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS,
+ stream.streamID);
if (!stream.data()) {
- outboundFrame.addFlag(FrameFlag::END_STREAM);
+ frameWriter.addFlag(FrameFlag::END_STREAM);
stream.state = Stream::halfClosedLocal;
} else {
stream.state = Stream::open;
}
- outboundFrame.append(quint32()); // No stream dependency in Qt.
- outboundFrame.append(stream.weight());
+ frameWriter.append(quint32()); // No stream dependency in Qt.
+ frameWriter.append(stream.weight());
const auto headers = build_headers(stream.request(), maxHeaderListSize);
if (!headers.size()) // nothing fits into maxHeaderListSize
return false;
// Compress in-place:
- BitOStream outputStream(outboundFrame.frameBuffer);
+ BitOStream outputStream(frameWriter.outboundFrame().buffer);
if (!encoder.encodeRequest(outputStream, headers))
return false;
- return outboundFrame.writeHEADERS(*m_socket, maxFrameSize);
+ return frameWriter.writeHEADERS(*m_socket, maxFrameSize);
}
bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
@@ -384,10 +383,10 @@ bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
return true;
}
- outboundFrame.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
+ frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
const qint32 bytesWritten = std::min<qint32>(slot, chunkSize);
- if (!outboundFrame.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
+ if (!frameWriter.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
return false;
stream.data()->advanceReadPointer(bytesWritten);
@@ -400,9 +399,9 @@ bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
}
if (replyPrivate->totallyUploadedData == request.contentLength()) {
- outboundFrame.start(FrameType::DATA, FrameFlag::END_STREAM, stream.streamID);
- outboundFrame.setPayloadSize(0);
- outboundFrame.write(*m_socket);
+ frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, stream.streamID);
+ frameWriter.setPayloadSize(0);
+ frameWriter.write(*m_socket);
stream.state = Stream::halfClosedLocal;
stream.data()->disconnect(this);
removeFromSuspended(stream.streamID);
@@ -417,61 +416,61 @@ bool QHttp2ProtocolHandler::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
{
Q_ASSERT(m_socket);
- outboundFrame.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
- outboundFrame.append(delta);
- return outboundFrame.write(*m_socket);
+ frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
+ frameWriter.append(delta);
+ return frameWriter.write(*m_socket);
}
bool QHttp2ProtocolHandler::sendRST_STREAM(quint32 streamID, quint32 errorCode)
{
Q_ASSERT(m_socket);
- outboundFrame.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
- outboundFrame.append(errorCode);
- return outboundFrame.write(*m_socket);
+ frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, streamID);
+ frameWriter.append(errorCode);
+ return frameWriter.write(*m_socket);
}
bool QHttp2ProtocolHandler::sendGOAWAY(quint32 errorCode)
{
Q_ASSERT(m_socket);
- outboundFrame.start(FrameType::GOAWAY, FrameFlag::EMPTY, connectionStreamID);
- outboundFrame.append(quint32(connectionStreamID));
- outboundFrame.append(errorCode);
- return outboundFrame.write(*m_socket);
+ frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY, connectionStreamID);
+ frameWriter.append(quint32(connectionStreamID));
+ frameWriter.append(errorCode);
+ return frameWriter.write(*m_socket);
}
void QHttp2ProtocolHandler::handleDATA()
{
- Q_ASSERT(inboundFrame.type == FrameType::DATA);
+ Q_ASSERT(inboundFrame.type() == FrameType::DATA);
- const auto streamID = inboundFrame.streamID;
+ const auto streamID = inboundFrame.streamID();
if (streamID == connectionStreamID)
return connectionError(PROTOCOL_ERROR, "DATA on stream 0x0");
if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
- if (qint32(inboundFrame.payloadSize) > sessionRecvWindowSize)
+ if (qint32(inboundFrame.payloadSize()) > sessionRecvWindowSize)
return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
- sessionRecvWindowSize -= inboundFrame.payloadSize;
+ sessionRecvWindowSize -= inboundFrame.payloadSize();
if (activeStreams.contains(streamID)) {
auto &stream = activeStreams[streamID];
- if (qint32(inboundFrame.payloadSize) > stream.recvWindow) {
+ if (qint32(inboundFrame.payloadSize()) > stream.recvWindow) {
finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
QLatin1String("flow control error"));
sendRST_STREAM(streamID, FLOW_CONTROL_ERROR);
markAsReset(streamID);
deleteActiveStream(streamID);
} else {
- stream.recvWindow -= inboundFrame.payloadSize;
+ stream.recvWindow -= inboundFrame.payloadSize();
// Uncompress data if needed and append it ...
updateStream(stream, inboundFrame);
- if (inboundFrame.flags.testFlag(FrameFlag::END_STREAM)) {
+ if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
finishStream(stream);
deleteActiveStream(stream.streamID);
} else if (stream.recvWindow < streamInitialRecvWindowSize / 2) {
@@ -493,22 +492,23 @@ void QHttp2ProtocolHandler::handleDATA()
void QHttp2ProtocolHandler::handleHEADERS()
{
- Q_ASSERT(inboundFrame.type == FrameType::HEADERS);
+ Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
- const auto streamID = inboundFrame.streamID;
+ const auto streamID = inboundFrame.streamID();
if (streamID == connectionStreamID)
return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
if (!activeStreams.contains(streamID) && !streamWasReset(streamID))
return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
- if (inboundFrame.flags.testFlag(FrameFlag::PRIORITY)) {
+ const auto flags = inboundFrame.flags();
+ if (flags.testFlag(FrameFlag::PRIORITY)) {
handlePRIORITY();
if (goingAway)
return;
}
- const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
continuedFrames.clear();
continuedFrames.push_back(std::move(inboundFrame));
if (!endHeaders) {
@@ -521,10 +521,10 @@ void QHttp2ProtocolHandler::handleHEADERS()
void QHttp2ProtocolHandler::handlePRIORITY()
{
- Q_ASSERT(inboundFrame.type == FrameType::PRIORITY ||
- inboundFrame.type == FrameType::HEADERS);
+ Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY ||
+ inboundFrame.type() == FrameType::HEADERS);
- const auto streamID = inboundFrame.streamID;
+ const auto streamID = inboundFrame.streamID();
if (streamID == connectionStreamID)
return connectionError(PROTOCOL_ERROR, "PIRORITY on 0x0 stream");
@@ -548,37 +548,38 @@ void QHttp2ProtocolHandler::handlePRIORITY()
void QHttp2ProtocolHandler::handleRST_STREAM()
{
- Q_ASSERT(inboundFrame.type == FrameType::RST_STREAM);
+ Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
// "RST_STREAM frames MUST be associated with a stream.
// If a RST_STREAM frame is received with a stream identifier of 0x0,
// the recipient MUST treat this as a connection error (Section 5.4.1)
// of type PROTOCOL_ERROR.
- if (inboundFrame.streamID == connectionStreamID)
+ const auto streamID = inboundFrame.streamID();
+ if (streamID == connectionStreamID)
return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
- if (!(inboundFrame.streamID & 0x1)) {
+ if (!(streamID & 0x1)) {
// RST_STREAM on a promised stream:
// since we do not keep track of such streams,
// just ignore.
return;
}
- if (inboundFrame.streamID >= nextID) {
+ if (streamID >= nextID) {
// "RST_STREAM frames MUST NOT be sent for a stream
// in the "idle" state. .. the recipient MUST treat this
// as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
}
- if (!activeStreams.contains(inboundFrame.streamID)) {
+ if (!activeStreams.contains(streamID)) {
// 'closed' stream, ignore.
return;
}
Q_ASSERT(inboundFrame.dataSize() == 4);
- Stream &stream = activeStreams[inboundFrame.streamID];
+ Stream &stream = activeStreams[streamID];
finishStreamWithError(stream, qFromBigEndian<quint32>(inboundFrame.dataBegin()));
markAsReset(stream.streamID);
deleteActiveStream(stream.streamID);
@@ -587,12 +588,12 @@ void QHttp2ProtocolHandler::handleRST_STREAM()
void QHttp2ProtocolHandler::handleSETTINGS()
{
// 6.5 SETTINGS.
- Q_ASSERT(inboundFrame.type == FrameType::SETTINGS);
+ Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
- if (inboundFrame.streamID != connectionStreamID)
+ if (inboundFrame.streamID() != connectionStreamID)
return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
- if (inboundFrame.flags.testFlag(FrameFlag::ACK)) {
+ if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
if (!waitingForSettingsACK)
return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
waitingForSettingsACK = false;
@@ -618,7 +619,7 @@ void QHttp2ProtocolHandler::handleSETTINGS()
void QHttp2ProtocolHandler::handlePUSH_PROMISE()
{
// 6.6 PUSH_PROMISE.
- Q_ASSERT(inboundFrame.type == FrameType::PUSH_PROMISE);
+ Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
if (prefaceSent && !waitingForSettingsACK) {
// This means, server ACKed our 'NO PUSH',
@@ -626,7 +627,7 @@ void QHttp2ProtocolHandler::handlePUSH_PROMISE()
return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
}
- const auto streamID = inboundFrame.streamID;
+ const auto streamID = inboundFrame.streamID();
if (streamID == connectionStreamID) {
return connectionError(PROTOCOL_ERROR,
"PUSH_PROMISE with invalid associated stream (0x0)");
@@ -648,7 +649,7 @@ void QHttp2ProtocolHandler::handlePUSH_PROMISE()
sendRST_STREAM(reservedID, REFUSE_STREAM);
markAsReset(reservedID);
- const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
continuedFrames.clear();
continuedFrames.push_back(std::move(inboundFrame));
@@ -664,30 +665,30 @@ void QHttp2ProtocolHandler::handlePING()
{
// Since we're implementing a client and not
// a server, we only reply to a PING, ACKing it.
- Q_ASSERT(inboundFrame.type == FrameType::PING);
+ Q_ASSERT(inboundFrame.type() == FrameType::PING);
Q_ASSERT(m_socket);
- if (inboundFrame.streamID != connectionStreamID)
+ if (inboundFrame.streamID() != connectionStreamID)
return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
- if (inboundFrame.flags & FrameFlag::ACK)
+ if (inboundFrame.flags() & FrameFlag::ACK)
return connectionError(PROTOCOL_ERROR, "unexpected PING ACK");
Q_ASSERT(inboundFrame.dataSize() == 8);
- outboundFrame.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
- outboundFrame.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
- outboundFrame.write(*m_socket);
+ frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
+ frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
+ frameWriter.write(*m_socket);
}
void QHttp2ProtocolHandler::handleGOAWAY()
{
// 6.8 GOAWAY
- Q_ASSERT(inboundFrame.type == FrameType::GOAWAY);
+ Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
// "An endpoint MUST treat a GOAWAY frame with a stream identifier
// other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
- if (inboundFrame.streamID != connectionStreamID)
+ if (inboundFrame.streamID() != connectionStreamID)
return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
const auto src = inboundFrame.dataBegin();
@@ -740,12 +741,12 @@ void QHttp2ProtocolHandler::handleGOAWAY()
void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
{
- Q_ASSERT(inboundFrame.type == FrameType::WINDOW_UPDATE);
+ Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
- const auto streamID = inboundFrame.streamID;
+ const auto streamID = inboundFrame.streamID();
if (streamID == Http2::connectionStreamID) {
if (!valid || sum_will_overflow(sessionSendWindowSize, delta))
@@ -776,13 +777,13 @@ void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
void QHttp2ProtocolHandler::handleCONTINUATION()
{
- Q_ASSERT(inboundFrame.type == FrameType::CONTINUATION);
+ Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
Q_ASSERT(continuedFrames.size()); // HEADERS frame must be already in.
- if (inboundFrame.streamID != continuedFrames.front().streamID)
+ if (inboundFrame.streamID() != continuedFrames.front().streamID())
return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
- const bool endHeaders = inboundFrame.flags.testFlag(FrameFlag::END_HEADERS);
+ const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
continuedFrames.push_back(std::move(inboundFrame));
if (!endHeaders)
@@ -796,9 +797,9 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS()
{
Q_ASSERT(continuedFrames.size());
- const auto streamID = continuedFrames[0].streamID;
+ const auto streamID = continuedFrames[0].streamID();
- if (continuedFrames[0].type == FrameType::HEADERS) {
+ if (continuedFrames[0].type() == FrameType::HEADERS) {
if (activeStreams.contains(streamID)) {
Stream &stream = activeStreams[streamID];
if (stream.state != Stream::halfClosedLocal) {
@@ -841,11 +842,11 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS()
if (!decoder.decodeHeaderFields(inputStream))
return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
- if (continuedFrames[0].type == FrameType::HEADERS) {
+ if (continuedFrames[0].type() == FrameType::HEADERS) {
if (activeStreams.contains(streamID)) {
Stream &stream = activeStreams[streamID];
updateStream(stream, decoder.decodedHeader());
- if (continuedFrames[0].flags & FrameFlag::END_STREAM) {
+ if (continuedFrames[0].flags() & FrameFlag::END_STREAM) {
finishStream(stream);
deleteActiveStream(stream.streamID);
}
@@ -953,9 +954,9 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
emit httpReply->headerChanged();
}
-void QHttp2ProtocolHandler::updateStream(Stream &stream, const Http2::FrameReader &frame)
+void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame)
{
- Q_ASSERT(frame.type == FrameType::DATA);
+ Q_ASSERT(frame.type() == FrameType::DATA);
if (const auto length = frame.dataSize()) {
const char *data = reinterpret_cast<const char *>(frame.dataBegin());
@@ -1214,4 +1215,4 @@ void QHttp2ProtocolHandler::closeSession()
QT_END_NAMESPACE
-#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+#endif // !defined(QT_NO_HTTP)
diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h
index b146e37dd3..92c6851078 100644
--- a/src/network/access/qhttp2protocolhandler_p.h
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -55,7 +55,7 @@
#include <private/qabstractprotocolhandler_p.h>
#include <private/qhttpnetworkrequest_p.h>
-#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+#if !defined(QT_NO_HTTP)
#include "http2/http2protocol_p.h"
#include "http2/http2streams_p.h"
@@ -124,7 +124,7 @@ private:
bool acceptSetting(Http2::Settings identifier, quint32 newValue);
void updateStream(Stream &stream, const HPack::HttpHeader &headers);
- void updateStream(Stream &stream, const Http2::FrameReader &dataFrame);
+ void updateStream(Stream &stream, const Http2::Frame &dataFrame);
void finishStream(Stream &stream);
// Error code send by a peer (GOAWAY/RST_STREAM):
void finishStreamWithError(Stream &stream, quint32 errorCode);
@@ -161,12 +161,13 @@ private:
// Peer's max frame size.
quint32 maxFrameSize = Http2::maxFrameSize;
- Http2::FrameReader inboundFrame;
- Http2::FrameWriter outboundFrame;
+ Http2::FrameReader frameReader;
+ Http2::Frame inboundFrame;
+ Http2::FrameWriter frameWriter;
// Temporary storage to assemble HEADERS' block
// from several CONTINUATION frames ...
bool continuationExpected = false;
- std::vector<Http2::FrameReader> continuedFrames;
+ std::vector<Http2::Frame> continuedFrames;
// Peer's max number of streams ...
quint32 maxConcurrentStreams = Http2::maxConcurrentStreams;
@@ -202,6 +203,6 @@ private:
QT_END_NAMESPACE
-#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
+#endif // !defined(QT_NO_HTTP)
#endif
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 09cea8e769..74fc23957c 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -86,7 +86,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &host
, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY || type == QHttpNetworkConnection::ConnectionTypeHTTP2)
? 1 : defaultHttpChannelCount)
#else
-, channelCount(defaultHttpChannelCount)
+, channelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2 ? 1 : defaultHttpChannelCount)
#endif // QT_NO_SSL
#ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy)
@@ -619,13 +619,11 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
break;
}
}
-#ifndef QT_NO_SSL
- else { // SPDY
+ else { // SPDY, HTTP/2
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].spdyRequestsToSend.insertMulti(request.priority(), pair);
}
-#endif // QT_NO_SSL
// For Happy Eyeballs the networkLayerState is set to Unknown
// untill we have started the first connection attempt. So no
@@ -1013,9 +1011,9 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
}
break;
}
- case QHttpNetworkConnection::ConnectionTypeSPDY:
- case QHttpNetworkConnection::ConnectionTypeHTTP2: {
-#ifndef QT_NO_SSL
+ case QHttpNetworkConnection::ConnectionTypeHTTP2:
+ case QHttpNetworkConnection::ConnectionTypeSPDY: {
+
if (channels[0].spdyRequestsToSend.isEmpty())
return;
@@ -1027,7 +1025,6 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
&& !channels[0].pendingEncrypt)
channels[0].sendRequest();
-#endif // QT_NO_SSL
break;
}
}
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 87f88aad5f..b6a9b80511 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -179,8 +179,11 @@ void QHttpNetworkConnectionChannel::init()
if (!sslConfiguration.isNull())
sslSocket->setSslConfiguration(sslConfiguration);
} else {
-#endif // QT_NO_SSL
- protocolHandler.reset(new QHttpProtocolHandler(this));
+#endif // !QT_NO_SSL
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2)
+ protocolHandler.reset(new QHttp2ProtocolHandler(this));
+ else
+ protocolHandler.reset(new QHttpProtocolHandler(this));
#ifndef QT_NO_SSL
}
#endif
@@ -835,10 +838,17 @@ void QHttpNetworkConnectionChannel::_q_connected()
#endif
} else {
state = QHttpNetworkConnectionChannel::IdleState;
- if (!reply)
- connection->d_func()->dequeueRequest(socket);
- if (reply)
- sendRequest();
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
+ if (spdyRequestsToSend.count() > 0) {
+ // wait for data from the server first (e.g. initial window, max concurrent requests)
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
+ } else {
+ if (!reply)
+ connection->d_func()->dequeueRequest(socket);
+ if (reply)
+ sendRequest();
+ }
}
}
@@ -972,9 +982,12 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
}
} while (!connection->d_func()->highPriorityQueue.isEmpty()
|| !connection->d_func()->lowPriorityQueue.isEmpty());
+
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
#ifndef QT_NO_SSL
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
- connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
+ || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY
+#endif
+ ) {
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
// emit error for all replies
@@ -983,7 +996,6 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
emit currentReply->finishedWithError(errorCode, errorString);
}
}
-#endif // QT_NO_SSL
// send the next request
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
@@ -1005,20 +1017,19 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
#ifndef QT_NO_NETWORKPROXY
void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
{
+ if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
#ifndef QT_NO_SSL
- if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
- connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
+ || connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY
+#endif
+ ) {
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
} else { // HTTP
-#endif // QT_NO_SSL
// Need to dequeue the request before we can emit the error.
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (reply)
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
-#ifndef QT_NO_SSL
}
-#endif // QT_NO_SSL
}
#endif
@@ -1028,6 +1039,19 @@ void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
sendRequest();
}
+void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error,
+ const char *message)
+{
+ if (reply)
+ emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
+ QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
+ for (int a = 0; a < spdyPairs.count(); ++a) {
+ QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
+ Q_ASSERT(currentReply);
+ emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
+ }
+}
+
#ifndef QT_NO_SSL
void QHttpNetworkConnectionChannel::_q_encrypted()
{
@@ -1078,9 +1102,10 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY ||
connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2) {
// we call setSpdyWasUsed(true) on the replies in the SPDY handler when the request is sent
- if (spdyRequestsToSend.count() > 0)
+ if (spdyRequestsToSend.count() > 0) {
// wait for data from the server first (e.g. initial window, max concurrent requests)
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
+ }
} else { // HTTP
if (!reply)
connection->d_func()->dequeueRequest(socket);
@@ -1102,19 +1127,6 @@ void QHttpNetworkConnectionChannel::requeueSpdyRequests()
spdyRequestsToSend.clear();
}
-void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error,
- const char *message)
-{
- if (reply)
- emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
- QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
- for (int a = 0; a < spdyPairs.count(); ++a) {
- QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
- Q_ASSERT(currentReply);
- emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
- }
-}
-
void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
{
if (!socket)
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
index d7d5d86a7a..61aea9d35d 100644
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -121,18 +121,21 @@ public:
bool authenticationCredentialsSent;
bool proxyCredentialsSent;
QScopedPointer<QAbstractProtocolHandler> protocolHandler;
+ // SPDY or HTTP/2 requests; SPDY is TLS-only, but
+ // HTTP/2 can be cleartext also, that's why it's
+ // outside of QT_NO_SSL section. Sorted by priority:
+ QMultiMap<int, HttpMessagePair> spdyRequestsToSend;
#ifndef QT_NO_SSL
bool ignoreAllSslErrors;
QList<QSslError> ignoreSslErrorsList;
QSslConfiguration sslConfiguration;
- QMultiMap<int, HttpMessagePair> spdyRequestsToSend; // sorted by priority
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
void setSslConfiguration(const QSslConfiguration &config);
void requeueSpdyRequests(); // when we wanted SPDY but got HTTP
+#endif
// to emit the signal for all in-flight replies:
void emitFinishedWithError(QNetworkReply::NetworkError error, const char *message);
-#endif
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession;
#endif
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index e16519c2f2..1dca7f02fb 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -285,10 +285,11 @@ void QHttpThreadDelegate::startRequest()
urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
QHttpNetworkConnection::ConnectionType connectionType
- = QHttpNetworkConnection::ConnectionTypeHTTP;
+ = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
+ : QHttpNetworkConnection::ConnectionTypeHTTP;
+
#ifndef QT_NO_SSL
if (httpRequest.isHTTP2Allowed() && ssl) {
- connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2;
QList<QByteArray> protocols;
protocols << QSslConfiguration::ALPNProtocolHTTP2
<< QSslConfiguration::NextProtocolHttp1_1;
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 63332d4fd1..29362b81e2 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -260,6 +260,14 @@ QT_BEGIN_NAMESPACE
Indicates whether SPDY was used for receiving
this reply.
+ \value HTTP2AllowedAttribute
+ Requests only, type: QMetaType::Bool (default: false)
+ Indicates whether the QNetworkAccessManager code is
+ allowed to use HTTP/2 with this request. This applies
+ to SSL requests or 'cleartext' HTTP/2.
+
+ \omitvalue HTTP2WasUsedAttribute
+
\value EmitAllUploadProgressSignalsAttribute
Requests only, type: QMetaType::Bool (default: false)
Indicates whether all upload signals should be emitted.