Qt retrieve additional data created during request when QNetworkAccessManager finished - c++

I would like to create an HTTP request and retrieve in the response some variables create during the request.
However, QNetworkAccessManager reponse is asynchronous and use SIGNAL/SLOT functionality, so variable are no more accessible.
Here is an example :
void makeRequest()
{
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
// here is the datas I want to get in the reply
// objectdata and objectdata2 are created just before the http request
MyClass objectdata("randomseed");
MyClass2 objectdata("randomseed") ;
QUrl websiteurl = objectdata.getUrl();
manager->get(QNetworkRequest(websiteurl));
}
void replyFinished (QNetworkReply *reply)
{
if(reply->error())
{
qDebug() << "ERROR!";
qDebug() << reply->errorString();
}
else
{
QByteArray dataHttp = reply->readAll();
// How can I get here objectdata and objectdata2 ?
// I would like to do something like that this->dataSuccess(objectdata,objectdata2,dataHttp);
}
reply->deleteLater();
}
In a synchronous system this problematic doesn't exist.
Is there a workaround to this problematic ?

One way to do it is to capture the locals in a lambda instead of using a separate function for the slot.
void makeRequest()
{
manager = new QNetworkAccessManager(this);
// here is the datas I want to get in the reply
// objectdata and objectdata2 are created just before the http request
MyClass objectdata("randomseed");
MyClass2 objectdata("randomseed") ;
connect(manager, &QNetworkAccessManager::finished, [this, objectdata, objectdata2](QNetworkReply *reply)
{
if(reply->error())
{
qDebug() << "ERROR!";
qDebug() << reply->errorString();
}
else
{
QByteArray dataHttp = reply->readAll();
dataSuccess(objectdata,objectdata2,dataHttp);
}
reply->deleteLater();
}
QUrl websiteurl = objectdata.getUrl();
manager->get(QNetworkRequest(websiteurl));
}
Another way is to add extra parameters as an attribute in the QNetworkRequest.
// Save off the data in the request
QNetworkRequest req(websiteurl);
req.setAttribute(QNetworkRequest::User, QVariant::fromValue<MyClass>(objectdata));
req.setAttribute(QNetworkRequest::User + 1, QVariant::fromValue<MyClass2>(objectdata2));
manager->get(req);
...
// Retrieve the data from the reply
auto data1 = reply->request().attribute(QNetworkRequest::User).value<MyClass>();
auto data2 = reply->request().attribute(QNetworkRequest::User + 1).value<MyClass2>();

Related

How to get data for a custom widget using an async source

I'm new to QT and I would like some help. If any of you could help me I would really appreciate it.
QUESTION:
I have an asynchronous class that makes an HTTP request and it's going to receive some data into a JSON format and from there I will extract the necessary information which should be passed to my custom widget. How can I do that? Because I don't know when the information will arrive.
WHAT I'VE DONE SO FAR:
My HTTP request and parsing JSON class:
WeatherAPI::WeatherAPI(QObject *parent) : QObject(parent) {
manager = new QNetworkAccessManager(this);
QObject::connect(manager, SIGNAL(finished(QNetworkReply * )), this, SLOT(readData(QNetworkReply * )));
}
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
QString strReply = (QString) reply->readAll();
QJsonDocument jsonResponse = QJsonDocument::fromJson(strReply.toUtf8());
QJsonObject jsonObject = jsonResponse.object();
weatherObject.city = jsonObject["name"].toString();
weatherObject.temperature = QString::number(jsonObject["main"].toObject()["temp"].toDouble() - 273.15);
int ts = jsonObject["dt"].toInt();
weatherObject.time = QDateTime::fromSecsSinceEpoch(ts).toString("hh:mm");
auto weatherData = jsonObject["weather"].toArray()[0].toObject()["main"].toString();
if (weatherData == "Clouds") {
weatherObject.icon = "Sun.png";
}
} else {
qDebug() << "ERROR";
}
}
void WeatherAPI::requestDataForCity(const QString &city) {
QString link = linkTemplate.arg(city, key);
QUrl url(link);
manager->get(QNetworkRequest(url));
}
const WeatherObject &WeatherAPI::getWeatherObject() const {
return weatherObject;
}
Now here is my custom Widget:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
WeatherObject data = api->getWeatherObject();//HERE I'm getting the DATA
m_title = data.city;
m_time = data.time;
m_icon = data.icon;
m_temperature = data.temperature;
}
}
In that function from WeatherButton::initStyle I'm going to make an HTTP request and also I'm going to place the data into the necessary variable. Now my question is... How can I wait for that data to be received and just after that to place them into those variables?
So far the only solution I know so far is to use a QEventLoop, but at that moment I'm going to basically convert an async call into a sync one, which is not quite what I want. I want to be fully async.
WeatherObject data = api->getWeatherObject(); //HERE I'm getting the DATA
No, you do not get the data here. WeatherAPI::readData is where you get the data.
That is the point of the Signal - Slot mechanism. You do not wait for an event to happen, but react to it via callback, i.e. slot.
Having that in mind, you have to rethink and extend your code. Here is one way to do this:
In the WeatherAPI class define a dataReady(const WeatherObject &weatherObject) signal
Emit this signal in WeatherAPI::readData like this:
void WeatherAPI::readData(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
// the processing of the http response remains unchanged
// ...
emit dataReady(weatherObject);
} else {
qDebug() << "ERROR";
}
}
In the WeatherButton class define a onDataReady slot with the following implementation:
void WeatherButton::onDataReady(const WeatherObject &weatherObject) {
m_title = weatherObject.city;
m_time = weatherObject.time;
m_icon = weatherObject.icon;
m_temperature = weatherObject.temperature;
}
Connect the newly created signal and slot in WeatherButton::initStyle like this:
void WeatherButton::initStyle(const QJsonValue &json) {
PolygonButtonWidget::initStyle(json);
auto cities = json.toObject()["cities"].toArray();
api = new WeatherAPI(this);
connect(api, &WeatherAPI::dataReady, this, &WeatherButton::onDataReady);
for (auto c: cities) {
QString city = c.toString();
api->requestDataForCity(city); // HERE I'm making the http request
}
}
As a sidenote I should say, that initStyle is probably not the best place to instantiate the WeatherAPI. api seems to be an attribute of WeatherButton, hence it should be initialized in the constructor of the class.

