Open a session during redirects when needed
In some cases when a session isn't needed (i.e. for localhost), the session is not opened at all. If a program (e.g. our tests) redirects from localhost to a different system (e.g. the qt network test servers, or the internet) it will wait for a session forever. So, we need to check if a session is needed for the redirect-target and then open one. It is usually opened in QNetworkReplyHttpImplPrivate::_q_startOperation Change-Id: Id3b78182a3fb3f63f0235ecb1fb665df8bd0c4ca Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
377d2502e3
commit
8a39384e90
@ -165,6 +165,16 @@ static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &hea
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_CONFIG(bearermanagement)
|
||||
static bool isSessionNeeded(const QUrl &url)
|
||||
{
|
||||
// Connections to the local machine does not require a session
|
||||
QString host = url.host().toLower();
|
||||
return !QHostAddress(host).isLoopback() && host != QLatin1String("localhost")
|
||||
&& host != QSysInfo::machineHostName().toLower();
|
||||
}
|
||||
#endif // bearer management
|
||||
|
||||
QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manager,
|
||||
const QNetworkRequest& request,
|
||||
QNetworkAccessManager::Operation& operation,
|
||||
@ -1189,6 +1199,24 @@ void QNetworkReplyHttpImplPrivate::followRedirect()
|
||||
if (managerPrivate->thread)
|
||||
managerPrivate->thread->disconnect();
|
||||
|
||||
#if QT_CONFIG(bearermanagement)
|
||||
// If the original request didn't need a session (i.e. it was to localhost)
|
||||
// then we might not have a session open, to which to redirect, if the
|
||||
// new URL is remote. When this happens, we need to open the session now:
|
||||
if (managerPrivate && isSessionNeeded(url)) {
|
||||
if (auto session = managerPrivate->getNetworkSession()) {
|
||||
if (session->state() != QNetworkSession::State::Connected || !session->isOpen()) {
|
||||
startWaitForSession(session);
|
||||
// Need to set 'request' to the redirectRequest so that when QNAM restarts
|
||||
// the request after the session starts it will not repeat the previous request.
|
||||
request = redirectRequest;
|
||||
// Return now, QNAM will start the request when the session has started.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // bearer management
|
||||
|
||||
QMetaObject::invokeMethod(q, "start", Qt::QueuedConnection,
|
||||
Q_ARG(QNetworkRequest, redirectRequest));
|
||||
}
|
||||
@ -1770,10 +1798,8 @@ bool QNetworkReplyHttpImplPrivate::start(const QNetworkRequest &newHttpRequest)
|
||||
}
|
||||
|
||||
// This is not ideal.
|
||||
const QString host = url.host();
|
||||
if (host == QLatin1String("localhost") ||
|
||||
QHostAddress(host).isLoopback()) {
|
||||
// Don't need an open session for localhost access.
|
||||
if (!isSessionNeeded(url)) {
|
||||
// Don't need to check for an open session if we don't need one.
|
||||
postRequest(newHttpRequest);
|
||||
return true;
|
||||
}
|
||||
@ -1797,6 +1823,32 @@ bool QNetworkReplyHttpImplPrivate::start(const QNetworkRequest &newHttpRequest)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool QNetworkReplyHttpImplPrivate::startWaitForSession(QSharedPointer<QNetworkSession> &session)
|
||||
{
|
||||
Q_Q(QNetworkReplyHttpImpl);
|
||||
state = WaitingForSession;
|
||||
|
||||
if (session) {
|
||||
QObject::connect(session.data(), SIGNAL(error(QNetworkSession::SessionError)),
|
||||
q, SLOT(_q_networkSessionFailed()), Qt::QueuedConnection);
|
||||
|
||||
if (!session->isOpen()) {
|
||||
QVariant isBackground = request.attribute(QNetworkRequest::BackgroundRequestAttribute,
|
||||
QVariant::fromValue(false));
|
||||
session->setSessionProperty(QStringLiteral("ConnectInBackground"), isBackground);
|
||||
session->open();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const Qt::ConnectionType connection = synchronous ? Qt::DirectConnection : Qt::QueuedConnection;
|
||||
qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
|
||||
QMetaObject::invokeMethod(q, "_q_error", connection,
|
||||
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::NetworkSessionFailedError),
|
||||
Q_ARG(QString, QCoreApplication::translate("QNetworkReply", "Network session error.")));
|
||||
QMetaObject::invokeMethod(q, "_q_finished", connection);
|
||||
return false;
|
||||
}
|
||||
|
||||
void QNetworkReplyHttpImplPrivate::_q_startOperation()
|
||||
{
|
||||
Q_Q(QNetworkReplyHttpImpl);
|
||||
@ -1824,24 +1876,8 @@ void QNetworkReplyHttpImplPrivate::_q_startOperation()
|
||||
// backend failed to start because the session state is not Connected.
|
||||
// QNetworkAccessManager will call reply->backend->start() again for us when the session
|
||||
// state changes.
|
||||
state = WaitingForSession;
|
||||
|
||||
if (session) {
|
||||
QObject::connect(session.data(), SIGNAL(error(QNetworkSession::SessionError)),
|
||||
q, SLOT(_q_networkSessionFailed()), Qt::QueuedConnection);
|
||||
|
||||
if (!session->isOpen()) {
|
||||
session->setSessionProperty(QStringLiteral("ConnectInBackground"), isBackground);
|
||||
session->open();
|
||||
}
|
||||
} else {
|
||||
qWarning("Backend is waiting for QNetworkSession to connect, but there is none!");
|
||||
QMetaObject::invokeMethod(q, "_q_error", synchronous ? Qt::DirectConnection : Qt::QueuedConnection,
|
||||
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::NetworkSessionFailedError),
|
||||
Q_ARG(QString, QCoreApplication::translate("QNetworkReply", "Network session error.")));
|
||||
QMetaObject::invokeMethod(q, "_q_finished", synchronous ? Qt::DirectConnection : Qt::QueuedConnection);
|
||||
if (!startWaitForSession(session))
|
||||
return;
|
||||
}
|
||||
} else if (session) {
|
||||
QObject::connect(session.data(), SIGNAL(stateChanged(QNetworkSession::State)),
|
||||
q, SLOT(_q_networkSessionStateChanged(QNetworkSession::State)),
|
||||
|
@ -160,6 +160,8 @@ signals:
|
||||
|
||||
class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
|
||||
{
|
||||
bool startWaitForSession(QSharedPointer<QNetworkSession> &session);
|
||||
|
||||
public:
|
||||
|
||||
static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio);
|
||||
|
@ -490,6 +490,7 @@ private Q_SLOTS:
|
||||
void ioHttpCookiesDuringRedirect();
|
||||
void ioHttpRedirect_data();
|
||||
void ioHttpRedirect();
|
||||
void ioHttpRedirectFromLocalToRemote();
|
||||
#ifndef QT_NO_SSL
|
||||
void putWithServerClosingConnectionImmediately();
|
||||
#endif
|
||||
@ -8490,6 +8491,33 @@ void tst_QNetworkReply::ioHttpRedirect()
|
||||
QVERIFY(validateRedirectedResponseHeaders(reply));
|
||||
}
|
||||
|
||||
void tst_QNetworkReply::ioHttpRedirectFromLocalToRemote()
|
||||
{
|
||||
QUrl targetUrl("http://" + QtNetworkSettings::serverName() + "/qtest/rfc3252.txt");
|
||||
|
||||
QString redirectReply = tempRedirectReplyStr().arg(targetUrl.toString());
|
||||
MiniHttpServer redirectServer(redirectReply.toLatin1(), false);
|
||||
QUrl url("http://localhost/");
|
||||
url.setPort(redirectServer.serverPort());
|
||||
|
||||
QFile reference(testDataDir + "/rfc3252.txt");
|
||||
QVERIFY(reference.open(QIODevice::ReadOnly));
|
||||
QNetworkRequest request(url);
|
||||
|
||||
auto oldRedirectPolicy = manager.redirectPolicy();
|
||||
manager.setRedirectPolicy(QNetworkRequest::RedirectPolicy::NoLessSafeRedirectPolicy);
|
||||
QNetworkReplyPtr reply(manager.get(request));
|
||||
// Restore previous policy
|
||||
manager.setRedirectPolicy(oldRedirectPolicy);
|
||||
|
||||
QCOMPARE(waitForFinish(reply), int(Success));
|
||||
|
||||
QCOMPARE(reply->url(), targetUrl);
|
||||
QCOMPARE(reply->error(), QNetworkReply::NoError);
|
||||
QCOMPARE(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(), reference.size());
|
||||
QCOMPARE(reply->readAll(), reference.readAll());
|
||||
}
|
||||
|
||||
#ifndef QT_NO_SSL
|
||||
|
||||
class PutWithServerClosingConnectionImmediatelyHandler: public QObject
|
||||
|
Loading…
Reference in New Issue
Block a user