How can I read content (http response body) from a QNetworkReply - c++

I'm using qt5.3 and I googled a lot before I post.
I want to read data from QNetworkReply. I have a QWebView and I also need the http response to be read by QWebView to display the webpage. What I need is just to log the web content or whatever response to http posts.
The problem is QNetworkReply is something that can only be read once.
If I call readAll() when I pick readyRead() signal, I will get the full data. But it will be cleared so QWebView displays nothing (it won't get any reply data).
Or if I pick finished() signal, since the data is already read by QWebView (or QNetworkAccessManager), I get nothing if I call readAll() here. Is there somewhere that QNetworkReply, or manager or any class, stores the data which I can still read?
In #1 I can get part of the data if I call peek(). This function does not clear the response data. But it won't work if the response body is big. The QNetworkReply is a sequential thing that I can neither know its data nor read further than buffered.
I have no idea of how to do with this.....
I just want to monitor and log the request and response body of any request made on my QWebView ...
Edit: note that my data to read from response is as large as 1MB so it won't be ok to peek the whole data without further reading.

You can create your own subclass of QNetworkAccessManager and override virtual function createRequest. Call base class implementation to get the response object and connect readyRead signal to some slot that will capture the data. In that slot call peek function to read data so that WebKit will get the data also :
class NetworkAccessManagerProxy : public QNetworkAccessManager {
Q_OBJECT
signals:
void dataGot(QByteArray data);
public:
NetworkAccessManagerProxy(QObject * parent = 0)
: QNetworkAccessManager()
{
}
virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice *outgoingData)
{
reply = QNetworkAccessManager::createRequest(op, request, outgoingData);
connect(this,SIGNAL(readyRead()), SLOT(readInternal()));
return reply;
}
private slots:
void readInternal()
{
QByteArray data = reply->peek(reply->bytesAvailable());
emit dataGot(data);
}
private:
QNetworkReply* reply;
};
After creating QWebPage object, call setNetworkAccessManager and pass a newly created instance of your subclass :
QWebPage * page = new QWebPage;
page->setNetworkAccessManager(new NetworkAccessManagerProxy());
page->mainFrame()->load(url);
webView->setPage(page);

Related

Json QtNetworkReply to QByteArray

I need to do a request in Qt/c++ to get a JSON file, and then parse it and fill my object.
The request seems good, and "it looks like" my QtNetworkReply reply is filled.
But after many attempts, I still don't understand how can I convert it into a QbyteArray (I don't even know if it's the right thing to do...), for being able to convert it into my class.
Here's my code :
QNetworkAccessManager networkManager;
QUrl url("https://api.myjson.com/bins/uvki"); //url from a free json host
QNetworkRequest request;enter code here
request.setUrl(url);
QNetworkReply* reply = networkManager.get(request);
QByteArray reponse;
if (reply == NULL)
{
std::cout << "Damn" << std::endl;
exit(2);
}
reponse = reply->readAll();
if (reponse == NULL)
{
std::cout << "i hate you" << std::endl;
exit(1000);
}
I might have done some stupid stuff, I only have 2 days of c++
Can you tell me how I can convert my "reply" into my "reponse"?
The answer provided by #MichaelBoone is correct.
In addtion, with C++11, you can simplify the code by using Qt 5's QObject::connection syntax and a lambda function
QJsonDocument document;
QNetworkReply* pReply = networkManager.get(request);
connect(reply, &QNetworkReply::finished, [=](){
// the reply will return here
QByteArray response = pReply->readAll();
document = QJsonDocument::fromBinaryData(response);
});
Qt 5's connections syntax has the advantage of compile-time verification of the connection, which is not present when using the SIGNAL and SLOT macros.
You have to connect the finished() signal from reply object, or from the NetworkManager to get the results. You will also need to make *reply a class member, or you won't be able to access it within your handler SLOT.
QNetworkReply* reply = networkManager.get(request);
connect(reply, SIGNAL(finished()), this, SLOT(YourFunctionHere()));
void YourFunctionHere(){
//handle the data
}
QNetworkReply is a non-blocking function, like most QT Network functions, it is asynchronous. By the time you are reaching your conditional if statement to check the reply, it hasn't yet received a response from the network.
As far as handling the download afterwards, you are correct in using a QByteArray.
QByteArray QIODevice::readAll()
This is an overloaded function.
Reads all available data from the device, and returns it as a
QByteArray.
From there you use QJsonDocument.
QJsonDocument QJsonDocument::fromBinaryData(const QByteArray & data,
DataValidation validation = Validate)
Creates a QJsonDocument from data.
Edit - Sorry I don't have the reputation to comment, but I feel The answer provided by TheDarkKnight lends itself better to the one-off nature of a "Reply" and is less encumbered by having to create a new slot. lambdas are just very cool, and the compile time verification is nice.