Wait till request finished Qt

My program is getting data from mysql table via json format through php script, that generating json.
My function sends to server post request (with some params) and getting response. With that all okay. Works fine.
But, when I want to get specified cell (data from json array), it takes empty string (program works fast and taking data from a string before it sets up)
Here's the code with post request:
void dbase::requestor(QString option)
{
curr_js = ""; //nulling string
QString urla = host+"transfer.php";
// /////////
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QUrl url(urla);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery params;
params.addQueryItem("api", key);
params.addQueryItem("option", option);
//QEventLoop loop;
connect(manager, SIGNAL(finished(QNetworkReply *)),this, SLOT(replyFinished(QNetworkReply *)));
//loop.exec(); tryed eventloop, but bcuz of this my replyFinished is not accessible. error code under this one
manager->post(request, params.query().toUtf8());
}
If using eventloop
QObject::connect: No such slot QEventLoop::replyFinished(QNetworkReply *)
replyFinished function (setting up variable)
void dbase::replyFinished(QNetworkReply *reply)
{
curr_js = reply->readAll();
}
Usage (where is problem)
QString req = "SOME REQUEST";
database.requestor(req);
if (database.db_cell (0,0) == "")
{
qDebug()<<database.db_cell (0,0) << " - EMPTY - "<<database.curr_js;
}
So in this case I'm getting
"" - EMPTY - ""
But if I getting data from string (created button for test), it's there:
{"0":["1", "Admin","hashpwd"],"1":["2", "Albert","hashpwd"]}
db_cell function
QString dbase::db_cell (int indexrow, int indexcols)
{
QJsonDocument sd = QJsonDocument::fromJson(curr_js.toUtf8());
QJsonObject setobj = sd.object();
QJsonValue qqq = setobj.value(QString(QString::number(indexrow))).toArray()[indexcols];
return qqq.toString();
}
As I see, problem is that program need to wait before getting a data from json-string.

How do I know a QNetWorkReply comes from which proxy?

