summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Hartmann <phartmann@blackberry.com>2013-05-03 15:57:40 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-06-03 23:16:08 +0200
commit1c901913c0af79b2bbde1b9da71f5267cb4fc76a (patch)
treee83c7e1c10cc6262931f1d03b9deb06cf1585bae
parent15da0a5af20fe6771bcb94ef8d46edbd5c8fb64c (diff)
QNetworkAccessManager: add public methods to pre-TCP/pre-SSL-connect
If an app knows it needs to connect to a host beforehand, it can "warm up" the connection cache by making DNS lookup, TCP (and if needed SSL) handshake before the actual HTTP request is sent. When the HTTP request is made, it will be considerably faster when there is already a working connection. Here are some typical results from the benchmark: * Linux desktop with Ethernet: "http://www.google.com" full request: 279 ms, pre-connect request: 61 ms, difference: 218 ms "https://www.google.com" full request: 344 ms, pre-connect request: 60 ms, difference: 284 ms * mobile device (BlackBerry 10) with Wifi: "https://www.google.com" full request: 898 ms, pre-connect request: 159 ms, difference: 739 ms "http://www.google.com" full request: 707 ms, pre-connect request: 200 ms, difference: 507 ms Task-number: QTBUG-30771 Change-Id: I3566b7f08216ab93a39e2024ae7d1ceb7ae21891 Reviewed-by: Jonas Gastal <gastal@intel.com> Reviewed-by: Richard J. Moore <rich@kde.org>
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp10
-rw-r--r--src/network/access/qhttpnetworkreply.cpp5
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp8
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp57
-rw-r--r--src/network/access/qnetworkaccessmanager.h9
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp4
-rw-r--r--tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro2
-rw-r--r--tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp109
8 files changed, 197 insertions, 7 deletions
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 4b8fe8aca7..53436b1769 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -205,6 +205,16 @@ bool QHttpNetworkConnectionChannel::sendRequest()
// _q_connected or _q_encrypted
return false;
}
+ QString scheme = request.url().scheme();
+ if (scheme == QLatin1String("preconnect-http")
+ || scheme == QLatin1String("preconnect-https")) {
+ state = QHttpNetworkConnectionChannel::IdleState;
+ reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
+ allDone();
+ reply = 0; // so we can reuse this channel
+ return true; // we have a working connection and are done
+ }
+
written = 0; // excluding the header
bytesTotal = 0;
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
index eb8a8869cc..1b9e1f5a53 100644
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -290,6 +290,11 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl)
#endif
{
+ QString scheme = newUrl.scheme();
+ if (scheme == QLatin1String("preconnect-http")
+ || scheme == QLatin1String("preconnect-https"))
+ // make sure we do not close the socket after preconnecting
+ connectionCloseEnabled = false;
}
QHttpNetworkReplyPrivate::~QHttpNetworkReplyPrivate()
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index a2cee48b22..ee3911c72c 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -107,8 +107,14 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy)
{
QString result;
QUrl copy = url;
- bool isEncrypted = copy.scheme().toLower() == QLatin1String("https");
+ QString scheme = copy.scheme().toLower();
+ bool isEncrypted = scheme == QLatin1String("https");
copy.setPort(copy.port(isEncrypted ? 443 : 80));
+ if (scheme == QLatin1String("preconnect-http")) {
+ copy.setScheme(QLatin1String("http"));
+ } else if (scheme == QLatin1String("preconnect-https")) {
+ copy.setScheme(QLatin1String("https"));
+ }
result = copy.toString(QUrl::RemoveUserInfo | QUrl::RemovePath |
QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::FullyEncoded);
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 13fd167f80..91655ef485 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -96,9 +96,11 @@ bool getProxyAuth(const QString& proxyHostname, const QString &scheme, QString&
SecProtocolType protocolType = kSecProtocolTypeAny;
if (scheme.compare(QLatin1String("ftp"),Qt::CaseInsensitive)==0) {
protocolType = kSecProtocolTypeFTP;
- } else if (scheme.compare(QLatin1String("http"),Qt::CaseInsensitive)==0) {
+ } else if (scheme.compare(QLatin1String("http"),Qt::CaseInsensitive)==0
+ || scheme.compare(QLatin1String("preconnect-http"),Qt::CaseInsensitive)==0) {
protocolType = kSecProtocolTypeHTTP;
- } else if (scheme.compare(QLatin1String("https"),Qt::CaseInsensitive)==0) {
+ } else if (scheme.compare(QLatin1String("https"),Qt::CaseInsensitive)==0
+ || scheme.compare(QLatin1String("preconnect-https"),Qt::CaseInsensitive)==0) {
protocolType = kSecProtocolTypeHTTPS;
}
QByteArray proxyHostnameUtf8(proxyHostname.toUtf8());
@@ -968,6 +970,53 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess
}
}
+#ifndef QT_NO_SSL
+/*!
+ \since 5.2
+
+ Initiates a connection to the host given by \a hostName at port \a port, using
+ \a sslConfiguration. This function is useful to complete the TCP and SSL handshake
+ to a host before the HTTPS request is made, resulting in a lower network latency.
+
+ \note This function has no possibility to report errors.
+
+ \sa connectToHost(), get(), post(), put(), deleteResource()
+*/
+void QNetworkAccessManager::connectToHostEncrypted(const QString &hostName, quint16 port,
+ const QSslConfiguration &sslConfiguration)
+{
+ QUrl url;
+ url.setHost(hostName);
+ url.setPort(port);
+ url.setScheme(QLatin1String("preconnect-https"));
+ QNetworkRequest request(url);
+ if (sslConfiguration != QSslConfiguration::defaultConfiguration())
+ request.setSslConfiguration(sslConfiguration);
+ get(request);
+}
+#endif
+
+/*!
+ \since 5.2
+
+ Initiates a connection to the host given by \a hostName at port \a port.
+ This function is useful to complete the TCP handshake
+ to a host before the HTTP request is made, resulting in a lower network latency.
+
+ \note This function has no possibility to report errors.
+
+ \sa connectToHostEncrypted(), get(), post(), put(), deleteResource()
+*/
+void QNetworkAccessManager::connectToHost(const QString &hostName, quint16 port)
+{
+ QUrl url;
+ url.setHost(hostName);
+ url.setPort(port);
+ url.setScheme(QLatin1String("preconnect-http"));
+ QNetworkRequest request(url);
+ get(request);
+}
+
/*!
\internal
@@ -1112,9 +1161,9 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
#ifndef QT_NO_HTTP
// Since Qt 5 we use the new QNetworkReplyHttpImpl
- if (scheme == QLatin1String("http")
+ if (scheme == QLatin1String("http") || scheme == QLatin1String("preconnect-http")
#ifndef QT_NO_SSL
- || scheme == QLatin1String("https")
+ || scheme == QLatin1String("https") || scheme == QLatin1String("preconnect-https")
#endif
) {
QNetworkReplyHttpImpl *reply = new QNetworkReplyHttpImpl(this, request, op, outgoingData);
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index 46e46c49ab..67b9bbcb07 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -43,6 +43,9 @@
#define QNETWORKACCESSMANAGER_H
#include <QtCore/QObject>
+#ifndef QT_NO_SSL
+#include <QtNetwork/QSslConfiguration>
+#endif
QT_BEGIN_NAMESPACE
@@ -135,6 +138,12 @@ public:
NetworkAccessibility networkAccessible() const;
#endif
+#ifndef QT_NO_SSL
+ void connectToHostEncrypted(const QString &hostName, quint16 port = 443,
+ const QSslConfiguration &sslConfiguration = QSslConfiguration::defaultConfiguration());
+#endif
+ void connectToHost(const QString &hostName, quint16 port = 80);
+
Q_SIGNALS:
#ifndef QT_NO_NETWORKPROXY
void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index c04421e5c7..443339f029 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -629,7 +629,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
QUrl url = request.url();
httpRequest.setUrl(url);
- bool ssl = url.scheme().toLower() == QLatin1String("https");
+ QString scheme = url.scheme().toLower();
+ bool ssl = (scheme == QLatin1String("https")
+ || scheme == QLatin1String("preconnect-https"));
q->setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, ssl);
httpRequest.setSsl(ssl);
diff --git a/tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro b/tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro
index 7031b83210..92d20f50d0 100644
--- a/tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro
+++ b/tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro
@@ -2,7 +2,7 @@ TEMPLATE = app
TARGET = tst_bench_qnetworkreply
QT -= gui
-QT += network testlib
+QT += core-private network network-private testlib
CONFIG += release
diff --git a/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp
index f62ce6bf5c..860bd3cf4d 100644
--- a/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -50,6 +50,9 @@
#include <QtNetwork/qtcpserver.h>
#include "../../../../auto/network-settings.h"
+#ifdef QT_BUILD_INTERNAL
+#include <QtNetwork/private/qhostinfo_p.h>
+#endif
Q_DECLARE_METATYPE(QSharedPointer<char>)
@@ -460,6 +463,7 @@ private slots:
#ifndef QT_NO_SSL
void echoPerformance_data();
void echoPerformance();
+ void preConnectEncrypted();
#endif
void downloadPerformance();
@@ -472,9 +476,12 @@ private slots:
void httpDownloadPerformanceDownloadBuffer();
void httpsRequestChain();
void httpsUpload();
+ void preConnect();
private:
void runHttpsUploadRequest(const QByteArray &data, const QNetworkRequest &request);
+ QPair<QNetworkReply *, qint64> runGetRequest(QNetworkAccessManager *manager,
+ const QNetworkRequest &request);
};
void tst_qnetworkreply::initTestCase()
@@ -495,6 +502,19 @@ void tst_qnetworkreply::httpLatency()
}
}
+QPair<QNetworkReply *, qint64> tst_qnetworkreply::runGetRequest(
+ QNetworkAccessManager *manager, const QNetworkRequest &request)
+{
+ QElapsedTimer timer;
+ timer.start();
+ QNetworkReply *reply = manager->get(request);
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
+ connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
+ QTestEventLoop::instance().enterLoop(20);
+ qint64 elapsed = timer.elapsed();
+ return qMakePair(reply, elapsed);
+}
+
#ifndef QT_NO_SSL
void tst_qnetworkreply::echoPerformance_data()
{
@@ -527,6 +547,51 @@ void tst_qnetworkreply::echoPerformance()
delete reply;
}
}
+
+void tst_qnetworkreply::preConnectEncrypted()
+{
+ QString hostName = QLatin1String("www.google.com");
+
+ QNetworkAccessManager manager;
+ QNetworkRequest request(QUrl("https://" + hostName));
+
+ // make sure we have a full request including
+ // DNS lookup, TCP and SSL handshakes
+#ifdef QT_BUILD_INTERNAL
+ qt_qhostinfo_clear_cache();
+#else
+ qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
+#endif
+
+ // first, benchmark a normal request
+ QPair<QNetworkReply *, qint64> normalResult = runGetRequest(&manager, request);
+ QNetworkReply *normalReply = normalResult.first;
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QVERIFY(normalReply->error() == QNetworkReply::NoError);
+ qint64 normalElapsed = normalResult.second;
+
+ // clear all caches again
+#ifdef QT_BUILD_INTERNAL
+ qt_qhostinfo_clear_cache();
+#else
+ qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
+#endif
+ manager.clearAccessCache();
+
+ // now try to make the connection beforehand
+ manager.connectToHostEncrypted(hostName);
+ QTestEventLoop::instance().enterLoop(2);
+
+ // now make another request and hopefully use the existing connection
+ QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);
+ QNetworkReply *preConnectReply = normalResult.first;
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QVERIFY(preConnectReply->error() == QNetworkReply::NoError);
+ qint64 preConnectElapsed = preConnectResult.second;
+ qDebug() << request.url().toString() << "full request:" << normalElapsed
+ << "ms, pre-connect request:" << preConnectElapsed << "ms, difference:"
+ << (normalElapsed - preConnectElapsed) << "ms";
+}
#endif
void tst_qnetworkreply::downloadPerformance()
@@ -852,6 +917,50 @@ void tst_qnetworkreply::httpsUpload()
}
}
+void tst_qnetworkreply::preConnect()
+{
+ QString hostName = QLatin1String("www.google.com");
+
+ QNetworkAccessManager manager;
+ QNetworkRequest request(QUrl("http://" + hostName));
+
+ // make sure we have a full request including
+ // DNS lookup and TCP handshake
+#ifdef QT_BUILD_INTERNAL
+ qt_qhostinfo_clear_cache();
+#else
+ qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
+#endif
+
+ // first, benchmark a normal request
+ QPair<QNetworkReply *, qint64> normalResult = runGetRequest(&manager, request);
+ QNetworkReply *normalReply = normalResult.first;
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QVERIFY(normalReply->error() == QNetworkReply::NoError);
+ qint64 normalElapsed = normalResult.second;
+
+ // clear all caches again
+#ifdef QT_BUILD_INTERNAL
+ qt_qhostinfo_clear_cache();
+#else
+ qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
+#endif
+ manager.clearAccessCache();
+
+ // now try to make the connection beforehand
+ manager.connectToHost(hostName);
+ QTestEventLoop::instance().enterLoop(2);
+
+ // now make another request and hopefully use the existing connection
+ QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);
+ QNetworkReply *preConnectReply = normalResult.first;
+ QVERIFY(!QTestEventLoop::instance().timeout());
+ QVERIFY(preConnectReply->error() == QNetworkReply::NoError);
+ qint64 preConnectElapsed = preConnectResult.second;
+ qDebug() << request.url().toString() << "full request:" << normalElapsed
+ << "ms, pre-connect request:" << preConnectElapsed << "ms, difference:"
+ << (normalElapsed - preConnectElapsed) << "ms";
+}
QTEST_MAIN(tst_qnetworkreply)