From 1de244ea65f1b40c488fe92b29170c1b1d447233 Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Wed, 22 Jan 2014 14:54:21 +0100 Subject: network: add support for the SPDY protocol Currently the only supported SPDY version is 3.0. The feature needs to be enabled explicitly via QNetworkRequest::SpdyAllowedAttribute. Whether SPDY actually was used can be determined via QNetworkRequest::SpdyWasUsedAttribute from a QNetworkReply once it has been started (i.e. after the encrypted() signal has been received). Whether SPDY can be used will be determined during the SSL handshake through the TLS NPN extension (see separate commit). The following things from SPDY have not been enabled currently: * server push is not implemented, it has never been seen in the wild; in that case we just reject a stream pushed by the server, which is legit. * settings are not persisted across SPDY sessions. In practice this means that the server sends a small message upon session start telling us e.g. the number of concurrent connections. * SSL client certificates are not supported. Task-number: QTBUG-18714 [ChangeLog][QtNetwork] Added support for the SPDY protocol (version 3.0). Change-Id: I81bbe0495c24ed84e9cf8af3a9dbd63ca1e93d0d Reviewed-by: Richard J. Moore --- tests/manual/qnetworkreply/main.cpp | 220 ++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) (limited to 'tests/manual/qnetworkreply') diff --git a/tests/manual/qnetworkreply/main.cpp b/tests/manual/qnetworkreply/main.cpp index fe667e92a0..ff96f2598d 100644 --- a/tests/manual/qnetworkreply/main.cpp +++ b/tests/manual/qnetworkreply/main.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the test suite of the Qt Toolkit. @@ -53,6 +54,7 @@ #if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) #include "private/qsslsocket_p.h" +#include #endif #define BANDWIDTH_LIMIT_BYTES (1024*100) @@ -68,8 +70,16 @@ private slots: void setSslConfiguration_data(); void setSslConfiguration(); void uploadToFacebook(); + void spdy_data(); + void spdy(); + void spdyMultipleRequestsPerHost(); + +protected slots: + void spdyReplyFinished(); // only used by spdyMultipleRequestsPerHost test + private: QHttpMultiPart *createFacebookMultiPart(const QByteArray &accessToken); + QNetworkAccessManager m_manager; }; QNetworkReply *reply; @@ -104,6 +114,7 @@ protected: void tst_qnetworkreply::initTestCase() { + qRegisterMetaType(); // for QSignalSpy QVERIFY(QtNetworkSettings::verifyTestNetworkSettings()); } @@ -284,6 +295,215 @@ void tst_qnetworkreply::uploadToFacebook() } } +void tst_qnetworkreply::spdy_data() +{ + QTest::addColumn("host"); + QTest::addColumn("setAttribute"); + QTest::addColumn("enabled"); + QTest::addColumn("expectedProtocol"); + + QList hosts = QList() + << QStringLiteral("www.google.com") // sends SPDY and 30x redirect + << QStringLiteral("www.google.de") // sends SPDY and 200 OK + << QStringLiteral("mail.google.com") // sends SPDY and 200 OK + << QStringLiteral("www.youtube.com") // sends SPDY and 200 OK + << QStringLiteral("www.dropbox.com") // no SPDY, but NPN which selects HTTP + << QStringLiteral("www.facebook.com") // sends SPDY and 200 OK + << QStringLiteral("graph.facebook.com") // sends SPDY and 200 OK + << QStringLiteral("www.twitter.com") // sends SPDY and 30x redirect + << QStringLiteral("twitter.com") // sends SPDY and 200 OK + << QStringLiteral("api.twitter.com"); // sends SPDY and 200 OK + + foreach (const QString &host, hosts) { + QByteArray tag = host.toLocal8Bit(); + tag.append("-not-used"); + QTest::newRow(tag) + << QStringLiteral("https://") + host + << false + << false + << QByteArray(); + + tag = host.toLocal8Bit(); + tag.append("-disabled"); + QTest::newRow(tag) + << QStringLiteral("https://") + host + << true + << false + << QByteArray(); + + if (host != QStringLiteral("api.twitter.com")) { // they don't offer an API over HTTP + tag = host.toLocal8Bit(); + tag.append("-no-https-url"); + QTest::newRow(tag) + << QStringLiteral("http://") + host + << true + << true + << QByteArray(); + } + +#ifndef QT_NO_OPENSSL + tag = host.toLocal8Bit(); + tag.append("-enabled"); + QTest::newRow(tag) + << QStringLiteral("https://") + host + << true + << true + << (host == QStringLiteral("www.dropbox.com") + ? QByteArray(QSslConfiguration::NextProtocolHttp1_1) + : QByteArray(QSslConfiguration::NextProtocolSpdy3_0)); +#endif // QT_NO_OPENSSL + } +} + +void tst_qnetworkreply::spdy() +{ +#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + + m_manager.clearAccessCache(); + + QFETCH(QString, host); + QUrl url(host); + QNetworkRequest request(url); + + QFETCH(bool, setAttribute); + QFETCH(bool, enabled); + if (setAttribute) { + request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, QVariant(enabled)); + } + + QNetworkReply *reply = m_manager.get(request); + QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop())); + + QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged())); + QSignalSpy readyReadSpy(reply, SIGNAL(readyRead())); + QSignalSpy finishedSpy(reply, SIGNAL(finished())); + QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); + + QTestEventLoop::instance().enterLoop(15); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QFETCH(QByteArray, expectedProtocol); + + bool expectedSpdyUsed = (expectedProtocol == QSslConfiguration::NextProtocolSpdy3_0) + ? true : false; + QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), expectedSpdyUsed); + + QCOMPARE(metaDataChangedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 1); + QCOMPARE(finishedManagerSpy.count(), 1); + + QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl(); + QByteArray content = reply->readAll(); + + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QVERIFY(statusCode >= 200 && statusCode < 500); + if (statusCode == 200 || statusCode >= 400) { + QVERIFY(readyReadSpy.count() > 0); + QVERIFY(!content.isEmpty()); + } else if (statusCode >= 300 && statusCode < 400) { + QVERIFY(!redirectUrl.isEmpty()); + } + + QSslConfiguration::NextProtocolNegotiationStatus expectedStatus = + expectedProtocol.isNull() ? QSslConfiguration::NextProtocolNegotiationNone + : QSslConfiguration::NextProtocolNegotiationNegotiated; + QCOMPARE(reply->sslConfiguration().nextProtocolNegotiationStatus(), + expectedStatus); + + QCOMPARE(reply->sslConfiguration().nextNegotiatedProtocol(), expectedProtocol); +#else + QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old"); +#endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ... +} + +void tst_qnetworkreply::spdyReplyFinished() +{ + static int finishedCount = 0; + finishedCount++; + + if (finishedCount == 12) + QTestEventLoop::instance().exitLoop(); +} + +void tst_qnetworkreply::spdyMultipleRequestsPerHost() +{ +#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG) + + QList requests; + requests + << QNetworkRequest(QUrl("https://www.facebook.com")) + << QNetworkRequest(QUrl("https://www.facebook.com/images/fb_icon_325x325.png")) + + << QNetworkRequest(QUrl("https://www.google.de")) + << QNetworkRequest(QUrl("https://www.google.de/preferences?hl=de")) + << QNetworkRequest(QUrl("https://www.google.de/intl/de/policies/?fg=1")) + << QNetworkRequest(QUrl("https://www.google.de/intl/de/about.html?fg=1")) + << QNetworkRequest(QUrl("https://www.google.de/services/?fg=1")) + << QNetworkRequest(QUrl("https://www.google.de/intl/de/ads/?fg=1")) + + << QNetworkRequest(QUrl("https://i1.ytimg.com/li/tnHdj3df7iM/default.jpg")) + << QNetworkRequest(QUrl("https://i1.ytimg.com/li/7Dr1BKwqctY/default.jpg")) + << QNetworkRequest(QUrl("https://i1.ytimg.com/li/hfZhJdhTqX8/default.jpg")) + << QNetworkRequest(QUrl("https://i1.ytimg.com/vi/14Nprh8163I/hqdefault.jpg")) + ; + QList replies; + QList metaDataChangedSpies; + QList readyReadSpies; + QList finishedSpies; + + QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*))); + + foreach (QNetworkRequest request, requests) { + request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true); + QNetworkReply *reply = m_manager.get(request); + QObject::connect(reply, SIGNAL(finished()), this, SLOT(spdyReplyFinished())); + replies << reply; + QSignalSpy *metaDataChangedSpy = new QSignalSpy(reply, SIGNAL(metaDataChanged())); + metaDataChangedSpies << metaDataChangedSpy; + QSignalSpy *readyReadSpy = new QSignalSpy(reply, SIGNAL(readyRead())); + readyReadSpies << readyReadSpy; + QSignalSpy *finishedSpy = new QSignalSpy(reply, SIGNAL(finished())); + finishedSpies << finishedSpy; + } + + QCOMPARE(requests.count(), replies.count()); + + QTestEventLoop::instance().enterLoop(15); + QVERIFY(!QTestEventLoop::instance().timeout()); + + QCOMPARE(finishedManagerSpy.count(), requests.count()); + + for (int a = 0; a < replies.count(); ++a) { + + QCOMPARE(replies.at(a)->sslConfiguration().nextProtocolNegotiationStatus(), + QSslConfiguration::NextProtocolNegotiationNegotiated); + QCOMPARE(replies.at(a)->sslConfiguration().nextNegotiatedProtocol(), + QByteArray(QSslConfiguration::NextProtocolSpdy3_0)); + + QCOMPARE(replies.at(a)->error(), QNetworkReply::NoError); + QCOMPARE(replies.at(a)->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true); + QCOMPARE(replies.at(a)->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true); + QCOMPARE(replies.at(a)->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200); + + QByteArray content = replies.at(a)->readAll(); + QVERIFY(content.count() > 0); + + QCOMPARE(metaDataChangedSpies.at(a)->count(), 1); + metaDataChangedSpies.at(a)->deleteLater(); + + QCOMPARE(finishedSpies.at(a)->count(), 1); + finishedSpies.at(a)->deleteLater(); + + QVERIFY(readyReadSpies.at(a)->count() > 0); + readyReadSpies.at(a)->deleteLater(); + + replies.at(a)->deleteLater(); + } +#else + QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old"); +#endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ... +} + QTEST_MAIN(tst_qnetworkreply) #include "main.moc" -- cgit v1.2.3