It's a HTTP request sending method. When the goal website responses, httpFinished() will be called.
void HTTPClientBase:: HttpRequestGet()
{
network_manager.setProxy(proxy);
QNetworkRequest network_request;
network_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
network_request.setUrl(URL);
reply = network_manager.get(network_request);
connect(reply, SIGNAL(finished(QNetWorkReply*)), this, SLOT(httpFinished(QNetWorkReply*)));
}
void HTTPClientBase::httpFinished(QNetWorkReply* reply)
{
// How do I know this reply comes from which proxy?
}
I can call HttpRequestGet() in a loop.
static HTTPClientBase myClient;
for(int i=0; i<20; i++)
{
myClient.setUrl("https:\\www.google.com");
myClient.setProxy("123.123.123.123:1234"); // The proxy changes every time in this loop.
myClient.HttpRequestGet();
}
When HTTPClientBase::httpFinished(QNetWorkReply* reply) is called, How do I know this reply comes from which proxy?
}
As per the QNetworkReply document, you can get the corresponding request using the member function QNetworkReply::request().
Anyway, QNetworkRequest has not member function of setProxy.
But if you are setting proxy for QNetworkAccessManager you can have a pointer to the corresponding manager by QNetworkReply::manager().
Notice the connect command. finished() has no QNetworkReply* argument so your connect command will also fail and you have to change HTTPClientBase::httpFinished(QNetWorkReply* reply) to HTTPClientBase::httpFinished().
...
connect(reply, SIGNAL(finished()), this,
SLOT(httpFinished()));
}
void HTTPClientBase::httpFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(reply){
QNetworkAccessManager * manager = reply->manager();
QNetworkProxy proxy = manager->proxy();
// now you have the proxy
}
}
As you see, you have to use sender() to obtain the actual signal sender.
You need to create different QNetworkAccessManager instances for each proxy you have. If you have a proxy pool, create your QNetworkAccessManager instances first and choose them according to your specific needs.
If you don't want to create a new QNetworkAccessManager for each proxy, you can do something like this:
void HTTPClientBase:: HttpRequestGet()
{
network_manager.setProxy(proxy);
QNetworkRequest network_request;
network_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
network_request.setUrl(URL);
reply = network_manager.get(network_request);
//new member variable: QHash<QNetworkReply*,QString> m_RequestToProxy;
m_RequestToProxy.insert(reply,proxy);
connect(reply, SIGNAL(finished()), this, SLOT(httpFinished()));
}
void HTTPClientBase::httpFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(reply){
QString proxy = m_RequestToProxy.take(reply);
//check if proxy is valid
//and then do whatever you want
}
}
And another better solution is to set a property of reply and get it in slot.
void HTTPClientBase:: HttpRequestGet()
{
network_manager.setProxy(proxy);
QNetworkRequest network_request;
network_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
network_request.setUrl(URL);
reply = network_manager.get(network_request);
reply->setProperty("_proxy_",proxy);
connect(reply, SIGNAL(finished()), this, SLOT(httpFinished()));
}
void HTTPClientBase::httpFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if(reply){
QVariant v_proxy = reply->property("_proxy_");
//check if proxy is valid
if(v_proxy.isValid()){
QString proxy = v_proxy.toString();
//and then do whatever you want
}
}
}

How to intercept AJAX-Requests within QtWebKit?

