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:
parent
42789d04a3
commit
1de244ea65
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -86,6 +86,8 @@ public:
|
||||
DownloadBufferAttribute, // internal
|
||||
SynchronousRequestAttribute, // internal
|
||||
BackgroundRequestAttribute,
|
||||
SpdyAllowedAttribute,
|
||||
SpdyWasUsedAttribute,
|
||||
|
||||
User = 1000,
|
||||
UserMax = 32767
|
||||
|
1272
src/network/access/qspdyprotocolhandler.cpp
Normal file
1272
src/network/access/qspdyprotocolhandler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
228
src/network/access/qspdyprotocolhandler_p.h
Normal file
228
src/network/access/qspdyprotocolhandler_p.h
Normal 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
|
@ -7,6 +7,7 @@ SUBDIRS=\
|
||||
qnetworkrequest \
|
||||
qhttpnetworkconnection \
|
||||
qnetworkreply \
|
||||
spdy \
|
||||
qnetworkcachemetadata \
|
||||
qftp \
|
||||
qhttpnetworkreply \
|
||||
|
7
tests/auto/network/access/spdy/spdy.pro
Normal file
7
tests/auto/network/access/spdy/spdy.pro
Normal 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
|
690
tests/auto/network/access/spdy/tst_spdy.cpp
Normal file
690
tests/auto/network/access/spdy/tst_spdy.cpp
Normal 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"
|
@ -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()
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user