1
1
mirror of https://github.com/ariya/phantomjs.git synced 2024-09-11 12:55:33 +03:00

added response body to response object in onResourceReceived event

https://github.com/ariya/phantomjs/issues/10158
This commit is contained in:
Dmitry Parshin 2013-07-16 14:17:56 -04:00 committed by Vitaly Slobodin
parent 6897baae33
commit 434d4e0101
8 changed files with 568 additions and 61 deletions

View File

@ -43,6 +43,7 @@
#include "config.h"
#include "cookiejar.h"
#include "networkaccessmanager.h"
#include "networkreplyproxy.h"
// 10 MB
const qint64 MAX_REQUEST_POST_BODY_SIZE = 10 * 1000 * 1000;
@ -160,6 +161,7 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent, const Config *config
, m_idCounter(0)
, m_networkDiskCache(0)
, m_sslConfiguration(QSslConfiguration::defaultConfiguration())
, m_replyTracker(this)
{
if (config->diskCacheEnabled()) {
m_networkDiskCache = new QNetworkDiskCache(this);
@ -215,7 +217,6 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent, const Config *config
}
connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*)));
connect(this, SIGNAL(finished(QNetworkReply*)), SLOT(handleFinished(QNetworkReply*)));
}
void NetworkAccessManager::setUserName(const QString &userName)
@ -315,24 +316,17 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
JsNetworkRequest jsNetworkRequest(&req, this);
emit resourceRequested(data, &jsNetworkRequest);
// Pass duty to the superclass - special case: file:/// may be disabled.
// This conditional must match QNetworkAccessManager's own idea of what a
// local file URL is.
QNetworkReply *reply;
if (!m_localUrlAccessEnabled && (isLocalFile || scheme == QLatin1String("qrc"))) {
reply = new NoFileAccessReply(this, req, op);
} else {
reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
}
QNetworkReply *nested_reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
QNetworkReply *tracked_reply = m_replyTracker.trackReply(nested_reply, m_idCounter);
// reparent jsNetworkRequest to make sure that it will be destroyed with QNetworkReply
jsNetworkRequest.setParent(reply);
jsNetworkRequest.setParent(tracked_reply);
// If there is a timeout set, create a TimeoutTimer
if(m_resourceTimeout > 0){
TimeoutTimer *nt = new TimeoutTimer(reply);
nt->reply = reply; // We need the reply object in order to abort it later on.
TimeoutTimer *nt = new TimeoutTimer(tracked_reply);
nt->reply = tracked_reply; // We need the reply object in order to abort it later on.
nt->data = data;
nt->setInterval(m_resourceTimeout);
nt->setSingleShot(true);
@ -341,13 +335,13 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
connect(nt, SIGNAL(timeout()), this, SLOT(handleTimeout()));
}
m_ids[reply] = m_idCounter;
connect(reply, SIGNAL(readyRead()), this, SLOT(handleStarted()));
connect(reply, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(handleSslErrors(const QList<QSslError> &)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleNetworkError()));
connect(&m_replyTracker, SIGNAL(started(QNetworkReply*, int)), this, SLOT(handleStarted(QNetworkReply*, int)));
connect(&m_replyTracker, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)), this, SLOT(handleSslErrors(QNetworkReply*, const QList<QSslError> &)));
connect(&m_replyTracker, SIGNAL(error(QNetworkReply*, int, QNetworkReply::NetworkError)), this, SLOT(handleNetworkError(QNetworkReply*, int)));
connect(&m_replyTracker, SIGNAL(finished(QNetworkReply *, int, int, const QString&, const QString&)), SLOT(handleFinished(QNetworkReply *, int, int, const QString&, const QString&)));
return reply;
return tracked_reply;
}
void NetworkAccessManager::handleTimeout()
@ -366,16 +360,8 @@ void NetworkAccessManager::handleTimeout()
nt->reply->abort();
}
void NetworkAccessManager::handleStarted()
void NetworkAccessManager::handleStarted(QNetworkReply* reply, int requestId)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
if (m_started.contains(reply))
return;
m_started += reply;
QVariantList headers;
foreach (QByteArray headerName, reply->rawHeaderList()) {
QVariantMap header;
@ -386,7 +372,7 @@ void NetworkAccessManager::handleStarted()
QVariantMap data;
data["stage"] = "start";
data["id"] = m_ids.value(reply);
data["id"] = requestId;
data["url"] = reply->url().toEncoded().data();
data["status"] = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
data["statusText"] = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
@ -395,21 +381,11 @@ void NetworkAccessManager::handleStarted()
data["redirectURL"] = reply->header(QNetworkRequest::LocationHeader);
data["headers"] = headers;
data["time"] = QDateTime::currentDateTime();
data["body"] = "";
emit resourceReceived(data);
}
void NetworkAccessManager::handleFinished(QNetworkReply *reply)
{
if (!m_ids.contains(reply))
return;
QVariant status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
QVariant statusText = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
this->handleFinished(reply, status, statusText);
}
void NetworkAccessManager::provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator)
{
if (m_authAttempts++ < m_maxAuthAttempts)
@ -420,12 +396,11 @@ void NetworkAccessManager::provideAuthentication(QNetworkReply *reply, QAuthenti
else
{
m_authAttempts = 0;
this->handleFinished(reply, 401, "Authorization Required");
reply->close();
m_replyTracker.abort(reply, 401, "Authorization Required");
}
}
void NetworkAccessManager::handleFinished(QNetworkReply *reply, const QVariant &status, const QVariant &statusText)
void NetworkAccessManager::handleFinished(QNetworkReply *reply, int requestId, int status, const QString &statusText, const QString& body)
{
QVariantList headers;
foreach (QByteArray headerName, reply->rawHeaderList()) {
@ -437,7 +412,7 @@ void NetworkAccessManager::handleFinished(QNetworkReply *reply, const QVariant &
QVariantMap data;
data["stage"] = "end";
data["id"] = m_ids.value(reply);
data["id"] = requestId;
data["url"] = reply->url().toEncoded().data();
data["status"] = status;
data["statusText"] = statusText;
@ -445,16 +420,14 @@ void NetworkAccessManager::handleFinished(QNetworkReply *reply, const QVariant &
data["redirectURL"] = reply->header(QNetworkRequest::LocationHeader);
data["headers"] = headers;
data["time"] = QDateTime::currentDateTime();
data["body"] = body;
m_ids.remove(reply);
m_started.remove(reply);
emit resourceReceived(data);
}
void NetworkAccessManager::handleSslErrors(const QList<QSslError> &errors)
void NetworkAccessManager::handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
foreach (QSslError e, errors) {
qDebug() << "Network - SSL Error:" << e;
}
@ -463,16 +436,15 @@ void NetworkAccessManager::handleSslErrors(const QList<QSslError> &errors)
reply->ignoreSslErrors();
}
void NetworkAccessManager::handleNetworkError()
void NetworkAccessManager::handleNetworkError(QNetworkReply* reply, int requestId)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
qDebug() << "Network - Resource request error:"
<< reply->error()
<< "(" << reply->errorString() << ")"
<< "URL:" << reply->url().toString();
QVariantMap data;
data["id"] = m_ids.value(reply);
data["id"] = requestId;
data["url"] = reply->url().toString();
data["errorCode"] = reply->error();
data["errorString"] = reply->errorString();

View File

@ -32,13 +32,13 @@
#define NETWORKACCESSMANAGER_H
#include <QAuthenticator>
#include <QHash>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QSet>
#include <QSslConfiguration>
#include <QTimer>
#include "networkreplytracker.h"
class Config;
class QNetworkDiskCache;
class QSslConfiguration;
@ -102,7 +102,6 @@ protected:
QString m_userName;
QString m_password;
QNetworkReply *createRequest(Operation op, const QNetworkRequest & req, QIODevice * outgoingData = 0);
void handleFinished(QNetworkReply *reply, const QVariant &status, const QVariant &statusText);
signals:
void resourceRequested(const QVariant& data, QObject *);
@ -111,20 +110,20 @@ signals:
void resourceTimeout(const QVariant& data);
private slots:
void handleStarted();
void handleFinished(QNetworkReply *reply);
void handleStarted(QNetworkReply* reply, int requestId);
void handleFinished(QNetworkReply *reply, int requestId, int status, const QString& statusText, const QString& body);
void provideAuthentication(QNetworkReply *reply, QAuthenticator *authenticator);
void handleSslErrors(const QList<QSslError> &errors);
void handleNetworkError();
void handleSslErrors(QNetworkReply* reply, const QList<QSslError> &errors);
void handleNetworkError(QNetworkReply* reply, int requestId);
void handleTimeout();
private:
QHash<QNetworkReply*, int> m_ids;
QSet<QNetworkReply*> m_started;
int m_idCounter;
QNetworkDiskCache* m_networkDiskCache;
QVariantMap m_customHeaders;
QSslConfiguration m_sslConfiguration;
NetworkReplyTracker m_replyTracker;
};
#endif // NETWORKACCESSMANAGER_H

156
src/networkreplyproxy.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <QNetworkRequest>
#include <QNetworkReply>
#include "networkreplyproxy.h"
NetworkReplyProxy::NetworkReplyProxy(QObject* parent, QNetworkReply* reply)
: QNetworkReply(parent)
, m_reply(reply)
{
// apply attributes...
setOperation(m_reply->operation());
setRequest(m_reply->request());
setUrl(m_reply->url());
// handle these to forward
connect(m_reply, SIGNAL(metaDataChanged()), SLOT(applyMetaData()));
connect(m_reply, SIGNAL(readyRead()), SLOT(readInternal()));
connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(errorInternal(QNetworkReply::NetworkError)));
// forward signals
connect(m_reply, SIGNAL(finished()), SIGNAL(finished()));
connect(m_reply, SIGNAL(uploadProgress(qint64,qint64)), SIGNAL(uploadProgress(qint64,qint64)));
connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(downloadProgress(qint64,qint64)));
// for the data proxy...
setOpenMode(ReadOnly);
}
QString NetworkReplyProxy::body()
{
return m_data.toBase64().data();
}
void NetworkReplyProxy::abort()
{
m_reply->abort();
}
void NetworkReplyProxy::close()
{
m_reply->close();
}
bool NetworkReplyProxy::isSequential() const
{
return m_reply->isSequential();
}
void NetworkReplyProxy::setReadBufferSize(qint64 size)
{
QNetworkReply::setReadBufferSize(size); m_reply->setReadBufferSize(size);
}
qint64 NetworkReplyProxy::bytesAvailable() const
{
return m_buffer.size() + QIODevice::bytesAvailable();
}
qint64 NetworkReplyProxy::bytesToWrite() const
{
return -1;
}
qint64 NetworkReplyProxy::readData(char* data, qint64 maxlen)
{
qint64 size = qMin(maxlen, qint64(m_buffer.size()));
memcpy(data, m_buffer.constData(), size);
m_buffer.remove(0, size);
return size;
}
void NetworkReplyProxy::ignoreSslErrors()
{
m_reply->ignoreSslErrors();
}
void NetworkReplyProxy::applyMetaData()
{
/*
We have to store headers and attributes, otherwise
QNetworkReply non-virtual methods (attribute, header, etc.)
would not have data to return.
*/
QList<QByteArray> headers = m_reply->rawHeaderList();
foreach(QByteArray header, headers)
setRawHeader(header, m_reply->rawHeader(header));
setHeader(QNetworkRequest::ContentTypeHeader, m_reply->header(QNetworkRequest::ContentTypeHeader));
setHeader(QNetworkRequest::ContentLengthHeader, m_reply->header(QNetworkRequest::ContentLengthHeader));
setHeader(QNetworkRequest::LocationHeader, m_reply->header(QNetworkRequest::LocationHeader));
setHeader(QNetworkRequest::LastModifiedHeader, m_reply->header(QNetworkRequest::LastModifiedHeader));
setHeader(QNetworkRequest::SetCookieHeader, m_reply->header(QNetworkRequest::SetCookieHeader));
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute));
setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute));
setAttribute(QNetworkRequest::RedirectionTargetAttribute, m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute));
setAttribute(QNetworkRequest::ConnectionEncryptedAttribute, m_reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute));
setAttribute(QNetworkRequest::CacheLoadControlAttribute, m_reply->attribute(QNetworkRequest::CacheLoadControlAttribute));
setAttribute(QNetworkRequest::CacheSaveControlAttribute, m_reply->attribute(QNetworkRequest::CacheSaveControlAttribute));
setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute));
setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, m_reply->attribute(QNetworkRequest::DoNotBufferUploadDataAttribute));
emit metaDataChanged();
}
void NetworkReplyProxy::errorInternal(QNetworkReply::NetworkError _error)
{
/*
Saving data for non-virtual QIODevice::errorString method
*/
setError(_error, m_reply->errorString());
emit error(_error);
}
void NetworkReplyProxy::readInternal()
{
QByteArray data = m_reply->readAll();
//this is a response buffer, whole response is stored here
m_data += data;
//this is a temporary buffer, data is wiped after a call to 'readData'
m_buffer += data;
emit readyRead();
}

