Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qhttpnetworkconnectionchannel.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2014 BlackBerry Limited. All rights reserved.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
8#include "private/qnoncontiguousbytedevice_p.h"
9
10#include <qpair.h>
11#include <qdebug.h>
12
13#include <private/qhttp2protocolhandler_p.h>
14#include <private/qhttpprotocolhandler_p.h>
15#include <private/http2protocol_p.h>
16
17#ifndef QT_NO_SSL
18# include <private/qsslsocket_p.h>
19# include <QtNetwork/qsslkey.h>
20# include <QtNetwork/qsslcipher.h>
21#endif
22
23#include "private/qnetconmonitor_p.h"
24
25#include <memory>
26
28
29namespace
30{
31
33{
34public:
36 ~ProtocolHandlerDeleter() { delete handler; }
37private:
38 QAbstractProtocolHandler *handler = nullptr;
39};
40
41}
42
43// TODO: Put channel specific stuff here so it does not pollute qhttpnetworkconnection.cpp
44
45// Because in-flight when sending a request, the server might close our connection (because the persistent HTTP
46// connection times out)
47// We use 3 because we can get a _q_error 3 times depending on the timing:
48static const int reconnectAttemptsDefault = 3;
49
52 , ssl(false)
53 , isInitialized(false)
54 , state(IdleState)
55 , reply(nullptr)
56 , written(0)
57 , bytesTotal(0)
58 , resendCurrent(false)
59 , lastStatus(0)
60 , pendingEncrypt(false)
61 , reconnectAttempts(reconnectAttemptsDefault)
62 , authenticationCredentialsSent(false)
63 , proxyCredentialsSent(false)
64 , protocolHandler(nullptr)
65#ifndef QT_NO_SSL
66 , ignoreAllSslErrors(false)
67#endif
68 , pipeliningSupported(PipeliningSupportUnknown)
69 , networkLayerPreference(QAbstractSocket::AnyIPProtocol)
71{
72 // Inlining this function in the header leads to compiler error on
73 // release-armv5, on at least timebox 9.2 and 10.1.
74}
75
77{
78#ifndef QT_NO_SSL
79 if (connection->d_func()->encrypt)
80 socket = new QSslSocket;
81 else
82 socket = new QTcpSocket;
83#else
84 socket = new QTcpSocket;
85#endif
86#ifndef QT_NO_NETWORKPROXY
87 // Set by QNAM anyway, but let's be safe here
89#endif
90
91 // After some back and forth in all the last years, this is now a DirectConnection because otherwise
92 // the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
93 // which behave slightly differently on Windows vs Linux
98 this, SLOT(_q_connected()),
100 QObject::connect(socket, SIGNAL(readyRead()),
101 this, SLOT(_q_readyRead()),
103
104 // The disconnected() and error() signals may already come
105 // while calling connectToHost().
106 // In case of a cached hostname or an IP this
107 // will then emit a signal to the user of QNetworkReply
108 // but cannot be caught because the user did not have a chance yet
109 // to connect to QNetworkReply's signals.
110 qRegisterMetaType<QAbstractSocket::SocketError>();
111 QObject::connect(socket, SIGNAL(disconnected()),
112 this, SLOT(_q_disconnected()),
117
118
119#ifndef QT_NO_NETWORKPROXY
120 QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
123#endif
124
125#ifndef QT_NO_SSL
126 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
127 if (sslSocket) {
128 // won't be a sslSocket if encrypt is false
129 QObject::connect(sslSocket, SIGNAL(encrypted()),
130 this, SLOT(_q_encrypted()),
132 QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
135 QObject::connect(sslSocket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
138 QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
141
143 sslSocket->ignoreSslErrors();
144
147
150 } else {
151#endif // !QT_NO_SSL
153 protocolHandler.reset(new QHttpProtocolHandler(this));
154#ifndef QT_NO_SSL
155 }
156#endif
157
158#ifndef QT_NO_NETWORKPROXY
161#endif
162 isInitialized = true;
163}
164
165
167{
169 return;
170
171 if (!socket)
175 else
177
178 // pendingEncrypt must only be true in between connected and encrypted states
179 pendingEncrypt = false;
180
181 if (socket) {
182 // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
183 // there is no socket yet.
184 socket->close();
185 }
186}
187
188
190{
191 if (!socket)
195 else
197
198 // pendingEncrypt must only be true in between connected and encrypted states
199 pendingEncrypt = false;
200
201 if (socket) {
202 // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
203 // there is no socket yet.
204 socket->abort();
205 }
206}
207
208
210{
212 return protocolHandler->sendRequest();
213}
214
215/*
216 * Invoke "protocolHandler->sendRequest" using a queued connection.
217 * It's used to return to the event loop before invoking sendRequest when
218 * there's a very real chance that the request could have been aborted
219 * (i.e. after having emitted 'encrypted').
220 */
222{
223 QMetaObject::invokeMethod(this, [this] {
225 if (reply)
226 protocolHandler->sendRequest();
228}
229
231{
233 protocolHandler->_q_receiveReply();
234}
235
237{
239 protocolHandler->_q_readyRead();
240}
241
242// called when unexpectedly reading a -1 or when data is expected but socket is closed
244{
246 if (reconnectAttempts <= 0) {
247 // too many errors reading/receiving/parsing the status, close the socket and emit error
249 close();
250 reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket);
252 reply = nullptr;
253 if (protocolHandler)
254 protocolHandler->setReply(nullptr);
257 } else {
259 reply->d_func()->clear();
260 reply->d_func()->connection = connection;
261 reply->d_func()->connectionChannel = this;
263 }
264}
265
267{
268 if (!isInitialized)
269 init();
270
272
273 // resend this request after we receive the disconnected signal
274 // If !socket->isOpen() then we have already called close() on the socket, but there was still a
275 // pending connectToHost() for which we hadn't seen a connected() signal, yet. The connected()
276 // has now arrived (as indicated by socketState != ClosingState), but we cannot send anything on
277 // such a socket anymore.
278 if (socketState == QAbstractSocket::ClosingState ||
279 (socketState != QAbstractSocket::UnconnectedState && !socket->isOpen())) {
280 if (reply)
281 resendCurrent = true;
282 return false;
283 }
284
285 // already trying to connect?
286 if (socketState == QAbstractSocket::HostLookupState ||
287 socketState == QAbstractSocket::ConnectingState) {
288 return false;
289 }
290
291 // make sure that this socket is in a connected state, if not initiate
292 // connection to the host.
293 if (socketState != QAbstractSocket::ConnectedState) {
294 // connect to the host if not already connected.
297
298 // reset state
301 proxyCredentialsSent = false;
304 priv->hasFailed = false;
307 priv->hasFailed = false;
308
309 // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done"
310 // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the
311 // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not
312 // check the "phase" for generating the Authorization header. NTLM authentication is a two stage
313 // process & needs the "phase". To make sure the QAuthenticator uses the current username/password
314 // the phase is reset to Start.
316 if (priv && priv->phase == QAuthenticatorPrivate::Done)
319 if (priv && priv->phase == QAuthenticatorPrivate::Done)
321
322 QString connectHost = connection->d_func()->hostName;
323 quint16 connectPort = connection->d_func()->port;
324
325 QHttpNetworkReply *potentialReply = connection->d_func()->predictNextRequestsReply();
326 if (potentialReply) {
327 QMetaObject::invokeMethod(potentialReply, "socketStartedConnecting", Qt::QueuedConnection);
328 } else if (h2RequestsToSend.size() > 0) {
329 QMetaObject::invokeMethod(h2RequestsToSend.values().at(0).second, "socketStartedConnecting", Qt::QueuedConnection);
330 }
331
332#ifndef QT_NO_NETWORKPROXY
333 // HTTPS always use transparent proxy.
334 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl) {
335 connectHost = connection->d_func()->networkProxy.hostName();
336 connectPort = connection->d_func()->networkProxy.port();
337 }
339 // Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
341 // ensureConnection is called before any request has been assigned, but can also be
342 // called again if reconnecting
343 if (request.url().isEmpty()) {
347 && h2RequestsToSend.size() > 0)) {
348 value = h2RequestsToSend.first().first.headerField("user-agent");
349 } else {
350 value = connection->d_func()->predictNextRequest().headerField("user-agent");
351 }
352 } else {
353 value = request.headerField("user-agent");
354 }
355 if (!value.isEmpty()) {
357 proxy.setRawHeader("User-Agent", value); //detaches
359 }
360 }
361#endif
362 if (ssl) {
363#ifndef QT_NO_SSL
364 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
365
366 // check whether we can re-use an existing SSL session
367 // (meaning another socket in this connection has already
368 // performed a full handshake)
369 if (auto ctx = connection->sslContext())
370 QSslSocketPrivate::checkSettingSslContext(sslSocket, std::move(ctx));
371
372 sslSocket->setPeerVerifyName(connection->d_func()->peerVerifyName);
373 sslSocket->connectToHostEncrypted(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
375 sslSocket->ignoreSslErrors();
377
378 // limit the socket read buffer size. we will read everything into
379 // the QHttpNetworkReply anyway, so let's grow only that and not
380 // here and there.
381 socket->setReadBufferSize(64*1024);
382#else
383 // Need to dequeue the request so that we can emit the error.
384 if (!reply)
385 connection->d_func()->dequeueRequest(socket);
386 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
387#endif
388 } else {
389 // In case of no proxy we can use the Unbuffered QTcpSocket
390#ifndef QT_NO_NETWORKPROXY
391 if (connection->d_func()->networkProxy.type() == QNetworkProxy::NoProxy
394#endif
396 // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
397 socket->setReadBufferSize(1*1024);
398#ifndef QT_NO_NETWORKPROXY
399 } else {
401
402 // limit the socket read buffer size. we will read everything into
403 // the QHttpNetworkReply anyway, so let's grow only that and not
404 // here and there.
405 socket->setReadBufferSize(64*1024);
406 }
407#endif
408 }
409 return false;
410 }
411
412 // This code path for ConnectedState
413 if (pendingEncrypt) {
414 // Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
415 // and corrupt the things sent to the server.
416 return false;
417 }
418
419 return true;
420}
421
423{
425
426 if (!reply) {
427 qWarning("QHttpNetworkConnectionChannel::allDone() called without reply. Please report at http://bugreports.qt.io/");
428 return;
429 }
430
431 // For clear text HTTP/2 we tried to upgrade from HTTP/1.1 to HTTP/2; for
432 // ConnectionTypeHTTP2Direct we can never be here in case of failure
433 // (after an attempt to read HTTP/1.1 as HTTP/2 frames) or we have a normal
434 // HTTP/2 response and thus can skip this test:
436 && !ssl && !switchedToHttp2) {
438 switchedToHttp2 = true;
439 protocolHandler->setReply(nullptr);
440
441 // As allDone() gets called from the protocol handler, it's not yet
442 // safe to delete it. There is no 'deleteLater', since
443 // QAbstractProtocolHandler is not a QObject. Instead we do this
444 // trick with ProtocolHandlerDeleter, a QObject-derived class.
445 // These dances below just make it somewhat exception-safe.
446 // 1. Create a new owner:
447 QAbstractProtocolHandler *oldHandler = protocolHandler.get();
448 auto deleter = std::make_unique<ProtocolHandlerDeleter>(oldHandler);
449 // 2. Retire the old one:
450 Q_UNUSED(protocolHandler.release());
451 // 3. Call 'deleteLater':
452 deleter->deleteLater();
453 // 3. Give up the ownerthip:
454 Q_UNUSED(deleter.release());
455
457 protocolHandler.reset(new QHttp2ProtocolHandler(this));
459 QMetaObject::invokeMethod(h2c, "_q_receiveReply", Qt::QueuedConnection);
461 // If we only had one request sent with H2 allowed, we may fail to send
462 // a client preface and SETTINGS, which is required by RFC 7540, 3.2.
463 QMetaObject::invokeMethod(h2c, "ensureClientPrefaceSent", Qt::QueuedConnection);
464 return;
465 } else {
466 // Ok, whatever happened, we do not try HTTP/2 anymore ...
468 connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
469 }
470 }
471
472 // while handling 401 & 407, we might reset the status code, so save this.
473 bool emitFinished = reply->d_func()->shouldEmitSignals();
474 bool connectionCloseEnabled = reply->d_func()->isConnectionCloseEnabled();
476
477 handleStatus();
478 // handleStatus() might have removed the reply because it already called connection->emitReplyError()
479
480 // queue the finished signal, this is required since we might send new requests from
481 // slot connected to it. The socket will not fire readyRead signal, if we are already
482 // in the slot connected to readyRead
483 if (reply && emitFinished)
485
486
487 // reset the reconnection attempts after we receive a complete reply.
488 // in case of failures, each channel will attempt two reconnects before emitting error.
490
491 // now the channel can be seen as free/idle again, all signal emissions for the reply have been done
494
495 // if it does not need to be sent again we can set it to 0
496 // the previous code did not do that and we had problems with accidental re-sending of a
497 // finished request.
498 // Note that this may trigger a segfault at some other point. But then we can fix the underlying
499 // problem.
500 if (!resendCurrent) {
502 reply = nullptr;
503 protocolHandler->setReply(nullptr);
504 }
505
506 // move next from pipeline to current request
508 if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
509 // move the pipelined ones back to the main queue
511 close();
512 } else {
513 // there were requests pipelined in and we can continue
515
516 request = messagePair.first;
517 reply = messagePair.second;
518 protocolHandler->setReply(messagePair.second);
520 resendCurrent = false;
521
522 written = 0; // message body, excluding the header, irrelevant here
523 bytesTotal = 0; // message body total, excluding the header, irrelevant here
524
525 // pipeline even more
526 connection->d_func()->fillPipeline(socket);
527
528 // continue reading
529 //_q_receiveReply();
530 // this was wrong, allDone gets called from that function anyway.
531 }
532 } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
533 // this is weird. we had nothing pipelined but still bytes available. better close it.
534 close();
535
537 } else if (alreadyPipelinedRequests.isEmpty()) {
538 if (connectionCloseEnabled)
540 close();
541 if (qobject_cast<QHttpNetworkConnection*>(connection))
543 }
544}
545
547{
549 // detect HTTP Pipelining support
550 QByteArray serverHeaderField;
551 if (
552 // check for HTTP/1.1
553 (reply->majorVersion() == 1 && reply->minorVersion() == 1)
554 // check for not having connection close
555 && (!reply->d_func()->isConnectionCloseEnabled())
556 // check if it is still connected
558 // check for broken servers in server reply header
559 // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
560 && (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
561 && (!serverHeaderField.contains("Microsoft-IIS/5."))
562 && (!serverHeaderField.contains("Netscape-Enterprise/3."))
563 // this is adpoted from the knowledge of the Nokia 7.x browser team (DEF143319)
564 && (!serverHeaderField.contains("WebLogic"))
565 && (!serverHeaderField.startsWith("Rocket")) // a Python Web Server, see Web2py.com
566 ) {
568 } else {
570 }
571}
572
573// called when the connection broke and we need to queue some pipelined requests again
575{
576 for (int i = 0; i < alreadyPipelinedRequests.size(); i++)
577 connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
579
580 // only run when the QHttpNetworkConnection is not currently being destructed, e.g.
581 // this function is called from _q_disconnected which is called because
582 // of ~QHttpNetworkConnectionPrivate
583 if (qobject_cast<QHttpNetworkConnection*>(connection))
585}
586
588{
591
592 int statusCode = reply->statusCode();
593 bool resend = false;
594
595 switch (statusCode) {
596 case 301:
597 case 302:
598 case 303:
599 case 305:
600 case 307:
601 case 308: {
602 // Parse the response headers and get the "location" url
603 QUrl redirectUrl = connection->d_func()->parseRedirectResponse(socket, reply);
604 if (redirectUrl.isValid())
605 reply->setRedirectUrl(redirectUrl);
606
607 if ((statusCode == 307 || statusCode == 308) && !resetUploadData()) {
608 // Couldn't reset the upload data, which means it will be unable to POST the data -
609 // this would lead to a long wait until it eventually failed and then retried.
610 // Instead of doing that we fail here instead, resetUploadData will already have emitted
611 // a ContentReSendError, so we're done.
612 } else if (qobject_cast<QHttpNetworkConnection *>(connection)) {
614 }
615 break;
616 }
617 case 401: // auth required
618 case 407: // proxy auth required
619 if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
620 if (resend) {
621 if (!resetUploadData())
622 break;
623
624 reply->d_func()->eraseData();
625
627 // this does a re-send without closing the connection
628 resendCurrent = true;
630 } else {
631 // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
634 }
635 } else {
636 //authentication cancelled, close the channel.
637 close();
638 }
639 } else {
642 QNetworkReply::NetworkError errorCode = (statusCode == 407)
645 reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
646 emit reply->finishedWithError(errorCode, reply->d_func()->errorString);
647 }
648 break;
649 default:
650 if (qobject_cast<QHttpNetworkConnection*>(connection))
652 }
653}
654
656{
657 if (!reply) {
658 //this happens if server closes connection while QHttpNetworkConnectionPrivate::_q_startNextRequest is pending
659 return false;
660 }
662 || switchedToHttp2) {
663 // The else branch doesn't make any sense for HTTP/2, since 1 channel is multiplexed into
664 // many streams. And having one stream fail to reset upload data should not completely close
665 // the channel. Handled in the http2 protocol handler.
666 } else if (QNonContiguousByteDevice *uploadByteDevice = request.uploadByteDevice()) {
667 if (!uploadByteDevice->reset()) {
668 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
669 return false;
670 }
671 written = 0;
672 }
673 return true;
674}
675
676#ifndef QT_NO_NETWORKPROXY
677
679{
680 if (socket)
681 socket->setProxy(networkProxy);
682
683 proxy = networkProxy;
684}
685
686#endif
687
688#ifndef QT_NO_SSL
689
691{
692 if (socket)
693 static_cast<QSslSocket *>(socket)->ignoreSslErrors();
694
695 ignoreAllSslErrors = true;
696}
697
698
700{
701 if (socket)
702 static_cast<QSslSocket *>(socket)->ignoreSslErrors(errors);
703
704 ignoreSslErrorsList = errors;
705}
706
708{
709 if (socket)
711
714 else
716}
717
718#endif
719
721{
722 // this is only called for simple GET
723
724 QHttpNetworkRequest &request = pair.first;
725 QHttpNetworkReply *reply = pair.second;
726 reply->d_func()->clear();
727 reply->d_func()->connection = connection;
728 reply->d_func()->connectionChannel = this;
729 reply->d_func()->autoDecompress = request.d->autoDecompress;
730 reply->d_func()->pipeliningUsed = true;
731
732#ifndef QT_NO_NETWORKPROXY
734 (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy)));
735#else
737#endif
738
740
741 // pipelineFlush() needs to be called at some point afterwards
742}
743
745{
746 if (pipeline.isEmpty())
747 return;
748
749 // The goal of this is so that we have everything in one TCP packet.
750 // For the Unbuffered QTcpSocket this is manually needed, the buffered
751 // QTcpSocket does it automatically.
752 // Also, sometimes the OS does it for us (Nagle's algorithm) but that
753 // happens only sometimes.
755 pipeline.clear();
756}
757
758
760{
762 close();
763 if (reply)
764 resendCurrent = true;
765 if (qobject_cast<QHttpNetworkConnection*>(connection))
767}
768
770{
772 if (reply)
773 resendCurrent = true;
774 if (qobject_cast<QHttpNetworkConnection*>(connection))
776}
777
779{
781}
782
784{
786}
787
789{
791}
792
794{
796}
797
799{
800 Q_UNUSED(bytes);
801 if (ssl) {
802 // In the SSL case we want to send data from encryptedBytesWritten signal since that one
803 // is the one going down to the actual network, not only into some SSL buffer.
804 return;
805 }
806
807 // bytes have been written to the socket. write even more of them :)
808 if (isSocketWriting())
809 sendRequest();
810 // otherwise we do nothing
811}
812
814{
818 return;
819 }
820
821 // read the available data before closing (also done in _q_error for other codepaths)
823 if (reply) {
826 }
828 // re-sending request because the socket was in ClosingState
830 }
833 // If nothing was in a pipeline, no need in calling
834 // _q_startNextRequest (which it does):
836 }
837
838 pendingEncrypt = false;
839}
840
841
843{
844 // For the Happy Eyeballs we need to check if this is the first channel to connect.
845 if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) {
846 if (connection->d_func()->delayedConnectionTimer.isActive())
847 connection->d_func()->delayedConnectionTimer.stop();
849 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
851 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
852 else {
854 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
855 else
856 connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
857 }
858 connection->d_func()->networkLayerDetected(networkLayerPreference);
859 } else {
861 if (((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4)
863 || ((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv6)
864 && (networkLayerPreference != QAbstractSocket::IPv6Protocol && !anyProtocol))) {
865 close();
866 // This is the second connection so it has to be closed and we can schedule it for another request.
868 return;
869 }
870 //The connections networkLayerState had already been decided.
871 }
872
873 // improve performance since we get the request sent by the kernel ASAP
874 //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
875 // We have this commented out now. It did not have the effect we wanted. If we want to
876 // do this properly, Qt has to combine multiple HTTP requests into one buffer
877 // and send this to the kernel in one syscall and then the kernel immediately sends
878 // it as one TCP packet because of TCP_NODELAY.
879 // However, this code is currently not in Qt, so we rely on the kernel combining
880 // the requests into one TCP packet.
881
882 // not sure yet if it helps, but it makes sense
884
886
888 auto connectionPrivate = connection->d_func();
889 if (!connectionPrivate->connectionMonitor.isMonitoring()) {
890 // Now that we have a pair of addresses, we can start monitoring the
891 // connection status to handle its loss properly.
892 if (connectionPrivate->connectionMonitor.setTargets(socket->localAddress(), socket->peerAddress()))
893 connectionPrivate->connectionMonitor.startMonitoring();
894 }
895 }
896
897 // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
898 //channels[i].reconnectAttempts = 2;
899 if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
900#ifndef QT_NO_SSL
901 if (!connection->sslContext()) {
902 // this socket is making the 1st handshake for this connection,
903 // we need to set the SSL context so new sockets can reuse it
904 if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket)))
905 connection->setSslContext(std::move(socketSslContext));
906 }
907#endif
910 protocolHandler.reset(new QHttp2ProtocolHandler(this));
911 if (h2RequestsToSend.size() > 0) {
912 // In case our peer has sent us its settings (window size, max concurrent streams etc.)
913 // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
915 }
916 } else {
918 const bool tryProtocolUpgrade = connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2;
919 if (tryProtocolUpgrade) {
920 // For HTTP/1.1 it's already created and never reset.
921 protocolHandler.reset(new QHttpProtocolHandler(this));
922 }
923 switchedToHttp2 = false;
924
925 if (!reply)
926 connection->d_func()->dequeueRequest(socket);
927
928 if (reply) {
929 if (tryProtocolUpgrade) {
930 // Let's augment our request with some magic headers and try to
931 // switch to HTTP/2.
933 }
934 sendRequest();
935 }
936 }
937}
938
939
941{
942 if (!socket)
943 return;
945
946 switch (socketError) {
949 break;
952#ifndef QT_NO_NETWORKPROXY
953 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl)
955#endif
956 break;
958 // This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer.
959 // Depending on timing it can also come three times in a row (first time when we try to write into a closing QSslSocket).
960 // The reconnectAttempts handling catches the cases where we can re-send the request.
962 // Not actually an error, it is normal for Keep-Alive connections to close after some time if no request
963 // is sent on them. No need to error the other replies below. Just bail out here.
964 // The _q_disconnected will handle the possibly pipelined replies. HTTP/2 is special for now,
965 // we do not resend, but must report errors if any request is in progress (note, while
966 // not in its sendRequest(), protocol handler switches the channel to IdleState, thus
967 // this check is under this condition in 'if'):
968 if (protocolHandler) {
971 && switchedToHttp2)) {
972 auto h2Handler = static_cast<QHttp2ProtocolHandler *>(protocolHandler.get());
973 h2Handler->handleConnectionClosure();
974 }
975 }
976 return;
978 // Try to reconnect/resend before sending an error.
979 // While "Reading" the _q_disconnected() will handle this.
980 // If we're using ssl then the protocolHandler is not initialized until
981 // "encrypted" has been emitted, since retrying requires the protocolHandler (asserted)
982 // we will not try if encryption is not done.
983 if (!pendingEncrypt && reconnectAttempts-- > 0) {
985 return;
986 } else {
988 }
990 if (!reply)
991 break;
992
993 if (!reply->d_func()->expectContent()) {
994 // No content expected, this is a valid way to have the connection closed by the server
995 // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
996 QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
997 return;
998 }
999 if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
1000 // There was no content-length header and it's not chunked encoding,
1001 // so this is a valid way to have the connection closed by the server
1002 // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
1003 QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
1004 return;
1005 }
1006 // ok, we got a disconnect even though we did not expect it
1007 // Try to read everything from the socket before we emit the error.
1008 if (socket->bytesAvailable()) {
1009 // Read everything from the socket into the reply buffer.
1010 // we can ignore the readbuffersize as the data is already
1011 // in memory and we will not receive more data on the socket.
1015 if (!reply) {
1016 // No more reply assigned after the previous call? Then it had been finished successfully.
1020 return;
1021 }
1022 }
1023
1025 } else {
1027 }
1028 break;
1030 // try to reconnect/resend before sending an error.
1033 return;
1034 }
1035 errorCode = QNetworkReply::TimeoutError;
1036 break;
1039 break;
1042 break;
1045 break;
1047 // try to reconnect/resend before sending an error.
1048 if (reconnectAttempts-- > 0) {
1050 return;
1051 }
1053 break;
1055 // try to reconnect/resend before sending an error.
1056 if (reconnectAttempts-- > 0) {
1058 return;
1059 }
1061 break;
1062 default:
1063 // all other errors are treated as NetworkError
1065 break;
1066 }
1068 QString errorString = connection->d_func()->errorDetail(errorCode, socket, socket->errorString());
1069
1070 // In the HostLookupPending state the channel should not emit the error.
1071 // This will instead be handled by the connection.
1072 if (!connection->d_func()->shouldEmitChannelError(socket))
1073 return;
1074
1075 // emit error for all waiting replies
1076 do {
1077 // First requeue the already pipelined requests for the current failed reply,
1078 // then dequeue pending requests so we can also mark them as finished with error
1079 if (reply)
1081 else
1082 connection->d_func()->dequeueRequest(socket);
1083
1084 if (reply) {
1085 reply->d_func()->errorString = errorString;
1086 reply->d_func()->httpErrorCode = errorCode;
1087 emit reply->finishedWithError(errorCode, errorString);
1088 reply = nullptr;
1089 if (protocolHandler)
1090 protocolHandler->setReply(nullptr);
1091 }
1092 } while (!connection->d_func()->highPriorityQueue.isEmpty()
1093 || !connection->d_func()->lowPriorityQueue.isEmpty());
1094
1098 for (int a = 0; a < h2Pairs.size(); ++a) {
1099 // emit error for all replies
1100 QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
1101 currentReply->d_func()->errorString = errorString;
1102 currentReply->d_func()->httpErrorCode = errorCode;
1103 Q_ASSERT(currentReply);
1104 emit currentReply->finishedWithError(errorCode, errorString);
1105 }
1107 }
1108
1109 // send the next request
1110 QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
1111
1112 if (that) {
1113 //signal emission triggered event loop
1114 if (!socket)
1118 else
1120
1121 // pendingEncrypt must only be true in between connected and encrypted states
1122 pendingEncrypt = false;
1123 }
1124}
1125
1126#ifndef QT_NO_NETWORKPROXY
1128{
1132 if (h2RequestsToSend.size() > 0)
1133 connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
1134 } else { // HTTP
1135 // Need to dequeue the request before we can emit the error.
1136 if (!reply)
1137 connection->d_func()->dequeueRequest(socket);
1138 if (reply)
1139 connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
1140 }
1141}
1142#endif
1143
1145{
1146 if (reply)
1147 sendRequest();
1148}
1149
1151 const char *message)
1152{
1153 if (reply)
1154 emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
1156 for (int a = 0; a < h2Pairs.size(); ++a) {
1157 QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
1158 Q_ASSERT(currentReply);
1159 emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
1160 }
1161}
1162
1163#ifndef QT_NO_SSL
1165{
1166 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
1167 Q_ASSERT(sslSocket);
1168
1170 // ConnectionTypeHTTP2Direct does not rely on ALPN/NPN to negotiate HTTP/2,
1171 // after establishing a secure connection we immediately start sending
1172 // HTTP/2 frames.
1173 switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) {
1175 QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol();
1176 if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) {
1177 // fall through to create a QHttpProtocolHandler
1178 } else if (nextProtocol == QSslConfiguration::ALPNProtocolHTTP2) {
1179 switchedToHttp2 = true;
1180 protocolHandler.reset(new QHttp2ProtocolHandler(this));
1182 break;
1183 } else {
1185 "detected unknown Next Protocol Negotiation protocol");
1186 break;
1187 }
1188 }
1189 Q_FALLTHROUGH();
1190 case QSslConfiguration::NextProtocolNegotiationUnsupported: // No agreement, try HTTP/1(.1)
1192 protocolHandler.reset(new QHttpProtocolHandler(this));
1193
1194 QSslConfiguration newConfiguration = sslSocket->sslConfiguration();
1195 QList<QByteArray> protocols = newConfiguration.allowedNextProtocols();
1196 const int nProtocols = protocols.size();
1197 // Clear the protocol that we failed to negotiate, so we do not try
1198 // it again on other channels that our connection can create/open.
1201
1202 if (nProtocols > protocols.size()) {
1203 newConfiguration.setAllowedNextProtocols(protocols);
1204 const int channelCount = connection->d_func()->channelCount;
1205 for (int i = 0; i < channelCount; ++i)
1206 connection->d_func()->channels[i].setSslConfiguration(newConfiguration);
1207 }
1208
1210 // We use only one channel for HTTP/2, but normally six for
1211 // HTTP/1.1 - let's restore this number to the reserved number of
1212 // channels:
1213 if (connection->d_func()->activeChannelCount < connection->d_func()->channelCount) {
1214 connection->d_func()->activeChannelCount = connection->d_func()->channelCount;
1215 // re-queue requests from HTTP/2 queue to HTTP queue, if any
1217 }
1218 break;
1219 }
1220 default:
1222 "detected unknown Next Protocol Negotiation protocol");
1223 }
1226 // We have to reset QHttp2ProtocolHandler's state machine, it's a new
1227 // connection and the handler's state is unique per connection.
1228 protocolHandler.reset(new QHttp2ProtocolHandler(this));
1229 }
1230
1231 if (!socket)
1232 return; // ### error
1234 pendingEncrypt = false;
1235
1238 if (h2RequestsToSend.size() > 0) {
1239 // Similar to HTTP/1.1 counterpart below:
1240 const auto &h2Pairs = h2RequestsToSend.values(); // (request, reply)
1241 const auto &pair = h2Pairs.first();
1242 emit pair.second->encrypted();
1243 // In case our peer has sent us its settings (window size, max concurrent streams etc.)
1244 // let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
1246 }
1247 } else { // HTTP
1248 if (!reply)
1249 connection->d_func()->dequeueRequest(socket);
1250 if (reply) {
1251 reply->setHttp2WasUsed(false);
1252 Q_ASSERT(reply->d_func()->connectionChannel == this);
1253 emit reply->encrypted();
1254 }
1255 if (reply)
1257 }
1258}
1259
1261{
1263 for (int a = 0; a < h2Pairs.size(); ++a)
1264 connection->d_func()->requeueRequest(h2Pairs.at(a));
1266}
1267
1269{
1270 if (!socket)
1271 return;
1272 //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
1273 // Also pause the connection because socket notifiers may fire while an user
1274 // dialog is displaying
1275 connection->d_func()->pauseConnection();
1276 if (pendingEncrypt && !reply)
1277 connection->d_func()->dequeueRequest(socket);
1279 if (reply)
1280 emit reply->sslErrors(errors);
1281 }
1282#ifndef QT_NO_SSL
1283 else { // HTTP/2
1285 for (int a = 0; a < h2Pairs.size(); ++a) {
1286 // emit SSL errors for all replies
1287 QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
1288 Q_ASSERT(currentReply);
1289 emit currentReply->sslErrors(errors);
1290 }
1291 }
1292#endif // QT_NO_SSL
1293 connection->d_func()->resumeConnection();
1294}
1295
1297{
1298 connection->d_func()->pauseConnection();
1299
1300 if (pendingEncrypt && !reply)
1301 connection->d_func()->dequeueRequest(socket);
1302
1304 if (reply)
1306 } else {
1308 for (int a = 0; a < h2Pairs.size(); ++a) {
1309 // emit SSL errors for all replies
1310 QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
1311 Q_ASSERT(currentReply);
1313 }
1314 }
1315
1316 connection->d_func()->resumeConnection();
1317}
1318
1320{
1321 Q_UNUSED(bytes);
1322 // bytes have been written to the socket. write even more of them :)
1323 if (isSocketWriting())
1324 sendRequest();
1325 // otherwise we do nothing
1326}
1327
1328#endif
1329
1331{
1332 // Inlining this function in the header leads to compiler error on
1333 // release-armv5, on at least timebox 9.2 and 10.1.
1334 connection = c;
1335}
1336
1338
1339#include "moc_qhttpnetworkconnectionchannel_p.cpp"
bool connected
The QAbstractSocket class provides the base functionality common to all socket types.
SocketState
This enum describes the different states in which a socket can be.
virtual void setReadBufferSize(qint64 size)
Sets the size of QAbstractSocket's internal read buffer to be size bytes.
static constexpr auto IPv4Protocol
void abort()
Aborts the current connection and resets the socket.
virtual void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
qint64 bytesAvailable() const override
Returns the number of incoming bytes that are waiting to be read.
QHostAddress localAddress() const
Returns the host address of the local socket if available; otherwise returns QHostAddress::Null.
QNetworkProxy proxy() const
void setProxy(const QNetworkProxy &networkProxy)
static constexpr auto AnyIPProtocol
void close() override
Closes the I/O device for the socket and calls disconnectFromHost() to close the socket's connection.
SocketState state() const
Returns the state of the socket.
SocketError
This enum describes the socket errors that can occur.
static constexpr auto IPv6Protocol
virtual void connectToHost(const QString &hostName, quint16 port, OpenMode mode=ReadWrite, NetworkLayerProtocol protocol=AnyIPProtocol)
Attempts to make a connection to hostName on the given port.
QHostAddress peerAddress() const
Returns the address of the connected peer if the socket is in ConnectedState; otherwise returns QHost...
static QAuthenticatorPrivate * getPrivate(QAuthenticator &auth)
The QAuthenticator class provides an authentication object.
\inmodule QtCore
Definition qbytearray.h:57
bool startsWith(QByteArrayView bv) const
Definition qbytearray.h:170
bool contains(char c) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qbytearray.h:583
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
void clear()
Clears the contents of the byte array and makes it null.
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
NetworkLayerProtocol protocol() const
Returns the network layer protocol of the host address.
Q_INVOKABLE void handleConnectionClosure()
std::unique_ptr< QAbstractProtocolHandler > protocolHandler
QScopedPointer< QSslConfiguration > sslConfiguration
void setProxy(const QNetworkProxy &networkProxy)
void _q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *)
void setSslConfiguration(const QSslConfiguration &config)
QAbstractSocket::NetworkLayerProtocol networkLayerPreference
void emitFinishedWithError(QNetworkReply::NetworkError error, const char *message)
QPointer< QHttpNetworkConnection > connection
QMultiMap< int, HttpMessagePair > h2RequestsToSend
void _q_sslErrors(const QList< QSslError > &errors)
void setConnection(QHttpNetworkConnection *c)
void _q_error(QAbstractSocket::SocketError)
void _q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *auth)
QHttp2Configuration http2Parameters() const
QHttpNetworkConnectionChannel * channels() const
void setConnectionType(ConnectionType type)
QNetworkProxy transparentProxy() const
std::shared_ptr< QSslContext > sslContext()
void setSslContext(std::shared_ptr< QSslContext > context)
QHttpNetworkConnection * connection()
QString errorString() const
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator)
void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail=QString())
void setHttp2WasUsed(bool h2Used)
void setReadBufferSize(qint64 size)
void setDownstreamLimited(bool t)
int minorVersion() const override
qint64 contentLength() const override
void setRedirectUrl(const QUrl &url)
void sslErrors(const QList< QSslError > &errors)
QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue=QByteArray()) const override
int majorVersion() const override
static QByteArray header(const QHttpNetworkRequest &request, bool throughProxy)
QUrl url() const override
QNonContiguousByteDevice * uploadByteDevice() const
QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue=QByteArray()) const override
bool isOpen() const
Returns true if the device is open; otherwise returns false.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
QString errorString() const
Returns a human-readable description of the last device error that occurred.
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & first()
Definition qlist.h:628
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
value_type takeFirst()
Definition qlist.h:549
qsizetype removeAll(const AT &t)
Definition qlist.h:575
void append(parameter_type t)
Definition qlist.h:441
void clear()
Definition qlist.h:417
size_type size() const
Definition qmap.h:911
void clear()
Definition qmap.h:933
T & first()
Definition qmap.h:1117
QList< T > values() const
Definition qmap.h:1078
The QNetworkProxy class provides a network layer proxy.
void setRawHeader(const QByteArray &headerName, const QByteArray &value)
QNetworkProxy::ProxyType type() const
Returns the proxy type for this instance.
NetworkError
Indicates all possible error conditions found during the processing of the request.
@ ProxyConnectionRefusedError
@ ProxyAuthenticationRequiredError
@ AuthenticationRequiredError
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
\inmodule QtCore
Definition qpointer.h:18
T * data() const noexcept
Returns the value of the pointer referenced by this object.
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
The QSslConfiguration class holds the configuration and state of an SSL connection.
QList< QByteArray > allowedNextProtocols() const
bool isNull() const
Returns true if this is a null QSslConfiguration object.
static const char ALPNProtocolHTTP2[]
QByteArray nextNegotiatedProtocol() const
void setAllowedNextProtocols(const QList< QByteArray > &protocols)
static const char NextProtocolHttp1_1[]
NextProtocolNegotiationStatus nextProtocolNegotiationStatus() const
The QSslPreSharedKeyAuthenticator class provides authentication data for pre shared keys (PSK) cipher...
static std::shared_ptr< QSslContext > sslContext(QSslSocket *socket)
static void checkSettingSslContext(QSslSocket *, std::shared_ptr< QSslContext >)
The QSslSocket class provides an SSL encrypted socket for both clients and servers.
Definition qsslsocket.h:29
QSslConfiguration sslConfiguration() const
void setSslConfiguration(const QSslConfiguration &config)
void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode mode=ReadWrite, NetworkLayerProtocol protocol=AnyIPProtocol)
Starts an encrypted connection to the device hostName on port, using mode as the \l OpenMode.
void setPeerVerifyName(const QString &hostName)
void ignoreSslErrors(const QList< QSslError > &errors)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
The QTcpSocket class provides a TCP socket.
Definition qtcpsocket.h:18
\inmodule QtCore
Definition qurl.h:94
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1874
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1888
EGLContext ctx
else opt state
[0]
bool is_protocol_upgraded(const QHttpNetworkReply &reply)
void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetworkRequest *request)
Combined button and popup list for selecting options.
@ QueuedConnection
@ DirectConnection
#define Q_FALLTHROUGH()
DBusConnection const char DBusError * error
DBusConnection * connection
EGLConfig config
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
static const int reconnectAttemptsDefault
QPair< QHttpNetworkRequest, QHttpNetworkReply * > HttpMessagePair
#define qWarning
Definition qlogging.h:162
static const QMetaObjectPrivate * priv(const uint *data)
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLenum GLsizei GLuint GLint * bytesWritten
GLboolean GLboolean GLboolean GLboolean a
[7]
GLuint GLsizei const GLchar * message
GLfloat GLfloat GLfloat GLfloat h
const GLubyte * c
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:43
long long qint64
Definition qtypes.h:55
QObject::connect nullptr
QTcpSocket * socket
[1]
QNetworkReply * reply
QNetworkProxy proxy
[0]
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...