diff options
-rw-r--r-- | src/network/access/qhttpnetworkconnectionchannel.cpp | 10 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkreply.cpp | 5 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate.cpp | 8 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.cpp | 57 | ||||
-rw-r--r-- | src/network/access/qnetworkaccessmanager.h | 9 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 4 | ||||
-rw-r--r-- | tests/benchmarks/network/access/qnetworkreply/qnetworkreply.pro | 2 | ||||
-rw-r--r-- | tests/benchmarks/network/access/qnetworkreply/tst_qnetworkreply.cpp | 109 |
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) |