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 <rich@kde.org>
This commit is contained in:
Peter Hartmann 2014-01-22 14:54:21 +01:00 committed by The Qt Project
parent 42789d04a3
commit 1de244ea65
23 changed files with 2888 additions and 86 deletions

View File

@ -9,6 +9,7 @@ HEADERS += \
access/qhttpnetworkconnectionchannel_p.h \
access/qabstractprotocolhandler_p.h \
access/qhttpprotocolhandler_p.h \
access/qspdyprotocolhandler_p.h \
access/qnetworkaccessauthenticationmanager_p.h \
access/qnetworkaccessmanager.h \
access/qnetworkaccessmanager_p.h \
@ -47,6 +48,7 @@ SOURCES += \
access/qhttpnetworkconnectionchannel.cpp \
access/qabstractprotocolhandler.cpp \
access/qhttpprotocolhandler.cpp \
access/qspdyprotocolhandler.cpp \
access/qnetworkaccessauthenticationmanager.cpp \
access/qnetworkaccessmanager.cpp \
access/qnetworkaccesscache.cpp \

View File

@ -68,7 +68,7 @@
QT_BEGIN_NAMESPACE
const int QHttpNetworkConnectionPrivate::defaultChannelCount = 6;
const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6;
// The pipeline length. So there will be 4 requests in flight.
const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3;
@ -77,20 +77,29 @@ const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3;
const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2;
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt)
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName,
quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type)
: state(RunningState),
networkLayerState(Unknown),
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true),
channelCount(defaultChannelCount)
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true)
#ifndef QT_NO_SSL
, channelCount((type == QHttpNetworkConnection::ConnectionTypeSPDY) ? 1 : defaultHttpChannelCount)
#else
, channelCount(defaultHttpChannelCount)
#endif // QT_NO_SSL
#ifndef QT_NO_NETWORKPROXY
, networkProxy(QNetworkProxy::NoProxy)
#endif
, preConnectRequests(0)
, connectionType(type)
{
channels = new QHttpNetworkConnectionChannel[channelCount];
}
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt)
QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName,
quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type)
: state(RunningState), networkLayerState(Unknown),
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true),
channelCount(channelCount)
@ -98,6 +107,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 channelCoun
, networkProxy(QNetworkProxy::NoProxy)
#endif
, preConnectRequests(0)
, connectionType(type)
{
channels = new QHttpNetworkConnectionChannel[channelCount];
}
@ -546,15 +556,24 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::queueRequest(const QHttpNetwor
if (request.isPreConnect())
preConnectRequests++;
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
break;
case QHttpNetworkRequest::NormalPriority:
case QHttpNetworkRequest::LowPriority:
lowPriorityQueue.prepend(pair);
break;
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP) {
switch (request.priority()) {
case QHttpNetworkRequest::HighPriority:
highPriorityQueue.prepend(pair);
break;
case QHttpNetworkRequest::NormalPriority:
case QHttpNetworkRequest::LowPriority:
lowPriorityQueue.prepend(pair);
break;
}
}
#ifndef QT_NO_SSL
else { // SPDY
if (!pair.second->d_func()->requestIsPrepared)
prepareRequest(pair);
channels[0].spdyRequestsToSend.insertMulti(request.priority(), pair);
}
#endif // QT_NO_SSL
// For Happy Eyeballs the networkLayerState is set to Unknown
// untill we have started the first connection attempt. So no
@ -900,17 +919,39 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// dequeue new ones
// return fast if there is nothing to do
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
// try to get a free AND connected socket
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
switch (connectionType) {
case QHttpNetworkConnection::ConnectionTypeHTTP: {
// return fast if there is nothing to do
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
// try to get a free AND connected socket
for (int i = 0; i < channelCount; ++i) {
if (channels[i].socket) {
if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
}
}
break;
}
case QHttpNetworkConnection::ConnectionTypeSPDY: {
#ifndef QT_NO_SSL
if (channels[0].spdyRequestsToSend.isEmpty())
return;
if (networkLayerState == IPv4)
channels[0].networkLayerPreference = QAbstractSocket::IPv4Protocol;
else if (networkLayerState == IPv6)
channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[0].ensureConnection();
if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
&& !channels[0].pendingEncrypt)
channels[0].sendRequest();
#endif // QT_NO_SSL
break;
}
}
// try to push more into all sockets
@ -1059,7 +1100,19 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(QHostInfo info)
if (dequeueRequest(channels[0].socket)) {
emitReplyError(channels[0].socket, channels[0].reply, QNetworkReply::HostNotFoundError);
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
} else {
}
#ifndef QT_NO_SSL
else if (connectionType == QHttpNetworkConnection::ConnectionTypeSPDY) {
QList<HttpMessagePair> spdyPairs = channels[0].spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
// emit error for all replies
QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
Q_ASSERT(currentReply);
emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError);
}
}
#endif // QT_NO_SSL
else {
// Should not happen
qWarning() << "QHttpNetworkConnectionPrivate::_q_hostLookupFinished could not dequeu request";
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
@ -1127,31 +1180,41 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
}
#ifndef QT_NO_BEARERMANAGEMENT
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType connectionType,
QObject *parent, QSharedPointer<QNetworkSession> networkSession)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt, connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->networkSession = networkSession;
d->init();
}
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent, QSharedPointer<QNetworkSession> networkSession)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent)
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, QObject *parent,
QSharedPointer<QNetworkSession> networkSession,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->networkSession = networkSession;
d->init();
}
#else
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt)), parent)
QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt , connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->init();
}
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, QObject *parent)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt)), parent)
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
quint16 port, bool encrypt, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
: QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
d->init();
@ -1225,6 +1288,17 @@ QNetworkProxy QHttpNetworkConnection::transparentProxy() const
}
#endif
QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType()
{
Q_D(QHttpNetworkConnection);
return d->connectionType;
}
void QHttpNetworkConnection::setConnectionType(ConnectionType type)
{
Q_D(QHttpNetworkConnection);
d->connectionType = type;
}
// SSL support below
#ifndef QT_NO_SSL
@ -1299,7 +1373,23 @@ void QHttpNetworkConnectionPrivate::emitProxyAuthenticationRequired(const QHttpN
// Also pause the connection because socket notifiers may fire while an user
// dialog is displaying
pauseConnection();
emit chan->reply->proxyAuthenticationRequired(proxy, auth);
QHttpNetworkReply *reply;
#ifndef QT_NO_SSL
if (connectionType == QHttpNetworkConnection::ConnectionTypeSPDY) {
// we choose the reply to emit the proxyAuth signal from somewhat arbitrarily,
// but that does not matter because the signal will ultimately be emitted
// by the QNetworkAccessManager.
Q_ASSERT(chan->spdyRequestsToSend.count() > 0);
reply = chan->spdyRequestsToSend.values().first().second;
} else { // HTTP
#endif // QT_NO_SSL
reply = chan->reply;
#ifndef QT_NO_SSL
}
#endif // QT_NO_SSL
Q_ASSERT(reply);
emit reply->proxyAuthenticationRequired(proxy, auth);
resumeConnection();
int i = indexOf(chan->socket);
copyCredentials(i, auth, true);

View File

@ -94,12 +94,27 @@ class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject
Q_OBJECT
public:
enum ConnectionType {
ConnectionTypeHTTP,
ConnectionTypeSPDY
};
#ifndef QT_NO_BEARERMANAGEMENT
explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>());
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>());
explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false,
ConnectionType connectionType = ConnectionTypeHTTP,
QObject *parent = 0, QSharedPointer<QNetworkSession> networkSession
= QSharedPointer<QNetworkSession>());
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80,
bool encrypt = false, QObject *parent = 0,
QSharedPointer<QNetworkSession> networkSession = QSharedPointer<QNetworkSession>(),
ConnectionType connectionType = ConnectionTypeHTTP);
#else
explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0);
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80, bool encrypt = false, QObject *parent = 0);
explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false,
QObject *parent = 0,
ConnectionType connectionType = ConnectionTypeHTTP);
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80,
bool encrypt = false, QObject *parent = 0,
ConnectionType connectionType = ConnectionTypeHTTP);
#endif
~QHttpNetworkConnection();
@ -123,6 +138,9 @@ public:
QHttpNetworkConnectionChannel *channels() const;
ConnectionType connectionType();
void setConnectionType(ConnectionType type);
#ifndef QT_NO_SSL
void setSslConfiguration(const QSslConfiguration &config);
void ignoreSslErrors(int channel = -1);
@ -140,6 +158,7 @@ private:
friend class QHttpNetworkReplyPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
friend class QSpdyProtocolHandler;
Q_PRIVATE_SLOT(d_func(), void _q_startNextRequest())
Q_PRIVATE_SLOT(d_func(), void _q_hostLookupFinished(QHostInfo))
@ -155,7 +174,7 @@ class QHttpNetworkConnectionPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QHttpNetworkConnection)
public:
static const int defaultChannelCount;
static const int defaultHttpChannelCount;
static const int defaultPipelineLength;
static const int defaultRePipelineLength;
@ -172,8 +191,10 @@ public:
IPv4or6
};
QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt);
QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt);
QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type);
QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType type);
~QHttpNetworkConnectionPrivate();
void init();
@ -245,6 +266,8 @@ public:
int preConnectRequests;
QHttpNetworkConnection::ConnectionType connectionType;
#ifndef QT_NO_SSL
QSharedPointer<QSslContext> sslContext;
#endif

