diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2024-03-04 17:32:00 +0100 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2024-05-15 16:06:46 +0200 |
commit | 956795bda84f5e67e9f5c9eb3bd9bcce944eb290 (patch) | |
tree | 4795cf123d7a2ba4862b47a541b3a535605f35e9 | |
parent | c9a1e8d306d588c161461f8b22b76b701d10bce0 (diff) |
Http connection( channel)?: support local socket
Task-number: QTBUG-102855
Change-Id: Idcf571498b7a18792cf7843a826d78672e264a73
Reviewed-by: Mate Barany <mate.barany@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 45 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 145 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel_p.h | 5 |
3 files changed, 131 insertions, 64 deletions
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 419491a711..e481ab36fb 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -102,13 +102,18 @@ void QHttpNetworkConnectionPrivate::pauseConnection() // Disable all socket notifiers for (int i = 0; i < activeChannelCount; i++) { - if (channels[i].socket) { + if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) { #ifndef QT_NO_SSL if (encrypt) - QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket)); + QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(absSocket)); else #endif - QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket); + QAbstractSocketPrivate::pauseSocketNotifiers(absSocket); + } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) { + // @todo how would we do this? +#if 0 // @todo Enable this when there is a debug category for this + qDebug() << "Should pause socket but there is no way to do it for local sockets"; +#endif } } } @@ -118,17 +123,21 @@ void QHttpNetworkConnectionPrivate::resumeConnection() state = RunningState; // Enable all socket notifiers for (int i = 0; i < activeChannelCount; i++) { - if (channels[i].socket) { + if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) { #ifndef QT_NO_SSL if (encrypt) - QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket)); + QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(absSocket)); else #endif - QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket); + QAbstractSocketPrivate::resumeSocketNotifiers(absSocket); // Resume pending upload if needed if (channels[i].state == QHttpNetworkConnectionChannel::WritingState) QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection); + } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) { +#if 0 // @todo Enable this when there is a debug category for this + qDebug() << "Should resume socket but there is no way to do it for local sockets"; +#endif } } @@ -1024,7 +1033,7 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() for (int i = 0; i < activeChannelCount; ++i) { if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) { if (!channels[i].socket - || channels[i].socket->state() == QAbstractSocket::UnconnectedState) { + || QSocketAbstraction::socketState(channels[i].socket) == QAbstractSocket::UnconnectedState) { if (!channels[i].ensureConnection()) continue; } @@ -1048,7 +1057,9 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() // try to get a free AND connected socket for (int i = 0; i < activeChannelCount; ++i) { if (channels[i].socket) { - if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) { + if (!channels[i].reply && !channels[i].isSocketBusy() + && QSocketAbstraction::socketState(channels[i].socket) + == QAbstractSocket::ConnectedState) { if (dequeueRequest(channels[i].socket)) channels[i].sendRequest(); } @@ -1068,7 +1079,8 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() else if (networkLayerState == IPv6) channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol; channels[0].ensureConnection(); - if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState + if (auto *s = channels[0].socket; s + && QSocketAbstraction::socketState(s) == QAbstractSocket::ConnectedState && !channels[0].pendingEncrypt) { if (channels[0].h2RequestsToSend.size()) { channels[0].sendRequest(); @@ -1095,9 +1107,13 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() // return fast if there is nothing to pipeline if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty()) return; - for (int i = 0; i < activeChannelCount; i++) - if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState) + for (int i = 0; i < activeChannelCount; i++) { + if (channels[i].socket + && QSocketAbstraction::socketState(channels[i].socket) + == QAbstractSocket::ConnectedState) { fillPipeline(channels[i].socket); + } + } // If there is not already any connected channels we need to connect a new one. // We do not pair the channel with the request until we know if it is @@ -1122,15 +1138,16 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest() if (!channels[i].socket) continue; - if ((channels[i].socket->state() == QAbstractSocket::ConnectingState) - || (channels[i].socket->state() == QAbstractSocket::HostLookupState) + using State = QAbstractSocket::SocketState; + if ((QSocketAbstraction::socketState(channels[i].socket) == State::ConnectingState) + || (QSocketAbstraction::socketState(channels[i].socket) == State::HostLookupState) || channels[i].pendingEncrypt) { // pendingEncrypt == "EncryptingState" neededOpenChannels--; continue; } if (!channels[i].reply && !channels[i].isSocketBusy() - && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) { + && (QSocketAbstraction::socketState(channels[i].socket) == State::UnconnectedState)) { channelsToConnect.push_back(i); neededOpenChannels--; } diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index e178d65356..c58ffab5ed 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -12,6 +12,7 @@ #include <private/qhttp2protocolhandler_p.h> #include <private/qhttpprotocolhandler_p.h> #include <private/http2protocol_p.h> +#include <private/qsocketabstraction_p.h> #ifndef QT_NO_SSL # include <private/qsslsocket_p.h> @@ -85,7 +86,8 @@ void QHttpNetworkConnectionChannel::init() #endif #ifndef QT_NO_NETWORKPROXY // Set by QNAM anyway, but let's be safe here - socket->setProxy(QNetworkProxy::NoProxy); + if (auto s = qobject_cast<QAbstractSocket *>(socket)) + s->setProxy(QNetworkProxy::NoProxy); #endif // After some back and forth in all the last years, this is now a DirectConnection because otherwise @@ -94,32 +96,48 @@ void QHttpNetworkConnectionChannel::init() QObject::connect(socket, &QIODevice::bytesWritten, this, &QHttpNetworkConnectionChannel::_q_bytesWritten, Qt::DirectConnection); - QObject::connect(socket, &QAbstractSocket::connected, - this, &QHttpNetworkConnectionChannel::_q_connected, - Qt::DirectConnection); QObject::connect(socket, &QIODevice::readyRead, this, &QHttpNetworkConnectionChannel::_q_readyRead, Qt::DirectConnection); - // The disconnected() and error() signals may already come - // while calling connectToHost(). - // In case of a cached hostname or an IP this - // will then emit a signal to the user of QNetworkReply - // but cannot be caught because the user did not have a chance yet - // to connect to QNetworkReply's signals. - qRegisterMetaType<QAbstractSocket::SocketError>(); - QObject::connect(socket, &QAbstractSocket::disconnected, - this, &QHttpNetworkConnectionChannel::_q_disconnected, - Qt::DirectConnection); - QObject::connect(socket, &QAbstractSocket::errorOccurred, - this, &QHttpNetworkConnectionChannel::_q_error, - Qt::DirectConnection); + + QSocketAbstraction::visit([this](auto *socket){ + using SocketType = std::remove_pointer_t<decltype(socket)>; + QObject::connect(socket, &SocketType::connected, + this, &QHttpNetworkConnectionChannel::_q_connected, + Qt::DirectConnection); + + // The disconnected() and error() signals may already come + // while calling connectToHost(). + // In case of a cached hostname or an IP this + // will then emit a signal to the user of QNetworkReply + // but cannot be caught because the user did not have a chance yet + // to connect to QNetworkReply's signals. + QObject::connect(socket, &SocketType::disconnected, + this, &QHttpNetworkConnectionChannel::_q_disconnected, + Qt::DirectConnection); + if constexpr (std::is_same_v<SocketType, QAbstractSocket>) { + QObject::connect(socket, &QAbstractSocket::errorOccurred, + this, &QHttpNetworkConnectionChannel::_q_error, + Qt::DirectConnection); + } else if constexpr (std::is_same_v<SocketType, QLocalSocket>) { + auto convertAndForward = [this](QLocalSocket::LocalSocketError error) { + _q_error(static_cast<QAbstractSocket::SocketError>(error)); + }; + QObject::connect(socket, &SocketType::errorOccurred, + this, std::move(convertAndForward), + Qt::DirectConnection); + } + }, socket); + #ifndef QT_NO_NETWORKPROXY - QObject::connect(socket, &QAbstractSocket::proxyAuthenticationRequired, - this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, - Qt::DirectConnection); + if (auto *s = qobject_cast<QAbstractSocket *>(socket)) { + QObject::connect(s, &QAbstractSocket::proxyAuthenticationRequired, + this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired, + Qt::DirectConnection); + } #endif #ifndef QT_NO_SSL @@ -156,8 +174,10 @@ void QHttpNetworkConnectionChannel::init() #endif #ifndef QT_NO_NETWORKPROXY - if (proxy.type() != QNetworkProxy::NoProxy) - socket->setProxy(proxy); + if (auto *s = qobject_cast<QAbstractSocket *>(socket); + s && proxy.type() != QNetworkProxy::NoProxy) { + s->setProxy(proxy); + } #endif isInitialized = true; } @@ -170,7 +190,7 @@ void QHttpNetworkConnectionChannel::close() if (!socket) state = QHttpNetworkConnectionChannel::IdleState; - else if (socket->state() == QAbstractSocket::UnconnectedState) + else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState) state = QHttpNetworkConnectionChannel::IdleState; else state = QHttpNetworkConnectionChannel::ClosingState; @@ -190,7 +210,7 @@ void QHttpNetworkConnectionChannel::abort() { if (!socket) state = QHttpNetworkConnectionChannel::IdleState; - else if (socket->state() == QAbstractSocket::UnconnectedState) + else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState) state = QHttpNetworkConnectionChannel::IdleState; else state = QHttpNetworkConnectionChannel::ClosingState; @@ -201,7 +221,10 @@ void QHttpNetworkConnectionChannel::abort() if (socket) { // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while // there is no socket yet. - socket->abort(); + auto callAbort = [](auto *s) { + s->abort(); + }; + QSocketAbstraction::visit(callAbort, socket); } } @@ -268,7 +291,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection() if (!isInitialized) init(); - QAbstractSocket::SocketState socketState = socket->state(); + QAbstractSocket::SocketState socketState = QSocketAbstraction::socketState(socket); // resend this request after we receive the disconnected signal // If !socket->isOpen() then we have already called close() on the socket, but there was still a @@ -335,7 +358,8 @@ bool QHttpNetworkConnectionChannel::ensureConnection() connectHost = connection->d_func()->networkProxy.hostName(); connectPort = connection->d_func()->networkProxy.port(); } - if (socket->proxy().type() == QNetworkProxy::HttpProxy) { + if (auto *abSocket = qobject_cast<QAbstractSocket *>(socket); + abSocket && abSocket->proxy().type() == QNetworkProxy::HttpProxy) { // Make user-agent field available to HTTP proxy socket engine (QTBUG-17223) QByteArray value; // ensureConnection is called before any request has been assigned, but can also be @@ -353,11 +377,11 @@ bool QHttpNetworkConnectionChannel::ensureConnection() value = request.headerField("user-agent"); } if (!value.isEmpty()) { - QNetworkProxy proxy(socket->proxy()); + QNetworkProxy proxy(abSocket->proxy()); auto h = proxy.headers(); h.replaceOrAppend(QHttpHeaders::WellKnownHeader::UserAgent, value); proxy.setHeaders(std::move(h)); - socket->setProxy(proxy); + abSocket->setProxy(proxy); } } #endif @@ -380,7 +404,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection() // limit the socket read buffer size. we will read everything into // the QHttpNetworkReply anyway, so let's grow only that and not // here and there. - socket->setReadBufferSize(64*1024); + sslSocket->setReadBufferSize(64*1024); #else // Need to dequeue the request so that we can emit the error. if (!reply) @@ -394,17 +418,24 @@ bool QHttpNetworkConnectionChannel::ensureConnection() && connection->cacheProxy().type() == QNetworkProxy::NoProxy && connection->transparentProxy().type() == QNetworkProxy::NoProxy) { #endif - socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered, networkLayerPreference); - // For an Unbuffered QTcpSocket, the read buffer size has a special meaning. - socket->setReadBufferSize(1*1024); + if (auto *s = qobject_cast<QAbstractSocket *>(socket)) { + s->connectToHost(connectHost, connectPort, + QIODevice::ReadWrite | QIODevice::Unbuffered, + networkLayerPreference); + // For an Unbuffered QTcpSocket, the read buffer size has a special meaning. + s->setReadBufferSize(1 * 1024); + } else if (auto *s = qobject_cast<QLocalSocket *>(socket)) { + s->connectToServer(connectHost); + } #ifndef QT_NO_NETWORKPROXY } else { - socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference); - + auto *s = qobject_cast<QAbstractSocket *>(socket); + Q_ASSERT(s); // limit the socket read buffer size. we will read everything into // the QHttpNetworkReply anyway, so let's grow only that and not // here and there. - socket->setReadBufferSize(64*1024); + s->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference); + s->setReadBufferSize(64 * 1024); } #endif } @@ -507,7 +538,7 @@ void QHttpNetworkConnectionChannel::allDone() // move next from pipeline to current request if (!alreadyPipelinedRequests.isEmpty()) { - if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) { + if (resendCurrent || connectionCloseEnabled || QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState) { // move the pipelined ones back to the main queue requeueCurrentlyPipelinedRequests(); close(); @@ -538,7 +569,7 @@ void QHttpNetworkConnectionChannel::allDone() QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); } else if (alreadyPipelinedRequests.isEmpty()) { if (connectionCloseEnabled) - if (socket->state() != QAbstractSocket::UnconnectedState) + if (QSocketAbstraction::socketState(socket) != QAbstractSocket::UnconnectedState) close(); if (qobject_cast<QHttpNetworkConnection*>(connection)) QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection); @@ -556,7 +587,7 @@ void QHttpNetworkConnectionChannel::detectPipeliningSupport() // check for not having connection close && (!reply->d_func()->isConnectionCloseEnabled()) // check if it is still connected - && (socket->state() == QAbstractSocket::ConnectedState) + && (QSocketAbstraction::socketState(socket) == QAbstractSocket::ConnectedState) // check for broken servers in server reply header // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining && (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4.")) @@ -679,8 +710,8 @@ bool QHttpNetworkConnectionChannel::resetUploadData() void QHttpNetworkConnectionChannel::setProxy(const QNetworkProxy &networkProxy) { - if (socket) - socket->setProxy(networkProxy); + if (auto *s = qobject_cast<QAbstractSocket *>(socket)) + s->setProxy(networkProxy); proxy = networkProxy; } @@ -841,7 +872,7 @@ void QHttpNetworkConnectionChannel::_q_disconnected() } -void QHttpNetworkConnectionChannel::_q_connected() +void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket *absSocket) { // For the Happy Eyeballs we need to check if this is the first channel to connect. if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) { @@ -852,7 +883,7 @@ void QHttpNetworkConnectionChannel::_q_connected() else if (networkLayerPreference == QAbstractSocket::IPv6Protocol) connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6; else { - if (socket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol) + if (absSocket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol) connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4; else connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6; @@ -875,7 +906,7 @@ void QHttpNetworkConnectionChannel::_q_connected() } // improve performance since we get the request sent by the kernel ASAP - //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); + //absSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1); // We have this commented out now. It did not have the effect we wanted. If we want to // do this properly, Qt has to combine multiple HTTP requests into one buffer // and send this to the kernel in one syscall and then the kernel immediately sends @@ -884,7 +915,7 @@ void QHttpNetworkConnectionChannel::_q_connected() // the requests into one TCP packet. // not sure yet if it helps, but it makes sense - socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); + absSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1); pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown; @@ -893,7 +924,7 @@ void QHttpNetworkConnectionChannel::_q_connected() if (!connectionPrivate->connectionMonitor.isMonitoring()) { // Now that we have a pair of addresses, we can start monitoring the // connection status to handle its loss properly. - if (connectionPrivate->connectionMonitor.setTargets(socket->localAddress(), socket->peerAddress())) + if (connectionPrivate->connectionMonitor.setTargets(absSocket->localAddress(), absSocket->peerAddress())) connectionPrivate->connectionMonitor.startMonitoring(); } } @@ -905,7 +936,7 @@ void QHttpNetworkConnectionChannel::_q_connected() if (!connection->sslContext()) { // this socket is making the 1st handshake for this connection, // we need to set the SSL context so new sockets can reuse it - if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket))) + if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(absSocket))) connection->setSslContext(std::move(socketSslContext)); } #endif @@ -927,7 +958,7 @@ void QHttpNetworkConnectionChannel::_q_connected() switchedToHttp2 = false; if (!reply) - connection->d_func()->dequeueRequest(socket); + connection->d_func()->dequeueRequest(absSocket); if (reply) { if (tryProtocolUpgrade) { @@ -940,6 +971,22 @@ void QHttpNetworkConnectionChannel::_q_connected() } } +void QHttpNetworkConnectionChannel::_q_connected_local_socket(QLocalSocket *localSocket) +{ + state = QHttpNetworkConnectionChannel::IdleState; + if (!reply) // No reply object, try to dequeue a request (which is paired with a reply): + connection->d_func()->dequeueRequest(localSocket); + if (reply) + sendRequest(); +} + +void QHttpNetworkConnectionChannel::_q_connected() +{ + if (auto *s = qobject_cast<QAbstractSocket *>(socket)) + _q_connected_abstract_socket(s); + else if (auto *s = qobject_cast<QLocalSocket *>(socket)) + _q_connected_local_socket(s); +} void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError) { @@ -1116,7 +1163,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket //signal emission triggered event loop if (!socket) state = QHttpNetworkConnectionChannel::IdleState; - else if (socket->state() == QAbstractSocket::UnconnectedState) + else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState) state = QHttpNetworkConnectionChannel::IdleState; else state = QHttpNetworkConnectionChannel::ClosingState; diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h index c42290feca..853b647ecc 100644 --- a/src/network/access/qhttpnetworkconnectionchannel_p.h +++ b/src/network/access/qhttpnetworkconnectionchannel_p.h @@ -19,6 +19,7 @@ #include <QtNetwork/qnetworkrequest.h> #include <QtNetwork/qnetworkreply.h> #include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qlocalsocket.h> #include <private/qobject_p.h> #include <qauthenticator.h> @@ -71,7 +72,7 @@ public: ClosingState = 16, BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|ClosingState) }; - QAbstractSocket *socket; + QIODevice *socket; bool ssl; bool isInitialized; ChannelState state; @@ -156,6 +157,8 @@ public: void _q_bytesWritten(qint64 bytes); // proceed sending void _q_readyRead(); // pending data to read void _q_disconnected(); // disconnected from host + void _q_connected_abstract_socket(QAbstractSocket *socket); + void _q_connected_local_socket(QLocalSocket *socket); void _q_connected(); // start sending request void _q_error(QAbstractSocket::SocketError); // error from socket #ifndef QT_NO_NETWORKPROXY |