93
src/networkreplyproxy.h Normal file
View File

@ -0,0 +1,93 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef NETWORKREPLYPROXY_H
#define NETWORKREPLYPROXY_H
#include <QNetworkReply>
/*
Proxy class for QNetworkReply. Collects response body, which can
be retreived using 'body' method.
*/
class NetworkReplyProxy : public QNetworkReply {
Q_OBJECT
public:
/*
reply - reply to be proxied(nested reply)
*/
NetworkReplyProxy(QObject* parent, QNetworkReply* reply);
/*
Returns collected body
*/
QString body();
/*
Returns nested reply
*/
QNetworkReply* nestedReply() const
{
//TODO: in cpp
return m_reply;
};
// QNetworkReply proxied methods
void abort();
void close();
bool isSequential() const;
// not possible...
void setReadBufferSize(qint64 size);
// QIODevice proxy...
virtual qint64 bytesAvailable() const;
virtual qint64 bytesToWrite() const;
virtual qint64 readData(char* data, qint64 maxlen);
public Q_SLOTS:
void ignoreSslErrors();
void applyMetaData();
void errorInternal(QNetworkReply::NetworkError _error);
void readInternal();
private:
QNetworkReply* m_reply;
QByteArray m_data;
QByteArray m_buffer;
};
#endif // NETWORKREPLYPROXY_H

