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:
Mårten Nordheim 2017-10-10 15:54:04 +02:00 committed by Timur Pocheptsov
parent 377d2502e3
commit 8a39384e90
3 changed files with 87 additions and 21 deletions

View File

@ -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)),

View File

@ -160,6 +160,8 @@ signals:
class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
{
bool startWaitForSession(QSharedPointer<QNetworkSession> &session);
public:
static QHttpNetworkRequest::Priority convert(const QNetworkRequest::Priority& prio);

View File

@ -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