I’m trying to implement a simple version checker below.
I’m getting a zero error code reading the file, but the file
contents show up blank. You can access the file via browser,
permissions are ok.
void check_version()
{
QNetworkAccessManager *nam = new QNetworkAccessManager();
QUrl data_url("http://www.example.com/version.txt");
QNetworkRequest req(data_url);
QNetworkReply *reply = nam->get(req);
QByteArray data = reply->readAll() ;
QString s1(data);
int err = reply->error();
QString s2 = QString::number(err);
delete reply;
delete nam;
QMessageBox::critical(0, "",s1+" "+s2,QMessageBox::Cancel);
}
I gather the problem is that I need to wait to read until the get is finished, so I need a signal and a slot: the signal tells the slot to read the data.
pseudocode:
QObject::connect(&rep, SIGNAL( rep is finished ),
QByteArray newver , SLOT( reply->readAll() ));
How do I set up a signal/slot for my task?
You're right, you have to wait until the get() is "finished" in order to obtain the whole response with a call to readAll().
Following a working example to start with:
// ...
QNetworkAccessManager *nam = new QNetworkAccessManager();
QUrl data_url("http://www.example.com/version.txt");
QNetworkReply* reply = nam->get(QNetworkRequest(data_url));
QEventLoop eventLoop;
QObject::connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
eventLoop.exec();
if (reply->error() != QNetworkReply::NoError)
{
// Something went wrong. Error can be retrieved with: reply->error()
}
else
{
// Call reply->readAll() and do what you need with the data
}
// ...
This example will block until the reply is ready. If you need an asynchronous behavior you can just connect the signal finished() to a custom slot and check for error and/or read there. I do not recommend to connect the signal finished() directly to readAll() because "sometimes errors happen".
Related
I have to use post to a google script (see below) and I want in the end of the script receive a feedback from it that everything went okay.
function doPost(e){
var idm = e.parameter.idm;
var sheet_purchaseHistory = SpreadsheetApp.getActive().getSheetByName('purchaseHistory');
var date = new Date();
var time = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
sheet_purchaseHistory.getRange(sheet_purchaseHistory.getLastRow()+1, 1).setValue(time);
sheet_purchaseHistory.getRange(sheet_purchaseHistory.getLastRow(), 2).setValue(mailadress);
return ContentService.createTextOutput(JSON.stringify({'status': 'success'})).setMimeType(ContentService.MimeType.JSON);
}
In my cpp code I have:
header (.h)
QNetworkRequest request;
QNetworkAccessManager *manager;
QByteArray ba;
QNetworkReply* reply;
Constructor:
manager = new QNetworkAccessManager(this); // I tried with and without parsing the (this)
request.setUrl(QUrl("https://script.google.com/macros/s/AKfycbzhHBpZ8jMfa2bwE_A0lQJNqOzVl894dcjkch_-0mNC6EX9usn1/exec"));
Inside a function:
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery postData;
postData.addQueryItem("idm", "string");
QNetworkReply *rp = manager->post(request,postData.toString(QUrl::FullyEncoded).toUtf8());
QEventLoop loop;
connect(rp, SIGNAL(finished()), &loop, SLOT(quit())); //readready SIGNAL also tried
loop.exec();
QByteArray data = rp->readAll();
QString dataReply(data);
qDebug() << dataReply;
What happens is that I always receive null. I already tried several combinations such as different signals (readyread, finished...), using QUrlQuery and ByteArray, setting different headers, but with no success.
I also noticed that, when my parameters are in my request (https://address?parameters=string) I can receive the return. (In this case by removing the parameters from "postdate" and including them in post "req"
QNetworkReply *rep = man->post(req,postdata);
PRINT OF THE RESTEStTEST: I tried to post using https://resttesttest.com/ and it worked properly. It gave the following comments
Could someone help me please?
With the help of the following command using curl I was able to analyze the request:
curl --trace-ascii output.txt \
-d "idm=0123" \
-L "https://script.google.com/macros/s/AKfycbzhHBpZ8jMfa2bwE_A0lQJNqOzVl894dcjkch_-0mNC6EX9usn1/exec"
In the .txt file is the following line:
# ...
== Info: Issue another request to this URL: 'https://script.googleusercontent.com/macros/echo?user_content_key=odFvp9PfkjN-H6-IhBUWhj1XC4LbgGXq3moew78NtUkeQAI4UCwjEaz33jH1p_VSrMLvl9R-1p2f-LCuIURpcA9ynEuB76Kdm5_BxDlH2jW0nuo2oDemN9CCS2h10ox_1xSncGQajx_ryfhECjZEnDmqvNuuY9_pIuNLYjrNDt9AJiVy2Q9_jt9ucsQKYIYmzKj2_WuA-BZmfTxlnQZT_cbqvF_hZXXo&lib=MbJJIMSCHwBgrN--y_1G9PbYl-ov70HvX'
== Info: Switch from POST to GET
# ...
That tells us that in the redirection is changed from POST to GET so it is not taken into account with the attribute QNetworkRequest::NoLessSafeRedirectPolicy that continues trying with POST. So the solution is to handle the redirect manually.
QNetworkAccessManager manager;
QNetworkRequest request;
QUrl url("https://script.google.com/macros/s/AKfycbzhHBpZ8jMfa2bwE_A0lQJNqOzVl894dcjkch_-0mNC6EX9usn1/exec");
request.setUrl(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery postData;
postData.addQueryItem("idm", "string");
QNetworkReply *reply_post = manager.post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
QEventLoop loop;
QObject::connect(reply_post, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
QNetworkRequest redirect_request;
redirect_request.setUrl(reply_post->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
reply_post->deleteLater();
QNetworkReply *reply_get = manager.get(redirect_request);
QObject::connect(reply_get, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
qDebug()<< reply_get->readAll();
reply_get->deleteLater();
Output:
OK
I have 1 QNetworkAccessManager in my application and I am making 2 requests at the same time. When I get the reply back from the manager the replies are not in the order I called them and that makes sense. How can I work around that? Should I have another manager to weed out any request queues issues?
QNetworkRequest request1(ONE_GET);
request1.setRawHeader("Content-Type", "application/vnd.api+json");
request1.setRawHeader("Accept", "application/vnd.api+json");
m_nam.get(request1);
connect(&m_nam, &QNetworkAccessManager::finished,this , &HelperClass::onReply1Recieved);
QNetworkRequest request2(TWO_GET);
request2.setRawHeader("Content-Type", "application/vnd.api+json");
request2.setRawHeader("Accept", "application/vnd.api+json");
m_nam.get(request2);
connect(&m_nam, &QNetworkAccessManager::finished,this , &HelperClass::onReply2Recieved);
The problem in your case is that both slots are connecting to the same signal so both will be notified and even if you try to disconnect the signal that does not guarantee that it works correctly, the solution is to connect the signal of each of the QNetworkReply:
QNetworkRequest request1(ONE_GET);
request1.setRawHeader("Content-Type", "application/vnd.api+json");
request1.setRawHeader("Accept", "application/vnd.api+json");
QNetworkReply *reply1 = m_nam.get(request1);
connect(reply1, &QNetworkReply::finished, this, &HelperClass::onReply1Recieved);
QNetworkRequest request2(TWO_GET);
request2.setRawHeader("Content-Type", "application/vnd.api+json");
request2.setRawHeader("Accept", "application/vnd.api+json");
QNetworkReply *reply2 = m_nam.get(request2);
connect(reply2, &QNetworkReply::finished, this, &HelperClass::onReply2Recieved);
void HelperClass::onReply1Recieved(){
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
qDebug() << reply->readAll();
}
void HelperClass::onReply2Recieved(){
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
qDebug() << reply->readAll();
}
I would like to make a class for accessing data via REST API, for example:
class MeteoStation{
int getLatestTemperature();
int getLatestPessure();
private:
QNetworkManager nmng;
}
How could I implement this methods? Usually I was using something like:
int MeteoStation::getLatestTemperature(){
...
QEventLoop eventLoop;
connect(&m_nam,SIGNAL(finished(QNetworkReply*)),&eventLoop,SLOT(quit()));
QNetworkReply *reply = m_nam.get( req );
eventLoop.exec();
reply->readAll()
...
}
But since using inner QEventLoop is not recommended, how should I see to whom the response belong to?
MeteoStation::MeteoStation(){
connect(&nmam, SIGNAL(finished(QNetworkReply*)),
this, SLOT(parseNetworkResponse(QNetworkReply*)));
}
void MeteoStation::parseNetworkResponse( QNetworkReply *finished )
{
QByteArray data = finished->readAll();
...
Yes and it would be nice to have the class thread save. How are you solving that in your code?
How bad is making the call synchronous with:
QNetworkRequest req(url);
QScopedPointer<QNetworkReply> reply(nam.get(req));
QTime timeout= QTime::currentTime().addSecs(10);
while( QTime::currentTime() < timeout && !reply->isFinished()){
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Failure" <<reply->errorString();
}
QByteArray data = reply->readAll();
I've resolved my problem using QCoreApplication::processEvents(). The response is there within ms and I'm able to implement functionality close to libcurl.
QNetworkRequest req(url);
QScopedPointer<QNetworkReply> reply(nam.get(req));
QTime timeout= QTime::currentTime().addSecs(10);
while( QTime::currentTime() < timeout && !reply->isFinished()){
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Failure" <<reply->errorString();
}
QByteArray data = reply->readAll();
The Qt docs should provide all info you need.
You creat a nam, connect the finished signal, send the request.
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(parseNetworkResponse(QNetworkReply*)));
manager->get(QNetworkRequest(QUrl("http://qt-project.org")));
Detecting to which request a reply belongs should not be too hard. The reply contains the url. It might be different, but not that different:
...but for a variety of reasons it can be different (for example, a
file path being made absolute or canonical).
QUrl QNetworkReply::url() const
Returns the URL of the content downloaded or uploaded. Note that the
URL may be different from that of the original request.
I'm trying to have my application download a file from a URL, typically an EXE or a Jar, not that this should change much though. I have this all running in a thread, but I don't think that will make a difference (if it does let me know).
So Do_Download is my function that creates the manager, sets the URL and request, and performs get. I then try to connect the finished signal to the slot the will write the file.
void DownloadManager::Do_Download() {
QNetworkAccessManager *netManager = new QNetworkAccessManager(this);
QUrl url(install_mirror); //istall_mirror is the URL provided by user
QNetworkRequest req(url);
QNetworkReply *reply = netManager->get(req);
connect(reply, SIGNAL(finished()), this, SLOT(writeData()));
}
My writeData function checks for errors, and if there are no errors it writes the data to file.
void DownloadManager::writeData() {
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply) {
if (reply->error() == QNetworkReply::NoError) {
QFile file(location);
if(file.open(QIODevice::WriteOnly)) {
file.write(reply->readAll());
} else {
errorMessage = "Error writing downloaded file for mirror installation";
}
} else {
//get http status code
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
errorMessage = "HTTP Error code while downloading from mirror: " + httpStatus;
}
reply->deleteLater();
} else {
errorMessage = "Error downloading file from installation mirror";
}
}
The problem being there is no data being written. It just creates a 0Kb file.
I tried adding a download progress slot so I could see what was going on recieving the data. So I added this to my Do_Download method.
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(DL_Progress(qint64,qint64)));
void DownloadManager::DL_Progress(qint64 recieved, qint64 total) {
std::cout << recieved << " / " << total << endl;
}
The output displays one time as 0 / 01
What am I doing wrong?
The only problem I see in your code is you are not waiting for the download to be finished. The NetworkRequest object would be destructed at the end of function call.
So, I would rewrite Do_Download like this (QEventLoop syncronizes the network request):
void DownloadManager::Do_Download() {
QEventLoop eventLoop;
QNetworkAccessManager *netManager = new QNetworkAccessManager(this);
QUrl url(install_mirror); //istall_mirror is the URL provided by user
QNetworkRequest req(url);
QNetworkReply *reply = netManager->get(req);
connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
eventLoop.exec();
writeData(reply);
}
I know that in version 4.8 each http request gets its own thread to run.
I'm doing a links checker app that does a lot of http requests in a while loop and I notice in the windows task manager that my app is using more than 1600 threads over time and the number never goes down, only up until it crashes the app. (I'm guessing that is the cause.)
My question is, does QNetworkAccessManager have an option to use thread pool?
Or does it have an option to clean its threads after it finishes its http request?
This is the main loop:
while(!rpm_urlStack->isEmpty())
{
QString url = rpm_urlStack->top();
//define the reply
QNetworkReply *reply;
rpm_urlStack->pop();
QString urlForReq(url);
bool returnVal = true;
QNetworkRequest request;
request.setUrl(QUrl(urlForReq));
request.setRawHeader("User-Agent", USER_AGENT.toUtf8());
request.setRawHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
request.setRawHeader("Accept-Language", "en-us,en;q=0.5");
request.setRawHeader("Connection", "Keep-Alive");
QEventLoop loop;
reply = m_networkManager->get(request);
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exit();
if(!loop.isRunning()) {
loop.exec();
}
RequestFinishedHandler(reply);
// this is how I delete the reply object
delete reply;
}
RequestFinishedHandler(QNetworkReply *reply)
{
if (reply->error() > 0) {
QNetworkReply::NetworkError networkError = reply->error();
QString err = reply->errorString();
} else {
QVariant vStatusCodeV = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
QMutexLocker lock(_pMutex); // _pMutex defined as class member
char *buffer;
buffer = getCurrentDateTime();
QTextStream out(m_file);
out << buffer << " " << _sCurrentUrl << "\n";
lock.unlock();
if(vStatusCodeV.toInt() == 200) {
QString ApiResponse;
QByteArray data;
data=reply->readAll();
ApiResponse.append(QString::fromUtf8(data));
}
}
}
It appears that to be effective, the deleteLater method must be called from within an event loop, which must regain control of execution to handle garbage collection.
Maybe you should refactor your code to have the event loop replace your while loop. Alternatively, since you're not using the finished slot to process the reply, perhaps you can delete the reply directly at the end of the RequestFinishedHandler function.