153
src/networkreplytracker.cpp Normal file
View File

@ -0,0 +1,153 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "networkreplytracker.h"
#include "networkreplyproxy.h"
NetworkReplyTracker::NetworkReplyTracker(QObject* parent)
:QObject(parent)
{
}
QNetworkReply* NetworkReplyTracker::trackReply(QNetworkReply *reply, int requestId)
{
NetworkReplyProxy *proxy = new NetworkReplyProxy(this, reply);
/*
Tracking link between proxy and proxied reply.
Its possible to get nested reply as a slot argument instead of proxy.
QNetworkAccessManager::createRequest stores original reply internally
and passes it in certain conditions as a signal argument -
authenticationRequire slot, for instance, will receive original reply not the proxy.
*/
m_replies[reply] = proxy;
m_ids[proxy] = requestId;
connect(proxy, SIGNAL(readyRead()), this, SLOT(handleIncomingData()));
connect(proxy, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleNetworkError(QNetworkReply::NetworkError)));
connect(proxy, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(handleSslErrors(const QList<QSslError> &)));
connect(proxy, SIGNAL(finished()), SLOT(handleReplyFinished()));
return proxy;
}
void NetworkReplyTracker::abort(QNetworkReply *reply, int status, const QString& statusString)
{
finishReply(reply, status, statusString);
reply->close();
}
void NetworkReplyTracker::finishReply(QNetworkReply *reply, int status, const QString& statusText)
{
NetworkReplyProxy *proxy = getProxyFor(reply);
if (!m_ids.contains(proxy))
return;
m_replies.remove(proxy->nestedReply());
int requestId = m_ids.value(proxy);
m_ids.remove(proxy);
m_started.remove(proxy);
emit finished(proxy, requestId, status, statusText, proxy->body());
}
void NetworkReplyTracker::handleIncomingData()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
NetworkReplyProxy *proxy = getProxyFor(reply);
if (m_started.contains(proxy))
return;
m_started.insert(proxy);
emit started(proxy, m_ids.value(proxy));
}
void NetworkReplyTracker::handleReplyFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
QVariant status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
QVariant statusText = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
finishReply(reply, status.toInt(), statusText.toString());
}
void NetworkReplyTracker::handleSslErrors(const QList<QSslError> &errors)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
NetworkReplyProxy *proxy = getProxyFor(reply);
emit sslErrors(proxy, errors);
}
void NetworkReplyTracker::handleNetworkError(QNetworkReply::NetworkError err)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
NetworkReplyProxy *proxy = getProxyFor(reply);
emit error(proxy, m_ids.value(proxy), err);
}
NetworkReplyProxy* NetworkReplyTracker::getProxyFor(QNetworkReply* reply)
{
NetworkReplyProxy *proxy = NULL;
// first trying to find proxy assuming that we got nested reply
if(m_replies.contains(reply)) {
proxy = m_replies.value(reply);
} else {
//if not then it is proxy
proxy = qobject_cast<NetworkReplyProxy*>(reply);
}
return proxy;
}