View File

@ -50,6 +50,7 @@
#ifndef QT_NO_HTTP
#include <private/qhttpprotocolhandler_p.h>
#include <private/qspdyprotocolhandler_p.h>
#ifndef QT_NO_SSL
# include <QtNetwork/qsslkey.h>
@ -166,9 +167,12 @@ void QHttpNetworkConnectionChannel::init()
if (!sslConfiguration.isNull())
sslSocket->setSslConfiguration(sslConfiguration);
} else {
#endif // QT_NO_SSL
protocolHandler.reset(new QHttpProtocolHandler(this));
#ifndef QT_NO_SSL
}
#endif
protocolHandler.reset(new QHttpProtocolHandler(this));
#ifndef QT_NO_NETWORKPROXY
if (proxy.type() != QNetworkProxy::NoProxy)
@ -879,6 +883,18 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
if (protocolHandler)
protocolHandler->setReply(0);
}
#ifndef QT_NO_SSL
else if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
// emit error for all replies
QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
Q_ASSERT(currentReply);
emit currentReply->finishedWithError(errorCode, errorString);
}
}
#endif // QT_NO_SSL
// send the next request
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
@ -889,11 +905,19 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
#ifndef QT_NO_NETWORKPROXY
void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
{
// Need to dequeue the request before we can emit the error.
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (reply)
#ifndef QT_NO_SSL
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
} else { // HTTP
#endif // QT_NO_SSL
// Need to dequeue the request before we can emit the error.
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (reply)
connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
#ifndef QT_NO_SSL
}
#endif // QT_NO_SSL
}
#endif
@ -905,16 +929,85 @@ void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
#ifndef QT_NO_SSL
void QHttpNetworkConnectionChannel::_q_encrypted()
{
QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
Q_ASSERT(sslSocket);
if (!protocolHandler) {
switch (sslSocket->sslConfiguration().nextProtocolNegotiationStatus()) {
case QSslConfiguration::NextProtocolNegotiationNegotiated: {
QByteArray nextProtocol = sslSocket->sslConfiguration().nextNegotiatedProtocol();
if (nextProtocol == QSslConfiguration::NextProtocolHttp1_1) {
// fall through to create a QHttpProtocolHandler
} else if (nextProtocol == QSslConfiguration::NextProtocolSpdy3_0) {
protocolHandler.reset(new QSpdyProtocolHandler(this));
connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeSPDY);
// no need to re-queue requests, if SPDY was enabled on the request it
// has gone to the SPDY queue already
break;
} else {
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"detected unknown Next Protocol Negotiation protocol");
break;
}
}
case QSslConfiguration::NextProtocolNegotiationNone:
protocolHandler.reset(new QHttpProtocolHandler(this));
connection->setConnectionType(QHttpNetworkConnection::ConnectionTypeHTTP);
// re-queue requests from SPDY queue to HTTP queue, if any
requeueSpdyRequests();
break;
case QSslConfiguration::NextProtocolNegotiationUnsupported:
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"chosen Next Protocol Negotiation value unsupported");
break;
default:
emitFinishedWithError(QNetworkReply::SslHandshakeFailedError,
"detected unknown Next Protocol Negotiation protocol");
}
}
if (!socket)
return; // ### error
state = QHttpNetworkConnectionChannel::IdleState;
pendingEncrypt = false;
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeSPDY) {
// we call setSpdyWasUsed(true) on the replies in the SPDY handler when the request is sent
if (spdyRequestsToSend.count() > 0)
// wait for data from the server first (e.g. initial window, max concurrent requests)
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
} else { // HTTP
if (!reply)
connection->d_func()->dequeueRequest(socket);
if (reply) {
reply->setSpdyWasUsed(false);
emit reply->encrypted();
}
if (reply)
sendRequest();
}
}
void QHttpNetworkConnectionChannel::requeueSpdyRequests()
{
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
connection->d_func()->requeueRequest(spdyPairs.at(a));
}
spdyRequestsToSend.clear();
}
void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::NetworkError error,
const char *message)
{
if (reply)
emit reply->encrypted();
if (reply)
sendRequest();
emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
Q_ASSERT(currentReply);
emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
}
}
void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
@ -927,8 +1020,21 @@ void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
connection->d_func()->pauseConnection();
if (pendingEncrypt && !reply)
connection->d_func()->dequeueRequest(socket);
if (reply)
emit reply->sslErrors(errors);
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
if (reply)
emit reply->sslErrors(errors);
}
#ifndef QT_NO_SSL
else { // SPDY
QList<HttpMessagePair> spdyPairs = spdyRequestsToSend.values();
for (int a = 0; a < spdyPairs.count(); ++a) {
// emit SSL errors for all replies
QHttpNetworkReply *currentReply = spdyPairs.at(a).second;
Q_ASSERT(currentReply);
emit currentReply->sslErrors(errors);
}
}
#endif // QT_NO_SSL
connection->d_func()->resumeConnection();
}

View File

@ -104,8 +104,8 @@ public:
bool ssl;
bool isInitialized;
ChannelState state;
QHttpNetworkRequest request; // current request
QHttpNetworkReply *reply; // current reply for this request
QHttpNetworkRequest request; // current request, only used for HTTP
QHttpNetworkReply *reply; // current reply for this request, only used for HTTP
qint64 written;
qint64 bytesTotal;
bool resendCurrent;
@ -123,9 +123,13 @@ public:
bool ignoreAllSslErrors;
QList<QSslError> ignoreSslErrorsList;
QSslConfiguration sslConfiguration;
QMultiMap<int, HttpMessagePair> spdyRequestsToSend; // sorted by priority
void ignoreSslErrors();
void ignoreSslErrors(const QList<QSslError> &errors);
void setSslConfiguration(const QSslConfiguration &config);
void requeueSpdyRequests(); // when we wanted SPDY but got HTTP
// to emit the signal for all in-flight replies:
void emitFinishedWithError(QNetworkReply::NetworkError error, const char *message);
#endif
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> networkSession;

View File

