diff options
author | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2016-08-22 11:30:00 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2016-08-22 11:30:01 +0200 |
commit | d314819fc02139e05e16c56657898c704f7fb48f (patch) | |
tree | a61ba968233634948401c8339f9613844de1c2b5 /src/network/access | |
parent | 9f888d2fde9c5413e5519e0914e9b13638760985 (diff) | |
parent | e0e9e196a72ffe5457034894eaaadc90ed0d34ef (diff) |
Merge dev into 5.8
Change-Id: I41ee7b50534b01cf042bed8bb8824ba2e5026a29
Diffstat (limited to 'src/network/access')
-rw-r--r-- | src/network/access/http2/http2frames.cpp | 444 | ||||
-rw-r--r-- | src/network/access/http2/http2frames_p.h | 101 | ||||
-rw-r--r-- | src/network/access/http2/http2streams.cpp | 4 | ||||
-rw-r--r-- | src/network/access/http2/http2streams_p.h | 3 | ||||
-rw-r--r-- | src/network/access/qhttp2protocolhandler.cpp | 263 | ||||
-rw-r--r-- | src/network/access/qhttp2protocolhandler_p.h | 13 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 13 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 68 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel_p.h | 7 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate.cpp | 5 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 8 |
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. |