100
src/networkreplytracker.h Normal file
View File

@ -0,0 +1,100 @@
/*
This file is part of the PhantomJS project from Ofi Labs.
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef NETWORKREPLYTRACKER_H
#define NETWORKREPLYTRACKER_H
#include <QtCore/qobject.h>
#include <QHash>
#include <QSet>
#include <QNetworkReply>
class QSslError;
class NetworkReplyProxy;
/*
This class tracks replies and converts reply signals
to phantomjs specific ones. It uses NetworkReplyProxy to collect
body of the response and passes body as a signal argument.
*/
class NetworkReplyTracker: public QObject {
Q_OBJECT
public:
NetworkReplyTracker(QObject* parent = 0);
/*
reply - reply to track
requestId - unique request id, used to distinguis replies internally
*/
QNetworkReply* trackReply(QNetworkReply *reply, int requestId);
/*
Abort request
status - set custom HTTP status code
statusString - text description of status code
*/
void abort(QNetworkReply *reply, int status, const QString& statusString);
signals:
void started(QNetworkReply* reply, int requestId);
void finished(QNetworkReply* reply, int requestId, int status, const QString& statusText, const QString& body);
void sslErrors(QNetworkReply*, const QList<QSslError>&);
void error(QNetworkReply*, int requestId, QNetworkReply::NetworkError);
private slots:
void handleIncomingData();
void handleReplyFinished();
void handleSslErrors(const QList<QSslError> &errors);
void handleNetworkError(QNetworkReply::NetworkError);
private:
Q_DISABLE_COPY(NetworkReplyTracker)
void finishReply(QNetworkReply *reply, int status, const QString& statusText);
NetworkReplyProxy* getProxyFor(QNetworkReply* reply);
QSet<QNetworkReply*> m_started;
QHash<QNetworkReply*, int> m_ids;
QHash<QNetworkReply*, NetworkReplyProxy*> m_replies;
};
#endif // NETWORKREPLYPROXY_H