@ -265,6 +265,16 @@ bool QHttpNetworkReply::isPipeliningUsed() const
return d_func()->pipeliningUsed;
}
bool QHttpNetworkReply::isSpdyUsed() const
{
return d_func()->spdyUsed;
}
void QHttpNetworkReply::setSpdyWasUsed(bool spdy)
{
d_func()->spdyUsed = spdy;
}
QHttpNetworkConnection* QHttpNetworkReply::connection()
{
return d_func()->connection;
@ -281,9 +291,15 @@ QHttpNetworkReplyPrivate::QHttpNetworkReplyPrivate(const QUrl &newUrl)
connectionCloseEnabled(true),
forceConnectionCloseEnabled(false),
lastChunkRead(false),
currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0), connection(0),
currentChunkSize(0), currentChunkRead(0), readBufferMaxSize(0),
windowSizeDownload(65536), // 64K initial window size according to SPDY standard
windowSizeUpload(65536), // 64K initial window size according to SPDY standard
currentlyReceivedDataInWindow(0),
currentlyUploadedDataInWindow(0),
totallyUploadedData(0),
connection(0),
autoDecompress(false), responseData(), requestIsPrepared(false)
,pipeliningUsed(false), downstreamLimited(false)
,pipeliningUsed(false), spdyUsed(false), downstreamLimited(false)
,userProvidedDownloadBuffer(0)
#ifndef QT_NO_COMPRESS
,inflateStrm(0)
@ -550,15 +566,7 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
// allocate inflate state
if (!inflateStrm)
inflateStrm = new z_stream;
inflateStrm->zalloc = Z_NULL;
inflateStrm->zfree = Z_NULL;
inflateStrm->opaque = Z_NULL;
inflateStrm->avail_in = 0;
inflateStrm->next_in = Z_NULL;
// "windowBits can also be greater than 15 for optional gzip decoding.
// Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
// http://www.zlib.net/manual.html
int ret = inflateInit2(inflateStrm, MAX_WBITS+32);
int ret = initializeInflateStream();
if (ret != Z_OK)
return -1;
}
@ -703,8 +711,28 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff
}
#ifndef QT_NO_COMPRESS
int QHttpNetworkReplyPrivate::initializeInflateStream()
{
inflateStrm->zalloc = Z_NULL;
inflateStrm->zfree = Z_NULL;
inflateStrm->opaque = Z_NULL;
inflateStrm->avail_in = 0;
inflateStrm->next_in = Z_NULL;
// "windowBits can also be greater than 15 for optional gzip decoding.
// Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
// http://www.zlib.net/manual.html
int ret = inflateInit2(inflateStrm, MAX_WBITS+32);
Q_ASSERT(ret == Z_OK);
return ret;
}
qint64 QHttpNetworkReplyPrivate::uncompressBodyData(QByteDataBuffer *in, QByteDataBuffer *out)
{
if (!inflateStrm) { // happens when called from the SPDY protocol handler
inflateStrm = new z_stream;
initializeInflateStream();
}
if (!inflateStrm)
return -1;

View File

@ -132,6 +132,8 @@ public:
bool isFinished() const;
bool isPipeliningUsed() const;
bool isSpdyUsed() const;
void setSpdyWasUsed(bool spdy);
QHttpNetworkConnection* connection();
@ -165,6 +167,7 @@ private:
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
friend class QSpdyProtocolHandler;
};
@ -205,7 +208,11 @@ public:
ReadingStatusState,
ReadingHeaderState,
ReadingDataState,
AllDoneState
AllDoneState,
SPDYSYNSent,
SPDYUploading,
SPDYHalfClosed,
SPDYClosed
} state;
QHttpNetworkRequest request;
@ -226,6 +233,11 @@ public:
qint64 currentChunkSize;
qint64 currentChunkRead;
qint64 readBufferMaxSize;
qint32 windowSizeDownload; // only for SPDY
qint32 windowSizeUpload; // only for SPDY
qint32 currentlyReceivedDataInWindow; // only for SPDY
qint32 currentlyUploadedDataInWindow; // only for SPDY
qint64 totallyUploadedData; // only for SPDY
QPointer<QHttpNetworkConnection> connection;
QPointer<QHttpNetworkConnectionChannel> connectionChannel;
@ -236,12 +248,14 @@ public:
bool requestIsPrepared;
bool pipeliningUsed;
bool spdyUsed;
bool downstreamLimited;
char* userProvidedDownloadBuffer;
#ifndef QT_NO_COMPRESS
z_stream_s *inflateStrm;
int initializeInflateStream();
qint64 uncompressBodyData(QByteDataBuffer *in, QByteDataBuffer *out);
#endif
};

View File

@ -49,8 +49,8 @@ QT_BEGIN_NAMESPACE
QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(QHttpNetworkRequest::Operation op,
QHttpNetworkRequest::Priority pri, const QUrl &newUrl)
: QHttpNetworkHeaderPrivate(newUrl), operation(op), priority(pri), uploadByteDevice(0),
autoDecompress(false), pipeliningAllowed(false), withCredentials(true),
preConnect(false)
autoDecompress(false), pipeliningAllowed(false), spdyAllowed(false),
withCredentials(true), preConnect(false)
{
}
@ -62,6 +62,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest
uploadByteDevice = other.uploadByteDevice;
autoDecompress = other.autoDecompress;
pipeliningAllowed = other.pipeliningAllowed;
spdyAllowed = other.spdyAllowed;
customVerb = other.customVerb;
withCredentials = other.withCredentials;
ssl = other.ssl;
@ -80,6 +81,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot
&& (uploadByteDevice == other.uploadByteDevice)
&& (autoDecompress == other.autoDecompress)
&& (pipeliningAllowed == other.pipeliningAllowed)
&& (spdyAllowed == other.spdyAllowed)
// we do not clear the customVerb in setOperation
&& (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb))
&& (withCredentials == other.withCredentials)
@ -299,6 +301,16 @@ void QHttpNetworkRequest::setPipeliningAllowed(bool b)
d->pipeliningAllowed = b;
}
bool QHttpNetworkRequest::isSPDYAllowed() const
{
return d->spdyAllowed;
}
void QHttpNetworkRequest::setSPDYAllowed(bool b)
{
d->spdyAllowed = b;
}
bool QHttpNetworkRequest::withCredentials() const
{
return d->withCredentials;

View File

@ -114,6 +114,9 @@ public:
bool isPipeliningAllowed() const;
void setPipeliningAllowed(bool b);
bool isSPDYAllowed() const;
void setSPDYAllowed(bool b);
bool withCredentials() const;
void setWithCredentials(bool b);
@ -135,6 +138,7 @@ private:
friend class QHttpNetworkConnectionPrivate;
friend class QHttpNetworkConnectionChannel;
friend class QHttpProtocolHandler;
friend class QSpdyProtocolHandler;
};
class QHttpNetworkRequestPrivate : public QHttpNetworkHeaderPrivate
@ -154,6 +158,7 @@ public:
mutable QNonContiguousByteDevice* uploadByteDevice;
bool autoDecompress;
bool pipeliningAllowed;
bool spdyAllowed;
bool withCredentials;
bool ssl;
bool preConnect;

View File