using QNetworkAccessManager GET multiple times

I am writing an application to request a web page at equal intervals in order to get any changes in it (to check whether new data is received). here how i did it.
private:
QNetworkReply *r;
QNetworkAccessManager *m;
QNetworkRequest request;
QTimer *timer;
in the constructor ,
m = new QNetworkAccessManager(this);
timer = new QTimer(this);
connect(r , SIGNAL(readyRead()), this , SLOT(readit()));
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
readit function,
void MainWindow::readit(){
QString st;
st=r->readAll();
m->deleteResource(request);
ui->textBrowser->append(st);
}
update function,
void MainWindow::update()
{
request.setUrl(QUrl("http://localhost/test/default.php"));
r = m->get(request);
}
my problem is m->get(request) gets the request at its first call only, when it is called again it does nothing. I did several experiments but end up with no success results. i changed the second request to another web page using a button click but it did nothing too.
So I need help from an expert how to update the get request and get new reply multiple times.
and also i want to know am i doing a correct thing or is there mo reliable methods to get data on data change from the server than checking for the website at regular intervals.
I see following problems:
readyRead fires an arbitrary number of times per request - including zero times (!), but you treat it as if it fired exactly once. Use the finished signal, which is does what you want: fires once, no more, no less.
The update slot doesn't connect any slots to the request.

Web service in blackberry 10

I am developing a BlackBerry 10 apps with Cascades (C++ programming language) right now. Can anyone tell me how do i make a call to web service in BlackBerry 10: Cascades? I'm just a beginner, so i don't really know anything. Thanks for your answer
void GetWeb::start(const QString &str)
{
QNetworkRequest request = QNetworkRequest();
request.setUrl(QUrl(str));
QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
networkAccessManager->get(request);
}
void GetWeb::requestFinished(QNetworkReply* reply)
{
if (reply->error() == QNetworkReply::NoError)
{
emit complete(reply->readAll());
}
reply->deleteLater();
}
In this case I am emiting the resulting string as a signal, but you could also just use the reply->readAll() string directly if you wished...
There's a few moving parts to sending a network request using Qt. Here's the example Qt uses:
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl("http://qt-project.org")));
So what you do is create a QNetworkAccessManager object, which handles the actual process of sending the request and processing the response. You then connect the signal that the manager emits when the QNetworkRequest has finished to a slot you've created called replyFinished which takes QNetworkReply * as a parameter, that might look like this:
void MyClass::replyFinished(QNetworkReply *serverResponse)
{
//do something with the response
}
You then use the managers get method to pass your QNetworkRequest, which you can create like it has been there, or separately. And that's about it, that's a minimal example that'll send a HTTP request to http://qt-project.org and return a response containing the data from the page, you can extend out from there to do things like get JSON or XML.
Example from: QtNetwork documentation

C++ - Qt QObject::connect GET request usage across classes