View File

@ -24,6 +24,8 @@ HEADERS += \
webserver.h \
consts.h \
utils.h \
networkreplyproxy.h \
networkreplytracker.h \
networkaccessmanager.h \
cookiejar.h \
filesystem.h \
@ -42,6 +44,8 @@ SOURCES += phantom.cpp \
webserver.cpp \
main.cpp \
utils.cpp \
networkreplyproxy.cpp \
networkreplytracker.cpp \
networkaccessmanager.cpp \
cookiejar.cpp \
filesystem.cpp \

View File

@ -146,7 +146,8 @@ describe("WebPage object", function() {
});
});
xit("should return properly from a 401 status", function() {
it("should return properly from a 401 status", function() {
var page = require('webpage').create();
var server = require('webserver').create();
server.listen(12345, function(request, response) {
response.statusCode = 401;
@ -179,7 +180,36 @@ describe("WebPage object", function() {
});
xit("should set valid cookie properly, then remove it", function() {
it("should contain resource body when last chunk of resource received", function() {
var page = require('webpage').create();
var server = require('webserver').create();
server.listen(12345, function(request, response) {
response.statusCode = 200;
response.write('resource body');
response.close();
});
var url = "http://localhost:12345/foo";
var lastChunk = "";
runs(function() {
page.onResourceReceived = function(resource) {
lastChunk = resource.body;
};
page.open(url, function(status) {
});
});
waits(50);
runs(function() {
expect(lastChunk).toEqual('resource body');
page.onResourceReceived = null;
server.close();
});
});
it("should set valid cookie properly, then remove it", function() {
var server = require('webserver').create();
server.listen(12345, function(request, response) {
// echo received request headers in response body