@ -180,11 +180,15 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
// Q_OBJECT
public:
#ifdef QT_NO_BEARERMANAGEMENT
QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt)
: QHttpNetworkConnection(hostName, port, encrypt)
QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType connectionType)
: QHttpNetworkConnection(hostName, port, encrypt, connectionType)
#else
QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt, QSharedPointer<QNetworkSession> networkSession)
: QHttpNetworkConnection(hostName, port, encrypt, /*parent=*/0, networkSession)
QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
QHttpNetworkConnection::ConnectionType connectionType,
QSharedPointer<QNetworkSession> networkSession)
: QHttpNetworkConnection(hostName, port, encrypt, connectionType, /*parent=*/0,
networkSession)
#endif
{
setExpires(true);
@ -230,6 +234,7 @@ QHttpThreadDelegate::QHttpThreadDelegate(QObject *parent) :
, synchronous(false)
, incomingStatusCode(0)
, isPipeliningUsed(false)
, isSpdyUsed(false)
, incomingContentLength(-1)
, incomingErrorCode(QNetworkReply::NoError)
, downloadBuffer(0)
@ -281,6 +286,19 @@ void QHttpThreadDelegate::startRequest()
QUrl urlCopy = httpRequest.url();
urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
QHttpNetworkConnection::ConnectionType connectionType
= QHttpNetworkConnection::ConnectionTypeHTTP;
#ifndef QT_NO_SSL
if (httpRequest.isSPDYAllowed() && ssl) {
connectionType = QHttpNetworkConnection::ConnectionTypeSPDY;
urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests
QList<QByteArray> nextProtocols;
nextProtocols << QSslConfiguration::NextProtocolSpdy3_0
<< QSslConfiguration::NextProtocolHttp1_1;
incomingSslConfiguration.setAllowedNextProtocols(nextProtocols);
}
#endif // QT_NO_SSL
#ifndef QT_NO_NETWORKPROXY
if (transparentProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &transparentProxy);
@ -297,9 +315,12 @@ void QHttpThreadDelegate::startRequest()
// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
#ifdef QT_NO_BEARERMANAGEMENT
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl);
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
connectionType);
#else
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, networkSession);
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
connectionType,
networkSession);
#endif
#ifndef QT_NO_SSL
// Set the QSslConfiguration from this QNetworkRequest.
@ -575,13 +596,15 @@ void QHttpThreadDelegate::headerChangedSlot()
incomingReasonPhrase = httpReply->reasonPhrase();
isPipeliningUsed = httpReply->isPipeliningUsed();
incomingContentLength = httpReply->contentLength();
isSpdyUsed = httpReply->isSpdyUsed();
emit downloadMetaData(incomingHeaders,
incomingStatusCode,
incomingReasonPhrase,
isPipeliningUsed,
downloadBuffer,
incomingContentLength);
incomingContentLength,
isSpdyUsed);
}
void QHttpThreadDelegate::synchronousHeaderChangedSlot()
@ -597,6 +620,7 @@ void QHttpThreadDelegate::synchronousHeaderChangedSlot()
incomingStatusCode = httpReply->statusCode();
incomingReasonPhrase = httpReply->reasonPhrase();
isPipeliningUsed = httpReply->isPipeliningUsed();
isSpdyUsed = httpReply->isSpdyUsed();
incomingContentLength = httpReply->contentLength();
}

View File

@ -111,6 +111,7 @@ public:
int incomingStatusCode;
QString incomingReasonPhrase;
bool isPipeliningUsed;
bool isSpdyUsed;
qint64 incomingContentLength;
QNetworkReply::NetworkError incomingErrorCode;
QString incomingErrorDetail;
@ -139,7 +140,8 @@ signals:
void sslErrors(const QList<QSslError> &, bool *, QList<QSslError> *);
void sslConfigurationChanged(const QSslConfiguration);
#endif
void downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64);
void downloadMetaData(QList<QPair<QByteArray,QByteArray> >, int, QString, bool,
QSharedPointer<char>, qint64, bool);
void downloadProgress(qint64, qint64);
void downloadData(QByteArray);
void error(QNetworkReply::NetworkError, const QString);

View File

@ -977,6 +977,12 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess
\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 Preconnecting a SPDY connection can be done by calling setAllowedNextProtocols()
on \a sslConfiguration with QSslConfiguration::NextProtocolSpdy3_0 contained in
the list of allowed protocols. When using SPDY, one single connection per host is
enough, i.e. calling this method multiple times per host will not result in faster
network transactions.
\note This function has no possibility to report errors.
\sa connectToHost(), get(), post(), put(), deleteResource()
@ -991,6 +997,13 @@ void QNetworkAccessManager::connectToHostEncrypted(const QString &hostName, quin
QNetworkRequest request(url);
if (sslConfiguration != QSslConfiguration::defaultConfiguration())
request.setSslConfiguration(sslConfiguration);
// There is no way to enable SPDY via a request, so we need to check
// the ssl configuration whether SPDY is allowed here.
if (sslConfiguration.allowedNextProtocols().contains(
QSslConfiguration::NextProtocolSpdy3_0))
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
get(request);
}
#endif

View File

@ -752,6 +752,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
if (request.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool() == true)
httpRequest.setPipeliningAllowed(true);
if (request.attribute(QNetworkRequest::SpdyAllowedAttribute).toBool() == true)
httpRequest.setSPDYAllowed(true);
if (static_cast<QNetworkRequest::LoadControl>
(request.attribute(QNetworkRequest::AuthenticationReuseAttribute,
QNetworkRequest::Automatic).toInt()) == QNetworkRequest::Manual)
@ -811,8 +814,12 @@ void QNetworkReplyHttpImplPrivate::postRequest()
QObject::connect(delegate, SIGNAL(downloadFinished()),
q, SLOT(replyFinished()),
Qt::QueuedConnection);
QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64)),
QObject::connect(delegate, SIGNAL(downloadMetaData(QList<QPair<QByteArray,QByteArray> >,
int, QString, bool,
QSharedPointer<char>, qint64, bool)),
q, SLOT(replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,
int, QString, bool,
QSharedPointer<char>, qint64, bool)),
Qt::QueuedConnection);
QObject::connect(delegate, SIGNAL(downloadProgress(qint64,qint64)),
q, SLOT(replyDownloadProgressSlot(qint64,qint64)),
@ -907,7 +914,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
delegate->incomingContentLength,
delegate->isSpdyUsed);
replyDownloadData(delegate->synchronousDownloadData);
httpError(delegate->incomingErrorCode, delegate->incomingErrorDetail);
} else {
@ -917,7 +925,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
delegate->incomingReasonPhrase,
delegate->isPipeliningUsed,
QSharedPointer<char>(),
delegate->incomingContentLength);
delegate->incomingContentLength,
delegate->isSpdyUsed);
replyDownloadData(delegate->synchronousDownloadData);
}
@ -1074,7 +1083,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
(QList<QPair<QByteArray,QByteArray> > hm,
int sc,QString rp,bool pu,
QSharedPointer<char> db,
qint64 contentLength)
qint64 contentLength, bool spdyWasUsed)
{
Q_Q(QNetworkReplyHttpImpl);
Q_UNUSED(contentLength);
@ -1091,6 +1100,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData
}
q->setAttribute(QNetworkRequest::HttpPipeliningWasUsedAttribute, pu);
q->setAttribute(QNetworkRequest::SpdyWasUsedAttribute, spdyWasUsed);
// reconstruct the HTTP header
QList<QPair<QByteArray, QByteArray> > headerMap = hm;

View File

@ -111,7 +111,9 @@ public:
// From reply
Q_PRIVATE_SLOT(d_func(), void replyDownloadData(QByteArray))
Q_PRIVATE_SLOT(d_func(), void replyFinished())
Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64))
Q_PRIVATE_SLOT(d_func(), void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,
int, QString, bool, QSharedPointer<char>,
qint64, bool))
Q_PRIVATE_SLOT(d_func(), void replyDownloadProgressSlot(qint64,qint64))
Q_PRIVATE_SLOT(d_func(), void httpAuthenticationRequired(const QHttpNetworkRequest &, QAuthenticator *))
Q_PRIVATE_SLOT(d_func(), void httpError(QNetworkReply::NetworkError, const QString &))
@ -276,7 +278,8 @@ public:
// From HTTP thread:
void replyDownloadData(QByteArray);
void replyFinished();
void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >,int,QString,bool,QSharedPointer<char>,qint64);
void replyDownloadMetaData(QList<QPair<QByteArray,QByteArray> >, int, QString, bool,
QSharedPointer<char>, qint64, bool);
void replyDownloadProgressSlot(qint64,qint64);
void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth);
void httpError(QNetworkReply::NetworkError error, const QString &errorString);

View File

@ -246,6 +246,17 @@ QT_BEGIN_NAMESPACE
The QNetworkSession ConnectInBackground property will be set according to
this attribute.
\value SpdyAllowedAttribute
Requests only, type: QMetaType::Bool (default: false)
Indicates whether the QNetworkAccessManager code is
allowed to use SPDY with this request. This applies only
to SSL requests, and depends on the server supporting SPDY.
\value SpdyWasUsedAttribute
Replies only, type: QMetaType::Bool
Indicates whether SPDY was used for receiving
this reply.
\value User
Special type. Additional information can be passed in
QVariants with types ranging from User to UserMax. The default

View File

