Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhttp2protocolhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:network-protocol
4
5#include "access/http2/http2protocol_p.h"
6#include "access/qhttp2connection_p.h"
9
10#include "http2/http2frames_p.h"
11
12#include <private/qnoncontiguousbytedevice_p.h>
13#include <private/qsocketabstraction_p.h>
14
15#include <QtNetwork/qabstractsocket.h>
16
17#include <QtCore/qloggingcategory.h>
18#include <QtCore/qendian.h>
19#include <QtCore/qdebug.h>
20#include <QtCore/qlist.h>
21#include <QtCore/qnumeric.h>
22#include <QtCore/qurl.h>
23
24#include <qhttp2configuration.h>
25
26#ifndef QT_NO_NETWORKPROXY
27# include <QtNetwork/qnetworkproxy.h>
28#endif
29
30#include <qcoreapplication.h>
31
32#include <algorithm>
33#include <vector>
34#include <optional>
35
37
38using namespace Qt::StringLiterals;
39
40namespace
41{
42
43HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxHeaderListSize,
44 bool useProxy)
45{
46 using namespace HPack;
47
48 HttpHeader header;
49 header.reserve(300);
50
51 // 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
52 // then stop immediately with error.
53 const auto auth = request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
54 header.emplace_back(":authority", auth);
55 header.emplace_back(":method", request.methodName());
56 header.emplace_back(":path", request.uri(useProxy));
57 header.emplace_back(":scheme", request.url().scheme().toLatin1());
58
59 HeaderSize size = header_size(header);
60 if (!size.first) // Ooops!
61 return HttpHeader();
62
63 if (size.second > maxHeaderListSize)
64 return HttpHeader(); // Bad, we cannot send this request ...
65
66 const QHttpHeaders requestHeader = request.header();
67 for (qsizetype i = 0; i < requestHeader.size(); ++i) {
68 const auto name = requestHeader.nameAt(i);
69 const auto value = requestHeader.valueAt(i);
70 const HeaderSize delta = entry_size(name, value);
71 if (!delta.first) // Overflow???
72 break;
73 if (std::numeric_limits<quint32>::max() - delta.second < size.second)
74 break;
75 size.second += delta.second;
76 if (size.second > maxHeaderListSize)
77 break;
78
79 if (name == "connection"_L1 || name == "host"_L1 || name == "keep-alive"_L1
80 || name == "proxy-connection"_L1 || name == "transfer-encoding"_L1) {
81 continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler
82 }
83 // TODO: verify with specs, which fields are valid to send ....
84 //
85 // Note: RFC 7450 8.1.2 (HTTP/2) states that header field names must be lower-cased
86 // prior to their encoding in HTTP/2; header name fields in QHttpHeaders are already
87 // lower-cased
88 header.emplace_back(QByteArray{name.data(), name.size()},
89 QByteArray{value.data(), value.size()});
90 }
91
92 return header;
93}
94
95QUrl urlkey_from_request(const QHttpNetworkRequest &request)
96{
97 QUrl url;
98
99 url.setScheme(request.url().scheme());
100 url.setAuthority(request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo));
101 url.setPath(QLatin1StringView(request.uri(false)));
102
103 return url;
104}
105
106} // Unnamed namespace
107
108// Since we anyway end up having this in every function definition:
109using namespace Http2;
110
111QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
112 : QAbstractProtocolHandler(channel)
113{
114 const auto h2Config = m_connection->http2Parameters();
115
116 if (!channel->ssl
117 && m_connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
118 h2Connection = QHttp2Connection::createUpgradedConnection(channel->socket, h2Config);
119 // Since we upgraded there is already one stream (the request was sent as http1)
120 // and we need to handle it:
121 QHttp2Stream *stream = h2Connection->getStream(1);
122 Q_ASSERT(stream);
123 Q_ASSERT(channel->reply);
124 connectStream({ channel->request, channel->reply }, stream);
125 } else {
126 Q_ASSERT(QSocketAbstraction::socketState(channel->socket) == QAbstractSocket::ConnectedState);
127 h2Connection = QHttp2Connection::createDirectConnection(channel->socket, h2Config);
128 }
129 connect(h2Connection, &QHttp2Connection::receivedGOAWAY, this,
130 &QHttp2ProtocolHandler::handleGOAWAY);
131 connect(h2Connection, &QHttp2Connection::errorOccurred, this,
132 &QHttp2ProtocolHandler::connectionError);
133 connect(h2Connection, &QHttp2Connection::newIncomingStream, this,
134 [this](QHttp2Stream *stream){
135 // Having our peer start streams doesn't make sense. We are
136 // doing regular http request-response.
137 stream->sendRST_STREAM(REFUSE_STREAM);
138 if (!h2Connection->isGoingAway())
139 h2Connection->close(Http2::PROTOCOL_ERROR);
140 });
141 connect(h2Connection, &QHttp2Connection::connectionClosed, this,
142 &QHttp2ProtocolHandler::closeSession);
143}
144
145void QHttp2ProtocolHandler::handleConnectionClosure()
146{
147 // The channel has just received RemoteHostClosedError and since it will
148 // not try (for HTTP/2) to re-connect, it's time to finish all replies
149 // with error.
150
151 // Maybe we still have some data to read and can successfully finish
152 // a stream/request?
153 _q_receiveReply();
154 h2Connection->handleConnectionClosure();
155}
156
157void QHttp2ProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData)
158{
159 QPointer<QHttp2Stream> stream = streamIDs.take(uploadData);
160 if (stream && stream->isActive())
161 stream->sendRST_STREAM(CANCEL);
162}
163
164void QHttp2ProtocolHandler::_q_readyRead()
165{
166 _q_receiveReply();
167}
168
169void QHttp2ProtocolHandler::_q_receiveReply()
170{
171 // not using QObject::connect because the QHttpNetworkConnectionChannel
172 // already handles the signals we care about, so we just call the slot
173 // directly.
174 Q_ASSERT(h2Connection);
175 h2Connection->handleReadyRead();
176}
177
178bool QHttp2ProtocolHandler::sendRequest()
179{
180 if (h2Connection->isGoingAway()) {
181 // Stop further calls to this method: we have received GOAWAY
182 // so we cannot create new streams.
183 m_channel->emitFinishedWithError(QNetworkReply::ProtocolUnknownError,
184 "GOAWAY received, cannot start a request");
185 m_channel->h2RequestsToSend.clear();
186 return false;
187 }
188
189 // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted())
190 // requests first:
191 auto &requests = m_channel->h2RequestsToSend;
192 for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
193 const auto &pair = *it;
194 if (pair.first.isPreConnect()) {
195 m_connection->preConnectFinished();
196 emit pair.second->finished();
197 it = requests.erase(it);
198 if (requests.empty()) {
199 // Normally, after a connection was established and H2
200 // was negotiated, we send a client preface. connectToHostEncrypted
201 // though is not meant to send any data, it's just a 'preconnect'.
202 // Thus we return early:
203 return true;
204 }
205 } else {
206 ++it;
207 }
208 }
209
210 if (requests.empty())
211 return true;
212
213 m_channel->state = QHttpNetworkConnectionChannel::WritingState;
214 // Check what was promised/pushed, maybe we do not have to send a request
215 // and have a response already?
216
217 for (auto it = requests.begin(), end = requests.end(); it != end;) {
218 HttpMessagePair &httpPair = *it;
219
220 QUrl promiseKey = urlkey_from_request(httpPair.first);
221 if (h2Connection->promisedStream(promiseKey) != nullptr) {
222 // There's a PUSH_PROMISE for this request, so we don't send one
223 initReplyFromPushPromise(httpPair, promiseKey);
224 it = requests.erase(it);
225 continue;
226 }
227
228 QHttp2Stream *stream = createNewStream(httpPair);
229 if (!stream) { // There was an issue creating the stream
230 // Check if it was unrecoverable, ie. the reply is errored out and finished:
231 if (httpPair.second->isFinished()) {
232 it = requests.erase(it);
233 }
234 // ... either way we stop looping:
235 break;
236 }
237
238 QHttpNetworkRequest &request = requestReplyPairs[stream].first;
239 if (!sendHEADERS(stream, request)) {
240 finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
241 "failed to send HEADERS frame(s)"_L1);
242 continue;
243 }
244 if (request.uploadByteDevice()) {
245 if (!sendDATA(stream, httpPair.second)) {
246 finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
247 "failed to send DATA frame(s)"_L1);
248 continue;
249 }
250 }
251 it = requests.erase(it);
252 }
253
254 m_channel->state = QHttpNetworkConnectionChannel::IdleState;
255
256 return true;
257}
258
259/*!
260 \internal
261 This gets called during destruction of \a reply, so do not call any functions
262 on \a reply. We check if there is a stream associated with the reply and,
263 if there is, we remove the request-reply pair associated with this stream,
264 delete the stream and return \c{true}. Otherwise nothing happens and we
265 return \c{false}.
266*/
267bool QHttp2ProtocolHandler::tryRemoveReply(QHttpNetworkReply *reply)
268{
269 QHttp2Stream *stream = streamIDs.take(reply);
270 if (stream) {
271 stream->sendRST_STREAM(stream->isUploadingDATA() ? Http2::CANCEL : Http2::HTTP2_NO_ERROR);
272 requestReplyPairs.remove(stream);
273 stream->deleteLater();
274 return true;
275 }
276 return false;
277}
278
279bool QHttp2ProtocolHandler::sendHEADERS(QHttp2Stream *stream, QHttpNetworkRequest &request)
280{
281 using namespace HPack;
282
283 bool useProxy = false;
284#ifndef QT_NO_NETWORKPROXY
285 useProxy = m_connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy;
286#endif
287 if (request.withCredentials()) {
288 m_connection->d_func()->createAuthorization(m_socket, request);
289 request.d->needResendWithCredentials = false;
290 }
291 const auto headers = build_headers(request, h2Connection->maxHeaderListSize(), useProxy);
292 if (headers.empty()) // nothing fits into maxHeaderListSize
293 return false;
294
295 bool mustUploadData = request.uploadByteDevice();
296 return stream->sendHEADERS(headers, !mustUploadData);
297}
298
299bool QHttp2ProtocolHandler::sendDATA(QHttp2Stream *stream, QHttpNetworkReply *reply)
300{
301 Q_ASSERT(reply);
302 QHttpNetworkReplyPrivate *replyPrivate = reply->d_func();
303 Q_ASSERT(replyPrivate);
304 QHttpNetworkRequest &request = replyPrivate->request;
305 Q_ASSERT(request.uploadByteDevice());
306
307 bool startedSending = stream->sendDATA(request.uploadByteDevice(), true);
308 return startedSending && !stream->wasReset();
309}
310
311void QHttp2ProtocolHandler::handleHeadersReceived(const HPack::HttpHeader &headers, bool endStream)
312{
313 QHttp2Stream *stream = qobject_cast<QHttp2Stream *>(sender());
314 Q_ASSERT(stream);
315 auto &requestPair = requestReplyPairs[stream];
316 auto *httpReply = requestPair.second;
317 auto &httpRequest = requestPair.first;
318 if (!httpReply)
319 return;
320
321 auto *httpReplyPrivate = httpReply->d_func();
322
323 // For HTTP/1 'location' is handled (and redirect URL set) when a protocol
324 // handler emits channel->allDone(). Http/2 protocol handler never emits
325 // allDone, since we have many requests multiplexed in one channel at any
326 // moment and we are probably not done yet. So we extract url and set it
327 // here, if needed.
328 int statusCode = 0;
329 for (const auto &pair : headers) {
330 const auto &name = pair.name;
331 const auto value = QByteArrayView(pair.value);
332
333 // TODO: part of this code copies what SPDY protocol handler does when
334 // processing headers. Binary nature of HTTP/2 and SPDY saves us a lot
335 // of parsing and related errors/bugs, but it would be nice to have
336 // more detailed validation of headers.
337 if (name == ":status") {
338 bool ok = false;
339 if (int status = value.toInt(&ok); ok && status >= 0 && status <= 999) {
340 statusCode = status;
341 httpReply->setStatusCode(statusCode);
342 m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth
343 } else {
344 finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError,
345 "invalid :status value"_L1);
346 return;
347 }
348 } else if (name == "content-length") {
349 bool ok = false;
350 const qlonglong length = value.toLongLong(&ok);
351 if (ok)
352 httpReply->setContentLength(length);
353 } else {
354 const auto binder = name == "set-cookie" ? QByteArrayView("\n") : QByteArrayView(", ");
355 httpReply->appendHeaderField(name, QByteArray(pair.value).replace('\0', binder));
356 }
357 }
358
359 // Discard all informational (1xx) replies with the exception of 101.
360 // Also see RFC 9110 (Chapter 15.2)
361 if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) {
362 httpReplyPrivate->clearHttpLayerInformation();
363 return;
364 }
365
366 if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
369 if (result.errorCode != QNetworkReply::NoError) {
370 auto errorString = m_connection->d_func()->errorDetail(result.errorCode, m_socket);
371 finishStreamWithError(stream, result.errorCode, errorString);
372 stream->sendRST_STREAM(INTERNAL_ERROR);
373 return;
374 }
375
376 if (result.redirectUrl.isValid())
377 httpReply->setRedirectUrl(result.redirectUrl);
378 }
379
380 if (httpReplyPrivate->isCompressed() && httpRequest.d->autoDecompress)
381 httpReplyPrivate->removeAutoDecompressHeader();
382
383 if (QHttpNetworkReply::isHttpRedirect(statusCode)) {
384 // Note: This status code can trigger uploadByteDevice->reset() in
385 // QHttpNetworkConnectionChannel::handleStatus. Alas, we have no single
386 // request/reply, we multiplex several requests and thus we never simply
387 // call 'handleStatus'. If we have a byte-device - we try to reset it
388 // here, we don't (and can't) handle any error during reset operation.
389 if (auto *byteDevice = httpRequest.uploadByteDevice()) {
390 byteDevice->reset();
391 httpReplyPrivate->totallyUploadedData = 0;
392 }
393 }
394
395 QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::headerChanged, Qt::QueuedConnection);
396 if (endStream)
397 finishStream(stream, Qt::QueuedConnection);
398}
399
400void QHttp2ProtocolHandler::handleDataReceived(const QByteArray &data, bool endStream)
401{
402 QHttp2Stream *stream = qobject_cast<QHttp2Stream *>(sender());
403 auto &httpPair = requestReplyPairs[stream];
404 auto *httpReply = httpPair.second;
405 if (!httpReply)
406 return;
407 Q_ASSERT(!stream->isPromisedStream());
408
409 if (!data.isEmpty() && !httpPair.first.d->needResendWithCredentials) {
410 auto *replyPrivate = httpReply->d_func();
411
412 replyPrivate->totalProgress += data.size();
413
414 replyPrivate->responseData.append(data);
415
416 if (replyPrivate->shouldEmitSignals()) {
417 QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::readyRead,
418 Qt::QueuedConnection);
419 QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::dataReadProgress,
420 Qt::QueuedConnection, replyPrivate->totalProgress,
421 replyPrivate->bodyLength);
422 }
423 }
424 stream->clearDownloadBuffer();
425 if (endStream)
426 finishStream(stream, Qt::QueuedConnection);
427}
428
429// After calling this function, either the request will be re-sent or
430// the reply will be finishedWithError! Do not emit finished() or similar on the
431// reply after this!
432void QHttp2ProtocolHandler::handleAuthorization(QHttp2Stream *stream)
433{
434 auto &requestPair = requestReplyPairs[stream];
435 auto *httpReply = requestPair.second;
436 auto *httpReplyPrivate = httpReply->d_func();
437 auto &httpRequest = requestPair.first;
438
439 Q_ASSERT(httpReply && (httpReply->statusCode() == 401 || httpReply->statusCode() == 407));
440
441 const auto handleAuth = [&, this](QByteArrayView authField, bool isProxy) -> bool {
442 Q_ASSERT(httpReply);
443 const QByteArrayView auth = authField.trimmed();
444 if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) {
445 // NTLM/Kerberos/Negotiate authentication is not supported with HTTP/2.
446 // Finish the stream with an error so QNetworkReply::finished is emitted.
447 // Falling back to HTTP/1.1 is a separate, larger effort (QTBUG-143926).
448 emit httpReply->headerChanged();
449 emit httpReply->readyRead();
450 const QNetworkReply::NetworkError error = isProxy
451 ? QNetworkReply::ProxyAuthenticationRequiredError
452 : QNetworkReply::AuthenticationRequiredError;
453 finishStreamWithError(stream, error,
454 m_connection->d_func()->errorDetail(error, m_socket));
455 return false;
456 }
457 // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus
458 bool resend = false;
459 const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge(
460 m_socket, httpReply, isProxy, resend);
461 if (authenticateHandled) {
462 if (resend) {
463 httpReply->d_func()->eraseData();
464 // Add the request back in queue, we'll retry later now that
465 // we've gotten some username/password set on it:
466 httpRequest.d->needResendWithCredentials = true;
467 m_channel->h2RequestsToSend.insert(httpRequest.priority(), requestPair);
468 httpReply->d_func()->clearHeaders();
469 // If we have data we were uploading we need to reset it:
470 if (auto *byteDevice = httpRequest.uploadByteDevice()) {
471 byteDevice->reset();
472 httpReplyPrivate->totallyUploadedData = 0;
473 }
474 // We automatically try to send new requests when the stream is
475 // closed, so we don't need to call sendRequest ourselves.
476 return true;
477 } // else: we're just not resending the request.
478 // @note In the http/1.x case we (at time of writing) call close()
479 // for the connectionChannel (which is a bit weird, we could surely
480 // reuse the open socket outside "connection:close"?), but in http2
481 // we only have one channel, so we won't close anything.
482 } else {
483 // No authentication header or authentication isn't supported, but
484 // we got a 401/407 so we cannot succeed. We need to emit signals
485 // for headers and data, and then finishWithError.
486 emit httpReply->headerChanged();
487 emit httpReply->readyRead();
488 QNetworkReply::NetworkError error = httpReply->statusCode() == 401
489 ? QNetworkReply::AuthenticationRequiredError
490 : QNetworkReply::ProxyAuthenticationRequiredError;
491 finishStreamWithError(stream, QNetworkReply::AuthenticationRequiredError,
492 m_connection->d_func()->errorDetail(error, m_socket));
493 }
494 return false;
495 };
496
497 // These statuses would in HTTP/1.1 be handled by
498 // QHttpNetworkConnectionChannel::handleStatus. But because h2 has
499 // multiple streams/requests in a single channel this structure does not
500 // map properly to that function.
501 bool authOk = true;
502 switch (httpReply->statusCode()) {
503 case 401:
504 authOk = handleAuth(httpReply->headerField("www-authenticate"), false);
505 break;
506 case 407:
507 authOk = handleAuth(httpReply->headerField("proxy-authenticate"), true);
508 break;
509 default:
510 Q_UNREACHABLE();
511 }
512 if (authOk) {
513 stream->sendRST_STREAM(CANCEL);
514 } // else: errors handled inside handleAuth
515}
516
517// Called when we have received a frame with the END_STREAM flag set
518void QHttp2ProtocolHandler::finishStream(QHttp2Stream *stream, Qt::ConnectionType connectionType)
519{
520 if (stream->state() != QHttp2Stream::State::Closed)
521 stream->sendRST_STREAM(CANCEL);
522
523 auto &pair = requestReplyPairs[stream];
524 auto *httpReply = pair.second;
525 if (httpReply) {
526 int statusCode = httpReply->statusCode();
527 if (statusCode == 401 || statusCode == 407) {
528 // handleAuthorization will either re-send the request or
529 // finishWithError. In either case we don't want to emit finished
530 // here.
531 handleAuthorization(stream);
532 return;
533 }
534
535 httpReply->disconnect(this);
536
537 if (!pair.first.d->needResendWithCredentials) {
538 if (connectionType == Qt::DirectConnection)
539 emit httpReply->finished();
540 else
541 QMetaObject::invokeMethod(httpReply, &QHttpNetworkReply::finished, connectionType);
542 }
543 }
544
545 qCDebug(QT_HTTP2) << "stream" << stream->streamID() << "closed";
546 stream->deleteLater();
547}
548
549void QHttp2ProtocolHandler::handleGOAWAY(Http2Error errorCode, quint32 lastStreamID)
550{
551 qCDebug(QT_HTTP2) << "GOAWAY received, error code:" << errorCode << "last stream ID:"
552 << lastStreamID;
553
554 // For the requests (and streams) we did not start yet, we have to report an
555 // error.
556 m_channel->emitFinishedWithError(QNetworkReply::ProtocolUnknownError,
557 "GOAWAY received, cannot start a request");
558 // Also, prevent further calls to sendRequest:
559 m_channel->h2RequestsToSend.clear();
560
561 QNetworkReply::NetworkError error = QNetworkReply::NoError;
562 QString message;
563 qt_error(errorCode, error, message);
564
565 // Even if the GOAWAY frame contains NO_ERROR we must send an error
566 // when terminating streams to ensure users can distinguish from a
567 // successful completion.
568 if (errorCode == HTTP2_NO_ERROR) {
569 error = QNetworkReply::ContentReSendError;
570 message = "Server stopped accepting new streams before this stream was established"_L1;
571 }
572}
573
574void QHttp2ProtocolHandler::finishStreamWithError(QHttp2Stream *stream, Http2Error errorCode)
575{
576 QNetworkReply::NetworkError error = QNetworkReply::NoError;
577 QString message;
578 qt_error(errorCode, error, message);
579 finishStreamWithError(stream, error, message);
580}
581
582void QHttp2ProtocolHandler::finishStreamWithError(QHttp2Stream *stream,
583 QNetworkReply::NetworkError error, const QString &message)
584{
585 stream->sendRST_STREAM(CANCEL);
586 const HttpMessagePair &pair = requestReplyPairs.value(stream);
587 if (auto *httpReply = pair.second) {
588 httpReply->disconnect(this);
589
590 // TODO: error message must be translated!!! (tr)
591 emit httpReply->finishedWithError(error, message);
592 }
593
594 qCWarning(QT_HTTP2) << "stream" << stream->streamID() << "finished with error:" << message;
595}
596
597/*!
598 \internal
599
600 Creates a QHttp2Stream for the request, will return \nullptr if the stream
601 could not be created for some reason, and will finish the reply if required.
602*/
603QHttp2Stream *QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message,
604 bool uploadDone)
605{
606 QUrl streamKey = urlkey_from_request(message.first);
607 if (auto promisedStream = h2Connection->promisedStream(streamKey)) {
608 Q_ASSERT(promisedStream->state() != QHttp2Stream::State::Closed);
609 return promisedStream;
610 }
611
612 QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
613 streamResult = h2Connection->createStream();
614 if (!streamResult.ok()) {
615 if (streamResult.error()
616 == QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached) {
617 // We have to wait for a stream to be closed before we can create a new one, so
618 // we just return nullptr, the caller should not remove it from the queue.
619 return nullptr;
620 }
621 qCDebug(QT_HTTP2) << "failed to create new stream:" << streamResult.error();
622 auto *reply = message.second;
623 const char *cstr = "Failed to initialize HTTP/2 stream with errorcode: %1";
624 const QString errorString = QCoreApplication::tr("QHttp", cstr)
625 .arg(QDebug::toString(streamResult.error()));
626 emit reply->finishedWithError(QNetworkReply::ProtocolFailure, errorString);
627 return nullptr;
628 }
629 QHttp2Stream *stream = streamResult.unwrap();
630
631 if (!uploadDone) {
632 if (auto *src = message.first.uploadByteDevice()) {
633 connect(src, &QObject::destroyed, this, &QHttp2ProtocolHandler::_q_uploadDataDestroyed);
634 streamIDs.insert(src, stream);
635 }
636 }
637
638 auto *reply = message.second;
639 QMetaObject::invokeMethod(reply, &QHttpNetworkReply::requestSent, Qt::QueuedConnection);
640
641 connectStream(message, stream);
642 return stream;
643}
644
645void QHttp2ProtocolHandler::connectStream(const HttpMessagePair &message, QHttp2Stream *stream)
646{
647 auto *reply = message.second;
648 auto *replyPrivate = reply->d_func();
649 replyPrivate->connection = m_connection;
650 replyPrivate->connectionChannel = m_channel;
651
652 reply->setHttp2WasUsed(true);
653 QPointer<QHttp2Stream> &oldStream = streamIDs[reply];
654 if (oldStream)
655 disconnect(oldStream, nullptr, this, nullptr);
656 oldStream = stream;
657 requestReplyPairs.emplace(stream, message);
658
659 QObject::connect(stream, &QHttp2Stream::headersReceived, this,
660 &QHttp2ProtocolHandler::handleHeadersReceived);
661 QObject::connect(stream, &QHttp2Stream::dataReceived, this,
662 &QHttp2ProtocolHandler::handleDataReceived);
663 QObject::connect(stream, &QHttp2Stream::errorOccurred, this,
664 [this, stream](Http2Error errorCode, const QString &errorString) {
665 qCWarning(QT_HTTP2)
666 << "stream" << stream->streamID() << "error:" << errorString;
667 finishStreamWithError(stream, errorCode);
668 });
669
670 QObject::connect(stream, &QHttp2Stream::stateChanged, this, [this](QHttp2Stream::State state) {
671 if (state == QHttp2Stream::State::Closed) {
672 // Try to send more requests if we have any
673 if (!m_channel->h2RequestsToSend.empty()) {
674 QMetaObject::invokeMethod(this, &QHttp2ProtocolHandler::sendRequest,
675 Qt::QueuedConnection);
676 }
677 }
678 });
679}
680
681void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &message,
682 const QUrl &cacheKey)
683{
684 QHttp2Stream *promise = h2Connection->promisedStream(cacheKey);
685 Q_ASSERT(promise);
686 Q_ASSERT(message.second);
687 message.second->setHttp2WasUsed(true);
688
689 qCDebug(QT_HTTP2) << "found cached/promised response on stream" << promise->streamID();
690
691 const bool replyFinished = promise->state() == QHttp2Stream::State::Closed;
692
693 connectStream(message, promise);
694
695 // Now that we have connect()ed, re-emit signals so that the reply
696 // can be processed as usual:
697
698 QByteDataBuffer downloadBuffer = promise->takeDownloadBuffer();
699 if (const auto &headers = promise->receivedHeaders(); !headers.empty())
700 emit promise->headersReceived(headers, replyFinished && downloadBuffer.isEmpty());
701
702 if (!downloadBuffer.isEmpty()) {
703 for (qsizetype i = 0; i < downloadBuffer.bufferCount(); ++i) {
704 const bool streamEnded = replyFinished && i == downloadBuffer.bufferCount() - 1;
705 emit promise->dataReceived(downloadBuffer[i], streamEnded);
706 }
707 }
708}
709
710void QHttp2ProtocolHandler::connectionError(Http2::Http2Error errorCode, const QString &message)
711{
712 Q_ASSERT(!message.isNull());
713
714 qCCritical(QT_HTTP2) << "connection error:" << message;
715
716 const auto error = qt_error(errorCode);
717 m_channel->emitFinishedWithError(error, qPrintable(message));
718
719 closeSession();
720}
721
722void QHttp2ProtocolHandler::closeSession()
723{
724 m_channel->close();
725}
726
727QT_END_NAMESPACE
728
729#include "moc_qhttp2protocolhandler_p.cpp"
static ParseRedirectResult parseRedirectResponse(QHttpNetworkReply *reply)
Combined button and popup list for selecting options.