A quick overview of what's happening: I am trying to do a GET request using Qt's QNetworkAccessManager, but the callback function on my QObject::connect(..) function is not being called. My questions is can I call QObject::connect from one object, but connect to a slot of another object (given that I have a pointer to both the object and the slot) - See below for more details.
My ultimate goal is to POST the data (seeing as it's a login function), I had POST Request code that was ultimately suffering from the same issue - callback function not being called. So I would like to be able to do a simple GET request first, once I have that, I think I'll be fine on my own from there.
I currently have a QMainWindow LoginWindow, with a button that calls a slot doLogin() in the LoginWindow class. This all works as you would expect. LoginWindow also has a public slots function called loginResponse(QNetworkReply* response).
//---LoginWindow.h
...
public slots:
void doLogin();
void loginResponse(QNetworkReply* response)
...
//---LoginWindow.cpp
LoginWindow::LoginWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::LoginWindow)
{
ui->setupUi(this);
ui->username_le->setFocus();
}
void LoginWindow::doLogin()
{
MyProduct::Network network(this);
qDebug() << "Logging in...";
//Here I call network.login from LoginWindow and pass
//references to the Slot I want to use and the LoginWindow itself
network.login(
ui->username_le->text(), //username
ui->password_le->text(), //password
this, //reference to this object (LoginWindow*)
SLOT(loginResponse(QNetworkReply*)) //loginResponse slot
);
}
void LoginWindow::loginResponse(QNetworkReply* response)
{
qDebug() << "Log in complete";
}
Next I have another class, under the MyProduct namespace, called Network. As you can see above, Network has a function called login. Here it is:
void MyProduct::Network login(QString username, QString password, QObject *receiver, const char *slot)
{
QNetworkRequest request(QUrl(API_ROOT + LOGIN_PATH)); //"http://localhost/basic/login.php"
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
//nam = QNetworkAccessManager* declared in the constructor
QObject::connect(nam,SIGNAL(finished(QNetworkReply*)), receiver, slot);
qDebug() << "Posting login data...";
nam->get(request);
}
The goal here is to create a login function in my Network class that can be used and connected in any number of windows (as users may log in from multiple places). But I'm getting no response - LoginWindow::loginResponse is not run.
I see "Logging in..." and "Posting login data" output in the console, but not "Log in complete".
Can anyone please point me in the right direction or tell me I'm crazy or that this is a bad idea?
Thanks in advance!
Note that QNetworkAccessManager operates asynchronously. The get() method does not block while the network operation occurs; it returns immediately. (See the Detailed Description section of the documentation for more info.)
This is pretty typical of Qt's network-related APIs, because you usually don't want your application to freeze while waiting for data to move across a network.
What this means is what your instance, nam, isn't alive long enough for the GET request to actually finish. Your instance of the Product::Network class is deleted immediately after the call to login() because it's allocated on the stack. Although I can't see the code, I'm guessing it cleans up the QNetworkAccessManager as well.
If you extend the lifetime of your network object, you may find that your slot will eventually be invoked.
Also, this is more a matter of preference, but I think it would be cleaner to avoid passing a receiver and a slot to your login() function. I'd recommend declaring your own signals in the Network class as part of its API, and to connect to those in the LoginWindow class.

How to set QNetworkReply's content

This is my first post here, and I'm fairly new to Qt.
I am using Qwebkit in order to load a web page, and I'm interested in NOT fully load some resources from web. To be specific, I'm trying to get only the size of jpg files and not the image data from within the jpg's binary data (not HTML tags). For doing so, I have re-implemented the createRequest method of QNAM to do as follow:
QNetworkReply *NetworkAccessManager::createRequest(Operation op,const QNetworkRequest & req,QIODevice * outgoingData )
{
if (req.url().path().endsWith("jpg"))
{
CustomReply *reply = new CustomReply(QNetworkAccessManager::createRequest(op, req, outgoingData));
return reply->getQNR();
}else{
return QNetworkAccessManager::createRequest(op, req, outgoingData);
}
}
Then I connect some signal in my CustomReply class to append the coming data into a QByteArray, then I process the QByteArray to see if I have the marker I'm looking for. Now here I don't know how to proceed. What I want to do after this is closing the connection (to not download more) and passing the reply with the data I have received through CustomReply::getQNR(). I need to implement a function to set the content of my reply to the QByteArray I stored, and I have read this and that but couldn't solve my problem.
Thank you in advance.
I'm not 100% clear on your question but if you're trying to return the value of the reply you received to another QByteArray just set up an additional signal which is either fired on completion or called with emit and pass the value across that way.
I did as described here and solved my problem. I was missing the offset, so every time readData() was called, the data was read from the beginning. In order to close the connection I connect the finish signal of CustomReply into original QNReply. When I get enough data, I emit finish signal to close the connection. Calling abort or close will result in ERROR 5 (although you can handle the error but I find working with signal a bit cooler).
Thank you everybody.