@ -86,6 +86,8 @@ public:
DownloadBufferAttribute, // internal
SynchronousRequestAttribute, // internal
BackgroundRequestAttribute,
SpdyAllowedAttribute,
SpdyWasUsedAttribute,
User = 1000,
UserMax = 32767

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,228 @@
/****************************************************************************
**
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QSPDYPROTOCOLHANDLER_H
#define QSPDYPROTOCOLHANDLER_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists for the convenience
// of the Network Access API. This header file may change from
// version to version without notice, or even be removed.
//
// We mean it.
//
#include <private/qabstractprotocolhandler_p.h>
#include <QtNetwork/qnetworkreply.h>
#include <private/qbytedata_p.h>
#include <zlib.h>
#if !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
QT_BEGIN_NAMESPACE
class QHttpNetworkRequest;
#ifndef HttpMessagePair
typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
#endif
class QSpdyProtocolHandler : public QObject, public QAbstractProtocolHandler {
Q_OBJECT
public:
QSpdyProtocolHandler(QHttpNetworkConnectionChannel *channel);
~QSpdyProtocolHandler();
enum DataFrameFlag {
DataFrame_FLAG_FIN = 0x01,
DataFrame_FLAG_COMPRESS = 0x02
};
Q_DECLARE_FLAGS(DataFrameFlags, DataFrameFlag)
enum ControlFrameFlag {
ControlFrame_FLAG_FIN = 0x01,
ControlFrame_FLAG_UNIDIRECTIONAL = 0x02
};
Q_DECLARE_FLAGS(ControlFrameFlags, ControlFrameFlag)
enum SETTINGS_Flag {
FLAG_SETTINGS_CLEAR_SETTINGS = 0x01
};
Q_DECLARE_FLAGS(SETTINGS_Flags, SETTINGS_Flag)
enum SETTINGS_ID_Flag {
FLAG_SETTINGS_PERSIST_VALUE = 0x01,
FLAG_SETTINGS_PERSISTED = 0x02
};
Q_DECLARE_FLAGS(SETTINGS_ID_Flags, SETTINGS_ID_Flag)
virtual void _q_receiveReply() Q_DECL_OVERRIDE;
virtual void _q_readyRead() Q_DECL_OVERRIDE;
virtual bool sendRequest() Q_DECL_OVERRIDE;
private slots:
void _q_uploadDataReadyRead();
private:
enum FrameType {
FrameType_SYN_STREAM = 1,
FrameType_SYN_REPLY = 2,
FrameType_RST_STREAM = 3,
FrameType_SETTINGS = 4,
FrameType_PING = 6,
FrameType_GOAWAY = 7,
FrameType_HEADERS = 8,
FrameType_WINDOW_UPDATE = 9,
FrameType_CREDENTIAL // has a special type
};
enum StatusCode {
StatusCode_PROTOCOL_ERROR = 1,
StatusCode_INVALID_STREAM = 2,
StatusCode_REFUSED_STREAM = 3,
StatusCode_UNSUPPORTED_VERSION = 4,
StatusCode_CANCEL = 5,
StatusCode_INTERNAL_ERROR = 6,
StatusCode_FLOW_CONTROL_ERROR = 7,
StatusCode_STREAM_IN_USE = 8,
StatusCode_STREAM_ALREADY_CLOSED = 9,
StatusCode_INVALID_CREDENTIALS = 10,
StatusCode_FRAME_TOO_LARGE = 11
};
enum SETTINGS_ID {
SETTINGS_UPLOAD_BANDWIDTH = 1,
SETTINGS_DOWNLOAD_BANDWIDTH = 2,
SETTINGS_ROUND_TRIP_TIME = 3,
SETTINGS_MAX_CONCURRENT_STREAMS = 4,
SETTINGS_CURRENT_CWND = 5,
SETTINGS_DOWNLOAD_RETRANS_RATE = 6,
SETTINGS_INITIAL_WINDOW_SIZE = 7,
SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE = 8
};
enum GOAWAY_STATUS {
GOAWAY_OK = 0,
GOAWAY_PROTOCOL_ERROR = 1,
GOAWAY_INTERNAL_ERROR = 11
};
enum RST_STREAM_STATUS_CODE {
RST_STREAM_PROTOCOL_ERROR = 1,
RST_STREAM_INVALID_STREAM = 2,
RST_STREAM_REFUSED_STREAM = 3,
RST_STREAM_UNSUPPORTED_VERSION = 4,
RST_STREAM_CANCEL = 5,
RST_STREAM_INTERNAL_ERROR = 6,
RST_STREAM_FLOW_CONTROL_ERROR = 7,
RST_STREAM_STREAM_IN_USE = 8,
RST_STREAM_STREAM_ALREADY_CLOSED = 9,
RST_STREAM_INVALID_CREDENTIALS = 10,
RST_STREAM_FRAME_TOO_LARGE = 11
};
quint64 bytesAvailable() const;
bool readNextChunk(qint64 length, char *sink);
void sendControlFrame(FrameType type, ControlFrameFlags flags, const char *data, quint32 length);
void sendSYN_STREAM(HttpMessagePair pair, qint32 streamID,
qint32 associatedToStreamID);
void sendRST_STREAM(qint32 streamID, RST_STREAM_STATUS_CODE statusCode);
void sendPING(quint32 pingID);
bool uploadData(qint32 streamID);
Q_INVOKABLE void sendWINDOW_UPDATE(qint32 streamID, quint32 deltaWindowSize);
qint64 sendDataFrame(qint32 streamID, DataFrameFlags flags, quint32 length,
const char *data);
QByteArray composeHeader(const QHttpNetworkRequest &request);
bool uncompressHeader(const QByteArray &input, QByteArray *output);
void handleControlFrame(const QByteArray &frameHeaders);
void handleDataFrame(const QByteArray &frameHeaders);
void handleSYN_STREAM(char, quint32, const QByteArray &frameData);
void handleSYN_REPLY(char flags, quint32, const QByteArray &frameData);
void handleRST_STREAM(char flags, quint32 length, const QByteArray &frameData);
void handleSETTINGS(char flags, quint32 length, const QByteArray &frameData);
void handlePING(char, quint32 length, const QByteArray &frameData);
void handleGOAWAY(char flags, quint32, const QByteArray &frameData);
void handleHEADERS(char flags, quint32, const QByteArray &frameData);
void handleWINDOW_UPDATE(char, quint32, const QByteArray &frameData);
qint32 generateNextStreamID();
void parseHttpHeaders(char flags, const QByteArray &frameData);
void replyFinished(QHttpNetworkReply *httpReply, qint32 streamID);
void replyFinishedWithError(QHttpNetworkReply *httpReply, qint32 streamID,
QNetworkReply::NetworkError errorCode, const char *errorMessage);
qint32 m_nextStreamID;
QHash<quint32, HttpMessagePair> m_inFlightStreams;
qint32 m_maxConcurrentStreams;
quint32 m_initialWindowSize;
QByteDataBuffer m_spdyBuffer;
bool m_waitingForCompleteStream;
z_stream m_deflateStream;
z_stream m_inflateStream;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::DataFrameFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::ControlFrameFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::SETTINGS_Flags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::SETTINGS_ID_Flags)
QT_END_NAMESPACE
#endif // !defined(QT_NO_HTTP) && !defined(QT_NO_SSL)
#endif // QSPDYPROTOCOLHANDLER_H

View File

@ -7,6 +7,7 @@ SUBDIRS=\
qnetworkrequest \
qhttpnetworkconnection \
qnetworkreply \
spdy \
qnetworkcachemetadata \
qftp \
qhttpnetworkreply \

View File

@ -0,0 +1,7 @@
CONFIG += testcase
CONFIG += parallel_test
TARGET = tst_spdy
SOURCES += tst_spdy.cpp
QT = core core-private network network-private testlib
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0

View File

@ -0,0 +1,690 @@
/****************************************************************************
**
** 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.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QHttpPart>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkProxy>
#include <QtNetwork/QAuthenticator>
#ifdef QT_BUILD_INTERNAL
#include <QtNetwork/private/qsslsocket_openssl_p.h>
#endif // QT_BUILD_INTERNAL
#include "../../../network-settings.h"
Q_DECLARE_METATYPE(QAuthenticator*)
class tst_Spdy: public QObject
{
Q_OBJECT
public:
tst_Spdy();
~tst_Spdy();
private Q_SLOTS:
void settingsAndNegotiation_data();
void settingsAndNegotiation();
void download_data();
void download();
void headerFields();
void upload_data();
void upload();
void errors_data();
void errors();
void multipleRequests_data();
void multipleRequests();
private:
QNetworkAccessManager m_manager;
int m_multipleRequestsCount;
int m_multipleRepliesFinishedCount;
protected Q_SLOTS:
void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *authenticator);
void multipleRequestsFinishedSlot();
};
tst_Spdy::tst_Spdy()
{
#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
qRegisterMetaType<QNetworkReply *>(); // for QSignalSpy
qRegisterMetaType<QAuthenticator *>();
connect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)));
#else
QSKIP("Qt built withouth OpenSSL, or the OpenSSL version is too old");
#endif // defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ...
}
tst_Spdy::~tst_Spdy()
{
}
void tst_Spdy::settingsAndNegotiation_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<bool>("setAttribute");
QTest::addColumn<bool>("enabled");
QTest::addColumn<QByteArray>("expectedProtocol");
QTest::addColumn<QByteArray>("expectedContent");
QTest::newRow("default-settings") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/cgi-bin/echo.cgi?1")
<< false << false << QByteArray()
<< QByteArray("1");
QTest::newRow("http-url") << QUrl("http://" + QtNetworkSettings::serverName()
+ "/qtest/cgi-bin/echo.cgi?1")
<< true << true << QByteArray()
<< QByteArray("1");
QTest::newRow("spdy-disabled") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/cgi-bin/echo.cgi?1")
<< true << false << QByteArray()
<< QByteArray("1");
#ifndef QT_NO_OPENSSL
QTest::newRow("spdy-enabled") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/cgi-bin/echo.cgi?1")
<< true << true << QByteArray(QSslConfiguration::NextProtocolSpdy3_0)
<< QByteArray("1");
#endif // QT_NO_OPENSSL
}
void tst_Spdy::settingsAndNegotiation()
{
QFETCH(QUrl, url);
QFETCH(bool, setAttribute);
QFETCH(bool, enabled);
QNetworkRequest request(url);
if (setAttribute) {
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, QVariant(enabled));
}
QNetworkReply *reply = m_manager.get(request);
reply->ignoreSslErrors();
QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
QSignalSpy finishedSpy(reply, SIGNAL(finished()));
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QFETCH(QByteArray, expectedProtocol);
#ifndef QT_NO_OPENSSL
bool expectedSpdyUsed = (expectedProtocol == QSslConfiguration::NextProtocolSpdy3_0)
? true : false;
QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), expectedSpdyUsed);
#endif // QT_NO_OPENSSL
QCOMPARE(metaDataChangedSpy.count(), 1);
QCOMPARE(finishedSpy.count(), 1);
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QCOMPARE(statusCode, 200);
QByteArray content = reply->readAll();
QFETCH(QByteArray, expectedContent);
QCOMPARE(expectedContent, content);
#ifndef QT_NO_OPENSSL
QSslConfiguration::NextProtocolNegotiationStatus expectedStatus =
(expectedProtocol.isEmpty())
? QSslConfiguration::NextProtocolNegotiationNone
: QSslConfiguration::NextProtocolNegotiationNegotiated;
QCOMPARE(reply->sslConfiguration().nextProtocolNegotiationStatus(),
expectedStatus);
QCOMPARE(reply->sslConfiguration().nextNegotiatedProtocol(), expectedProtocol);
#endif // QT_NO_OPENSSL
}
void tst_Spdy::proxyAuthenticationRequired(const QNetworkProxy &/*proxy*/,
QAuthenticator *authenticator)
{
authenticator->setUser("qsockstest");
authenticator->setPassword("password");
}
void tst_Spdy::download_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QString>("fileName");
QTest::addColumn<QNetworkProxy>("proxy");
QTest::newRow("mediumfile") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/rfc3252.txt")
<< QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
<< QNetworkProxy();
QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
QString proxyserver = hostInfo.addresses().first().toString();
QTest::newRow("mediumfile-http-proxy") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/rfc3252.txt")
<< QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
<< QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128);
QTest::newRow("mediumfile-http-proxy-auth") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/rfc3252.txt")
<< QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
<< QNetworkProxy(QNetworkProxy::HttpProxy,
proxyserver, 3129);
QTest::newRow("mediumfile-socks-proxy") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/rfc3252.txt")
<< QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
<< QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080);
QTest::newRow("mediumfile-socks-proxy-auth") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/rfc3252.txt")
<< QFINDTESTDATA("../qnetworkreply/rfc3252.txt")
<< QNetworkProxy(QNetworkProxy::Socks5Proxy,
proxyserver, 1081);
QTest::newRow("bigfile") << QUrl("https://" + QtNetworkSettings::serverName()
+ "/qtest/bigfile")
<< QFINDTESTDATA("../qnetworkreply/bigfile")
<< QNetworkProxy();
}
void tst_Spdy::download()
{
QFETCH(QUrl, url);
QFETCH(QString, fileName);
QFETCH(QNetworkProxy, proxy);
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
if (proxy.type() != QNetworkProxy::DefaultProxy) {
m_manager.setProxy(proxy);
}
QNetworkReply *reply = m_manager.get(request);
reply->ignoreSslErrors();
QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
QSignalSpy downloadProgressSpy(reply, SIGNAL(downloadProgress(qint64, qint64)));
QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
QSignalSpy finishedSpy(reply, SIGNAL(finished()));
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));
QSignalSpy proxyAuthRequiredSpy(&m_manager, SIGNAL(
proxyAuthenticationRequired(const QNetworkProxy &,
QAuthenticator *)));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(finishedManagerSpy.count(), 1);
QCOMPARE(metaDataChangedSpy.count(), 1);
QCOMPARE(finishedSpy.count(), 1);
QVERIFY(downloadProgressSpy.count() > 0);
QVERIFY(readyReadSpy.count() > 0);
QVERIFY(proxyAuthRequiredSpy.count() <= 1);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true);
QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true);
QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);
QFile file(fileName);
QVERIFY(file.open(QIODevice::ReadOnly));
qint64 contentLength = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
qint64 expectedContentLength = file.bytesAvailable();
QCOMPARE(contentLength, expectedContentLength);
QByteArray expectedContent = file.readAll();
QByteArray content = reply->readAll();
QCOMPARE(content, expectedContent);
reply->deleteLater();
m_manager.setProxy(QNetworkProxy()); // reset
}
void tst_Spdy::headerFields()
{
QUrl url(QUrl("https://" + QtNetworkSettings::serverName()));
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
QNetworkReply *reply = m_manager.get(request);
reply->ignoreSslErrors();
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(reply->rawHeader("Content-Type"), QByteArray("text/html"));
QVERIFY(reply->rawHeader("Content-Length").toInt() > 0);
QVERIFY(reply->rawHeader("server").contains("Apache"));
QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), QByteArray("text/html"));
QVERIFY(reply->header(QNetworkRequest::ContentLengthHeader).toLongLong() > 0);
QVERIFY(reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().isValid());
QVERIFY(reply->header(QNetworkRequest::ServerHeader).toByteArray().contains("Apache"));
}
static inline QByteArray md5sum(const QByteArray &data)
{
return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex().append('\n');
}
void tst_Spdy::upload_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QByteArray>("uploadMethod");
QTest::addColumn<QObject *>("uploadObject");
QTest::addColumn<QByteArray>("md5sum");
QTest::addColumn<QNetworkProxy>("proxy");
// 1. test uploading of byte arrays
QUrl md5Url("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi");
QByteArray data;
data = "";
QObject *dummyObject = 0;
QTest::newRow("empty") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = "This is a normal message.";
QTest::newRow("generic") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = "This is a message to show that Qt rocks!\r\n\n";
QTest::newRow("small") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = QByteArray("abcd\0\1\2\abcd",12);
QTest::newRow("with-nul") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = QByteArray(4097, '\4');
QTest::newRow("4k+1") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data)<< QNetworkProxy();
QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
QString proxyserver = hostInfo.addresses().first().toString();
QTest::newRow("4k+1-with-http-proxy") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data)
<< QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3128);
QTest::newRow("4k+1-with-http-proxy-auth") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data)
<< QNetworkProxy(QNetworkProxy::HttpProxy,
proxyserver, 3129);
QTest::newRow("4k+1-with-socks-proxy") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data)
<< QNetworkProxy(QNetworkProxy::Socks5Proxy, proxyserver, 1080);
QTest::newRow("4k+1-with-socks-proxy-auth") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data)
<< QNetworkProxy(QNetworkProxy::Socks5Proxy,
proxyserver, 1081);
data = QByteArray(128*1024+1, '\177');
QTest::newRow("128k+1") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = QByteArray(128*1024+1, '\177');
QTest::newRow("128k+1-put") << md5Url << data << QByteArray("PUT") << dummyObject
<< md5sum(data) << QNetworkProxy();
data = QByteArray(2*1024*1024+1, '\177');
QTest::newRow("2MB+1") << md5Url << data << QByteArray("POST") << dummyObject
<< md5sum(data) << QNetworkProxy();
// 2. test uploading of files
QFile *file = new QFile(QFINDTESTDATA("../qnetworkreply/rfc3252.txt"));
file->open(QIODevice::ReadOnly);
QTest::newRow("file-26K") << md5Url << QByteArray() << QByteArray("POST")
<< static_cast<QObject *>(file)
<< QByteArray("b3e32ac459b99d3f59318f3ac31e4bee\n") << QNetworkProxy();
QFile *file2 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg"));
file2->open(QIODevice::ReadOnly);
QTest::newRow("file-1MB") << md5Url << QByteArray() << QByteArray("POST")
<< static_cast<QObject *>(file2)
<< QByteArray("87ef3bb319b004ba9e5e9c9fa713776e\n") << QNetworkProxy();
// 3. test uploading of multipart
QUrl multiPartUrl("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/multipart.cgi");
QHttpPart imagePart31;
imagePart31.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart31.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage1\""));
imagePart31.setRawHeader("Content-Location", "http://my.test.location.tld");
imagePart31.setRawHeader("Content-ID", "my@id.tld");
QFile *file31 = new QFile(QFINDTESTDATA("../qnetworkreply/image1.jpg"));
file31->open(QIODevice::ReadOnly);
imagePart31.setBodyDevice(file31);
QHttpMultiPart *imageMultiPart3 = new QHttpMultiPart(QHttpMultiPart::FormDataType);
imageMultiPart3->append(imagePart31);
file31->setParent(imageMultiPart3);
QHttpPart imagePart32;
imagePart32.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart32.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage2\""));
QFile *file32 = new QFile(QFINDTESTDATA("../qnetworkreply/image2.jpg"));
file32->open(QIODevice::ReadOnly);
imagePart32.setBodyDevice(file31); // check that resetting works
imagePart32.setBodyDevice(file32);
imageMultiPart3->append(imagePart32);
file32->setParent(imageMultiPart3);
QHttpPart imagePart33;
imagePart33.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart33.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"testImage3\""));
QFile *file33 = new QFile(QFINDTESTDATA("../qnetworkreply/image3.jpg"));
file33->open(QIODevice::ReadOnly);
imagePart33.setBodyDevice(file33);
imageMultiPart3->append(imagePart33);
file33->setParent(imageMultiPart3);
QByteArray expectedData = "content type: multipart/form-data; boundary=\""
+ imageMultiPart3->boundary();
expectedData.append("\"\nkey: testImage1, value: 87ef3bb319b004ba9e5e9c9fa713776e\n"
"key: testImage2, value: 483761b893f7fb1bd2414344cd1f3dfb\n"
"key: testImage3, value: ab0eb6fd4fcf8b4436254870b4513033\n");
QTest::newRow("multipart-3images") << multiPartUrl << QByteArray() << QByteArray("POST")
<< static_cast<QObject *>(imageMultiPart3) << expectedData
<< QNetworkProxy();
}
void tst_Spdy::upload()
{
QFETCH(QUrl, url);
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
QFETCH(QByteArray, data);
QFETCH(QByteArray, uploadMethod);
QFETCH(QObject *, uploadObject);
QFETCH(QNetworkProxy, proxy);
if (proxy.type() != QNetworkProxy::DefaultProxy) {
m_manager.setProxy(proxy);
}
QNetworkReply *reply;
QHttpMultiPart *multiPart = 0;
if (uploadObject) {
// upload via device
if (QIODevice *device = qobject_cast<QIODevice *>(uploadObject)) {
reply = m_manager.post(request, device);
} else if ((multiPart = qobject_cast<QHttpMultiPart *>(uploadObject))) {
reply = m_manager.post(request, multiPart);
} else {
QFAIL("got unknown upload device");
}
} else {
// upload via byte array
if (uploadMethod == "PUT") {
reply = m_manager.put(request, data);
} else {
reply = m_manager.post(request, data);
}
}
reply->ignoreSslErrors();
QSignalSpy metaDataChangedSpy(reply, SIGNAL(metaDataChanged()));
QSignalSpy uploadProgressSpy(reply, SIGNAL(uploadProgress(qint64, qint64)));
QSignalSpy readyReadSpy(reply, SIGNAL(readyRead()));
QSignalSpy finishedSpy(reply, SIGNAL(finished()));
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));
QTestEventLoop::instance().enterLoop(20);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(finishedManagerSpy.count(), 1);
QCOMPARE(metaDataChangedSpy.count(), 1);
QCOMPARE(finishedSpy.count(), 1);
QVERIFY(uploadProgressSpy.count() > 0);
QVERIFY(readyReadSpy.count() > 0);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QCOMPARE(reply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool(), true);
QCOMPARE(reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool(), true);
QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), 200);
qint64 contentLength = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong();
if (!multiPart) // script to test multiparts does not return a content length
QCOMPARE(contentLength, 33); // 33 bytes for md5 sums (including new line)
QFETCH(QByteArray, md5sum);
QByteArray content = reply->readAll();
QCOMPARE(content, md5sum);
reply->deleteLater();
if (uploadObject)
uploadObject->deleteLater();
m_manager.setProxy(QNetworkProxy()); // reset
}
void tst_Spdy::errors_data()
{
QTest::addColumn<QUrl>("url");
QTest::addColumn<QNetworkProxy>("proxy");
QTest::addColumn<bool>("ignoreSslErrors");
QTest::addColumn<int>("expectedReplyError");
QTest::newRow("http-404") << QUrl("https://" + QtNetworkSettings::serverName() + "/non-existent-url")
<< QNetworkProxy() << true << int(QNetworkReply::ContentNotFoundError);
QTest::newRow("ssl-errors") << QUrl("https://" + QtNetworkSettings::serverName())
<< QNetworkProxy() << false << int(QNetworkReply::SslHandshakeFailedError);
QTest::newRow("host-not-found") << QUrl("https://this-host-does-not.exist")
<< QNetworkProxy()
<< true << int(QNetworkReply::HostNotFoundError);
QTest::newRow("proxy-not-found") << QUrl("https://" + QtNetworkSettings::serverName())
<< QNetworkProxy(QNetworkProxy::HttpProxy,
"https://this-host-does-not.exist", 3128)
<< true << int(QNetworkReply::HostNotFoundError);
QHostInfo hostInfo = QHostInfo::fromName(QtNetworkSettings::serverName());
QString proxyserver = hostInfo.addresses().first().toString();
QTest::newRow("proxy-unavailable") << QUrl("https://" + QtNetworkSettings::serverName())
<< QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 10)
<< true << int(QNetworkReply::UnknownNetworkError);
QTest::newRow("no-proxy-credentials") << QUrl("https://" + QtNetworkSettings::serverName())
<< QNetworkProxy(QNetworkProxy::HttpProxy, proxyserver, 3129)
<< true << int(QNetworkReply::ProxyAuthenticationRequiredError);
}
void tst_Spdy::errors()
{
QFETCH(QUrl, url);
QFETCH(QNetworkProxy, proxy);
QFETCH(bool, ignoreSslErrors);
QFETCH(int, expectedReplyError);
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
disconnect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
0, 0);
if (proxy.type() != QNetworkProxy::DefaultProxy) {
m_manager.setProxy(proxy);
}
QNetworkReply *reply = m_manager.get(request);
if (ignoreSslErrors)
reply->ignoreSslErrors();
QSignalSpy finishedSpy(reply, SIGNAL(finished()));
QSignalSpy errorSpy(reply, SIGNAL(error(QNetworkReply::NetworkError)));
QObject::connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(finishedSpy.count(), 1);
QCOMPARE(errorSpy.count(), 1);
QCOMPARE(reply->error(), static_cast<QNetworkReply::NetworkError>(expectedReplyError));
m_manager.setProxy(QNetworkProxy()); // reset
m_manager.clearAccessCache(); // e.g. to get an SSL error we need a new connection
connect(&m_manager, SIGNAL(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
this, SLOT(proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *)),
Qt::UniqueConnection); // reset
}
void tst_Spdy::multipleRequests_data()
{
QTest::addColumn<QList<QUrl> >("urls");
QString baseUrl = "https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/echo.cgi?";
QList<QUrl> urls;
for (int a = 1; a <= 50; ++a)
urls.append(QUrl(baseUrl + QLatin1String(QByteArray::number(a))));
QTest::newRow("one-request") << urls.mid(0, 1);
QTest::newRow("two-requests") << urls.mid(0, 2);
QTest::newRow("ten-requests") << urls.mid(0, 10);
QTest::newRow("twenty-requests") << urls.mid(0, 20);
QTest::newRow("fifty-requests") << urls;
}
void tst_Spdy::multipleRequestsFinishedSlot()
{
m_multipleRepliesFinishedCount++;
if (m_multipleRepliesFinishedCount == m_multipleRequestsCount)
QTestEventLoop::instance().exitLoop();
}
void tst_Spdy::multipleRequests()
{
QFETCH(QList<QUrl>, urls);
m_multipleRequestsCount = urls.count();
m_multipleRepliesFinishedCount = 0;
QList<QNetworkReply *> replies;
QList<QSignalSpy *> metaDataChangedSpies;
QList<QSignalSpy *> readyReadSpies;
QList<QSignalSpy *> finishedSpies;
foreach (const QUrl &url, urls) {
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
QNetworkReply *reply = m_manager.get(request);
replies.append(reply);
reply->ignoreSslErrors();
QObject::connect(reply, SIGNAL(finished()), this, SLOT(multipleRequestsFinishedSlot()));
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;
}
QSignalSpy finishedManagerSpy(&m_manager, SIGNAL(finished(QNetworkReply*)));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(finishedManagerSpy.count(), m_multipleRequestsCount);
for (int a = 0; a < replies.count(); ++a) {
#ifndef QT_NO_OPENSSL
QCOMPARE(replies.at(a)->sslConfiguration().nextProtocolNegotiationStatus(),
QSslConfiguration::NextProtocolNegotiationNegotiated);
QCOMPARE(replies.at(a)->sslConfiguration().nextNegotiatedProtocol(),
QByteArray(QSslConfiguration::NextProtocolSpdy3_0));
#endif // QT_NO_OPENSSL
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);
// using the echo script, a request to "echo.cgi?1" will return a body of "1"
QByteArray expectedContent = replies.at(a)->url().query().toUtf8();
QByteArray content = replies.at(a)->readAll();
QCOMPARE(expectedContent, content);
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();
}
}
QTEST_MAIN(tst_Spdy)
#include "tst_spdy.moc"