I want to intercept, inspect and (if needed) reject AJAX-Requests based on the Fingerprint of the SSL-Certificate. I use the QNetworkAccessManager::createRequest(...) function to issue requests. Everything works fine when I use QWebFrame::load(...). Even the content which is loaded within the request (like .css or .js files) emit signals. Unfortunately no AJAX-Requests emits any Signals. I know that the Signals are connected to the very same slots (for "normal" as well as AJAX-Requests) within MyNetworkAccessManager::createRequest(...) function.
QNetworkReply *reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
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()));
Why are AJAX Requests so different? Where can I access them?
From what I can tell, AJAX requests do emit the finished signal on QNetworkAccessManager. You need connect to the instance of QNetworkAccessManager on your QWebPage instance:
QWebPage *page = ui->webView->page();
QNetworkAccessManager *nam = page->networkAccessManager();
connect(nam, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
QFile file;
file.setFileName(":/js/jquery-2.1.1.min.js"); // jQuery is loaded as a resource
file.open(QIODevice::ReadOnly);
QString jQuery = file.readAll();
file.close();
ui->webView->load(QUrl("about:blank"));
QWebFrame *frame = m_page->mainFrame();
frame->evaluateJavaScript(jQuery); // load jQuery
// check that jQuery is loaded
frame->evaluateJavaScript("$(document).ready(function() { alert('jQuery loaded!'); });");
// do an AJAX GET
frame->evaluateJavaScript("$.ajax({"
"url: 'http://www.json-generator.com/api/json/get/cqkXBAEoQy?indent=2',"
"method: 'GET',"
"dataType: 'json'"
"}).done(function (data) {"
"for (var i = 0; i < data.length; i++) {"
"alert(data[i].name);"
"}"
"}).error(function (data) { alert('AJAX error'); });");
Then you can monitor replies in the replyFinished slot like so:
void MainWindow::replyFinished(QNetworkReply *reply)
{
QByteArray bytes = reply->readAll();
QString str = QString::fromUtf8(bytes.data(), bytes.size());
QString replyUrl = reply->url().toString();
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << statusCode;
qDebug() << replyUrl;
qDebug() << str;
}
I have noticed that jQuery AJAX's done promise doesn't seem to execute when you do anything with the QNetworkReply, but you can see that the request actually finishes in the debug console.
See my GitHub repository to try out the above code: https://github.com/pcmantinker/QtWebkitAJAX
As far as blocking connections based on SSL certificates, you'd have to subclass QNetworkAccessManager and override QNetworkAccessManager::createRequest. Something like this could work:
QNetworkReply *CustomQNetworkAccessManager::createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
{
QNetworkRequest req(request);
QNetworkReply *reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
QSslConfiguration *sslConfig = reply->sslConfiguration();
QList<QSslCertificate> sslCaCerts = sslConfig->caCertificates();
// do something with sslCaCerts
return reply;
}

how to return an Qt object from the result of an thread (Qtfutur)

i'm trying to load some data from a server and fill a Qt list. i want to run the dowloanding in a thread. so there is the code of:
principal function in the App.cpp
loadInterestPlaces(QString& urlInterestPlaces) {
LoadData* Data = new LoadData(urlInterestPlaces);
QFuture< list <InterestPlace *> > future = QtConcurrent::run(Data,
&LoadData::startLoading);
// Invoke our onLoadingFinished slot after the loading has finished.
bool ok = connect(&m_watcher, SIGNAL(finished()), this,
SLOT(onLoadingFinished()));
Q_ASSERT(ok);
Q_UNUSED(ok);
// starts watching the given future
m_watcher.setFuture(future);
}
void ApplicationUI::onLoadingFinished() {
qDebug() << "Loading finished";
interestPlacesList = m_watcher.future().result();
qDebug() << "List size= " << interestPlacesList.size();
}
}
the LoadData.cpp file : this is the code of the startloanding function :
std::list<InterestPlace *> LoadData::startLoading()
{
QNetworkAccessManager* netManager = new QNetworkAccessManager(this);
const QUrl url(_URL);
QNetworkRequest request(url);
QNetworkReply* reply = netManager->get(request);
netManager->moveToThread(this->thread());
netManager->setParent(this);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
qDebug() << reply->isFinished();
Q_ASSERT(ok);
Q_UNUSED(ok);
qDebug() << "load data: liste size" <<interestPlacesList.size();
return interestPlacesList;
}
Finally inside the SLOT onreplyfinished i parse the data and fill the list.
But the problem here, the QFuture is finished before the downloading so that the list is always empty.
How could i return the list filled just after the execution of the onReplyFinished ?
You may be making this more complex than you need to. Here is what I do to get data off the web. In my case I'm downloading a large ZIP file, writing it out and then unziping it, but the basic steps are the same (I have omitted a lot of my specific code for clarity):
void HtmlDownloader::startDownload() {
// Creates the network request and sets the destination URL
QNetworkRequest request = QNetworkRequest();
request.setUrl(mUrl);
// Creates the network access manager and connects a custom slot to its
// finished signal. Checks the return value for errors.
QNetworkAccessManager *networkAccessManager =
new QNetworkAccessManager(this);
bool c = connect(networkAccessManager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(requestFinished(QNetworkReply*)));
Q_ASSERT(c);
// Indicate that the variable res isn't used in the rest of the app, to prevent
// a compiler warning
Q_UNUSED(c);
// Sends the request
QNetworkReply *reply = networkAccessManager->get(request);
c = connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(onDownloadProgress(qint64,qint64)));
Q_ASSERT(c);
}
void HtmlDownloader::requestFinished(QNetworkReply *reply) {
// Handle the reply data...
// Check the network reply for errors
if (reply->error() == QNetworkReply::NoError) {
// use reply->readAll() or reply->read() to get the returned data
// and process into a list. The list can't be returned but could
// be sent as a payload of a signal, or stored in a data member and
// a signal sent to indicate it is available.
}
reply->deleteLater();
}
void HtmlDownloader::onDownloadProgress(qint64 bytesRecieved, qint64 bytesTotal) {
emit downloadProgress(bytesRecieved, bytesTotal);
}