View File

@ -52,6 +52,7 @@
#ifdef QT_BUILD_INTERNAL
#include <QtNetwork/private/qhostinfo_p.h>
#include <QtNetwork/private/qsslsocket_openssl_p.h>
#endif
Q_DECLARE_METATYPE(QSharedPointer<char>)
@ -552,10 +553,16 @@ void tst_qnetworkreply::echoPerformance()
void tst_qnetworkreply::preConnectEncrypted()
{
QFETCH(int, sleepTime);
QFETCH(QSslConfiguration, sslConfiguration);
bool spdyEnabled = !sslConfiguration.isNull();
QString hostName = QLatin1String("www.google.com");
QNetworkAccessManager manager;
QNetworkRequest request(QUrl("https://" + hostName));
if (spdyEnabled)
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
// make sure we have a full request including
// DNS lookup, TCP and SSL handshakes
@ -581,8 +588,12 @@ void tst_qnetworkreply::preConnectEncrypted()
manager.clearAccessCache();
// now try to make the connection beforehand
QFETCH(int, sleepTime);
manager.connectToHostEncrypted(hostName);
if (spdyEnabled) {
request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true);
manager.connectToHostEncrypted(hostName, 443, sslConfiguration);
} else {
manager.connectToHostEncrypted(hostName);
}
QTestEventLoop::instance().enterLoopMSecs(sleepTime);
// now make another request and hopefully use the existing connection
@ -590,18 +601,42 @@ void tst_qnetworkreply::preConnectEncrypted()
QNetworkReply *preConnectReply = normalResult.first;
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(preConnectReply->error() == QNetworkReply::NoError);
bool spdyWasUsed = preConnectReply->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool();
QCOMPARE(spdyEnabled, spdyWasUsed);
qint64 preConnectElapsed = preConnectResult.second;
qDebug() << request.url().toString() << "full request:" << normalElapsed
<< "ms, pre-connect request:" << preConnectElapsed << "ms, difference:"
<< (normalElapsed - preConnectElapsed) << "ms";
}
#endif // !QT_NO_SSL
void tst_qnetworkreply::preConnectEncrypted_data()
{
#ifndef QT_NO_OPENSSL
QTest::addColumn<int>("sleepTime");
QTest::newRow("2secs") << 2000; // to start a new request after preconnecting is done
QTest::newRow("100ms") << 100; // to start a new request while preconnecting is in-flight
QTest::addColumn<QSslConfiguration>("sslConfiguration");
// start a new normal request after preconnecting is done
QTest::newRow("HTTPS-2secs") << 2000 << QSslConfiguration();
// start a new normal request while preconnecting is in-flight
QTest::newRow("HTTPS-100ms") << 100 << QSslConfiguration();
QSslConfiguration spdySslConf = QSslConfiguration::defaultConfiguration();
QList<QByteArray> nextProtocols = QList<QByteArray>()
<< QSslConfiguration::NextProtocolSpdy3_0
<< QSslConfiguration::NextProtocolHttp1_1;
spdySslConf.setAllowedNextProtocols(nextProtocols);
#if defined(QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) && OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_NEXTPROTONEG)
// start a new SPDY request while preconnecting is done
QTest::newRow("SPDY-2secs") << 2000 << spdySslConf;
// start a new SPDY request while preconnecting is in-flight
QTest::newRow("SPDY-100ms") << 100 << spdySslConf;
#endif // defined (QT_BUILD_INTERNAL) && !defined(QT_NO_SSL) ...
#endif // QT_NO_OPENSSL
}
void tst_qnetworkreply::downloadPerformance()

View File

@ -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 <QtNetwork/private/qsslsocket_openssl_p.h>
#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<QNetworkReply *>(); // for QSignalSpy
QVERIFY(QtNetworkSettings::verifyTestNetworkSettings());
}
@ -284,6 +295,215 @@ void tst_qnetworkreply::uploadToFacebook()
}
}
void tst_qnetworkreply::spdy_data()
{
QTest::addColumn<QString>("host");
QTest::addColumn<bool>("setAttribute");
QTest::addColumn<bool>("enabled");
QTest::addColumn<QByteArray>("expectedProtocol");
QList<QString> hosts = QList<QString>()
<< 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<QNetworkRequest> 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<QNetworkReply *> replies;
QList<QSignalSpy *> metaDataChangedSpies;
QList<QSignalSpy *> readyReadSpies;
QList<QSignalSpy *> 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"