My code sometimes crashes due to future's async.
This is not production code, just very simple stuff for demonstrating what i want to do.
I have to download some info and then return first obtained result. Just imagine i do not want to wait so i'm using QFutureWatcher too speed up the downloading.
When i obtained the result (first or second instance) i want to return the result IMMEDIATELY (i mean i do not want to use QFutureWatcher::waitForFinished.
(If i received finished signal from first instance, i no longer need the second one. Or the same for the first.
In production code function download i cannot access to QNetworkReply, so the pending request could not be aborted.
Sometimes this code crashes. What am i doing wrong?
#include <QFutureWatcher>
#include <QtConcurrent>
#include <QEventLoop>
#include <QTimer>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
QByteArray download(const QUrl &url)
{
QNetworkAccessManager manager;
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::SynchronousRequestAttribute, true);
QNetworkReply *reply = manager.get(req);
reply->deleteLater();
return reply->readAll();
}
QByteArray downloadSmth(const QUrl &urlFirst, const QUrl &urlSecond)
{
typedef QFutureWatcher<QByteArray> FutureWatcher;
FutureWatcher *first = new FutureWatcher;
FutureWatcher *second = new FutureWatcher;
QByteArray res;
QEventLoop loop;
QObject::connect(first, &FutureWatcher::finished, [&res, &loop, first] {
res = first->result();
loop.quit();
});
QObject::connect(second, &FutureWatcher::finished, [&res, &loop, second] {
res = second->result();
loop.quit();
});
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
first->setFuture(
QtConcurrent::run([&urlFirst] {
return download(urlFirst);
}));
second->setFuture(
QtConcurrent::run([&urlSecond] {
return download(urlSecond);
}));
timer.start(60 * 1000);
loop.exec();
if (timer.isActive()) {
timer.stop();
}
first->deleteLater();
second->deleteLater();
return res;
}
Related
i'm quite starting OOP so i can make incredible stupid things.
Anyway.
I've got 2 cameras (or 4) witch own their tcp server for providing MJPEG video stream.
on one base computer i need to handle 2 (or 4) tcp client connections to these units.
if i just have one instance, or one camera to handle, it works. but with multiple, it fail in bad tcp connections, I my mind, the issue should be with the instanciation of 2 QTcpsocket objects.
I tried with thread, with slots, still the same, when i instanciate more than one object of my client class, the connection read nothing or auto close. I have different TCP port for different units before you tell that.
have a look on my main code sirs:
MAIN.CPP
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDebug>
#include <QImage>
#include "streamerclient.h"
#include "camimageprovider.h"
#include "streamerthread.h"
#include "streamerthread2.h"
#include<unistd.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
StreamerThread clientSat1(1238,"SAT1-SNTL");clientSat1.start();
CamImageProvider *camImageProviderSat1(new CamImageProvider());
CamImageProvider *camImageProviderSat2(new CamImageProvider());
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("camImageProviderSat1",camImageProviderSat1);
engine.addImageProvider("camSat1", camImageProviderSat1);
engine.rootContext()->setContextProperty("camImageProviderSat2",camImageProviderSat2);
engine.addImageProvider("camSat2", camImageProviderSat2);
const QUrl url(u"qrc:/baseApp001/main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);//const QImage &)) ,Qt::DirectConnection
QObject::connect(&clientSat1, SIGNAL(newImage(QImage)),camImageProviderSat1, SLOT(updateImage(QImage)));
QObject::connect(&clientSat1, SIGNAL(newImage(QImage)),camImageProviderSat2, SLOT(updateImage(QImage)));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
CLIENT OBJECT DECLARATION
#ifndef STREAMERTHREAD_H
#define STREAMERTHREAD_H
#include <QThread>
#include <QTcpSocket>
#include<QDebug>
#include <QObject>
#include <QTimer>
#include <QVector>
#include <QVariant>
#include <QImage>
#include <QFuture>
class StreamerThread : public QThread
{
Q_OBJECT
Q_PROPERTY(NOTIFY newImage)
public:
StreamerThread(int port,char *satName,QObject *parent=0);
qint64 newTcpDataRead();
void run();
QImage img(){return m_Image;}
private:
QTcpSocket *socket;
int socketDescriptor;
QImage m_Image;
QByteArray m_baImage; // Variable contenant l'image reçue.
bool m_quit; // Variable permettant de savoir que l'application est en cours de fermeture.
char m_satName[16];
private slots:
void slotQuit(); // Slot appelé lors de la fermeture de l'application.
signals:
void newImage(const QImage &);
};
#endif // STREAMERTHREAD_H
CLIENT OBJECT DESCRIPTION
#include "streamerthread.h"
#include<iostream>
#include<QImage>
#include<QDebug>
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
char datout=0;
int m_port=0;
StreamerThread::StreamerThread(int port,char *satName,QObject *parent)
{
m_quit = 0;
m_port=port;
strcpy(m_satName,satName);
this->socketDescriptor=port;
//m_Image = QImage(640,480, QImage::Format_RGB888);//RGB32);
m_Image = QImage(1280,720, QImage::Format_RGB888);//RGB32);
}
void StreamerThread::run()
{
qInfo()<<this<<" construit pour "<<m_satName <<"d: "<<this->socketDescriptor;
//socket = new QTcpSocket();
socket = new QTcpSocket();
socket->setSocketDescriptor(this->socketDescriptor);
// socket->connectToHost("127.0.0.1", 1234);
socket->connectToHost(m_satName, m_port);
if(!socket->waitForConnected(4000))
{
qDebug()<<"error "<<socket->errorString();
}
// socket->write(new char[4]{1,2,3,4});
// QByteArray buffer;
socket->flush();
std::cout<<"HostOk"<<std::endl;
m_quit=0;
while(m_quit == 0)
{
/* if(socket->state()==QTcpSocket::UnconnectedState)
{
qDebug()<<"deconnecte";
socket->close();
//delete socket;
socket->deleteLater();
sleep(2);
delete socket;
qDebug()<<"essai de reconnecter";
socket = new QTcpSocket();
socket->connectToHost(m_satName, m_port);
// socket->connectToHost("192.168.0.20", 1234);
if(!socket->waitForConnected(3000))
{
qDebug()<<"error "<<socket->errorString();
}
// socket->write(new char[4]{1,2,3,4});
//QByteArray buffer;
socket->flush();
std::cout<<"Host-Ok"<<std::endl;
}
*/
this->newTcpDataRead();
}
}
QImage Mat2QImage(cv::Mat const& src)
{
cv::Mat temp; //make the same cv::Mat
cvtColor(src, temp,cv::COLOR_BGR2RGB); //cvtColor Makes a copt, that what i need
QImage dest((const uchar *) temp.data, temp.cols, temp.rows, temp.step, QImage::Format_RGB888);
dest.bits(); //enforce deep copy, see documentation
return dest;
}
cv::Mat QImage2Mat(QImage const& src)
{
cv::Mat tmp(src.height(),src.width(),CV_8UC3,(uchar*)src.bits(),src.bytesPerLine());
cv::Mat result; //deep copy just in case (my lack of knowledge with open cv)
cvtColor(tmp, result,cv::COLOR_BGR2RGB);
return result;
}
qint64 StreamerThread::newTcpDataRead()
{
QDataStream in(socket);
in.setVersion(QDataStream::Qt_5_11);
// initialize data
QImage image;
QByteArray data;
static qint64 imageSize = 0;
QString currentSatName="";
QByteArray currentSatData;
socket->waitForReadyRead();
usleep(1000);
//le client envoie un QByteArrayavec la taille du jpeg en premier puis les datas
if ( 0 == imageSize ) {
if ( socket->bytesAvailable() < (int)sizeof(qint64) )
{
std::cout<<"error "<<std::endl;
return-1;
}
in >> imageSize;
in>>currentSatName;
in>>currentSatData;
// qInfo() << imageSize;
//std::cout<<currentSatName.toStdString()<<std::endl;
}
//le client envoie un QByteArrayavec les datas, une chaine jpeg a recoder avec cv::decode
if (socket->bytesAvailable() < imageSize ) {;}//return -2;
else
{
in>>data;
//Vers cv::Mat:
std::vector<uchar> vData(data.begin(), data.end());
cv::Mat matImg;
if(imageSize!=-1&&imageSize!=0&&data.size()>0)
{
matImg = cv::imdecode(cv::Mat(1,
imageSize,//vData.size(),
CV_8UC1,
&vData[0]), cv:: IMREAD_COLOR);
QImage image= Mat2QImage(matImg);
emit newImage(image);
}
else
{
qDebug()<<"matrice a -1";
qInfo()<<this<< " :erreur decodage tcp";
QImage image = QImage(640,480, QImage::Format_RGB888);
image.fill(QColor("red"));
socket->flush();
}
imageSize = 0;
//DATA OUT
QByteArray block;
QByteArray satData;
if(datout>=10)
{
QString satName="RATS";
satData.reserve(8);
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_11);
// initialize data
// serialize
out << qint64(0) <<satName<<satData;
out.device()->seek(0);
out << (qint64)(block.size() - sizeof(qint64)); // the size of the block to be sent
// send over TCP
qint64 written = socket->write(block);
socket->waitForBytesWritten();
datout=0;
}
datout++;
return image.sizeInBytes();
}
}
void StreamerThread::slotQuit()
{
m_quit = 1;
}
I really don't understand why it fail when i instanciate more than one client object
when it fail, it's loop here:
if ( 0 == imageSize ) {
if ( socket->bytesAvailable() < (int)sizeof(qint64) )
{
std::cout<<"error "<<std::endl;
return-1;
}
And it is the last instancied object that is running, the first created is keeping saying "error".
I really suppose it is related to socket, maybe instanciation is not done correctly.
i see that both are created with good TCP port.but, to validate my guess, i copied the
streamerthread.cpp
streamerthread.h
to a new file, a different object so:
streamerthread2.cpp
streamerthread2.h
so for the test i declare like this:
StreamerThread clientSat1(1238,"SAT1-SNTL");clientSat1.start();
StreamerThread2 clientSat2(1234,"SAT2-SNTL");clientSat2.start();
so i've one instance of 2 differents object....and it work here! stupid way of doing but it work....
I really don't want to do like this, i really want to us objects and their power.
please say me im stupid and my error is ->there<- .
many thanks all!
in Instantiation of my streamerSlot classe, i uses a static variable.
since you just have one ovbject instantiated, it seems to works.
BUT if you instantiate more than one object, the static declaration, is declared just one time...one time for every instantiations, this is why my instantiations was bad.
Hope this will help .
Regards
When I try to downloading file up to 50mb example, no problem, but with a big files give the following error
void MainWindow::downloadFile() {
QNetworkRequest requests;
requests.setUrl(QUrl("https://urlToFile"));
QSslConfiguration configSsl = QSslConfiguration::defaultConfiguration();
configSsl.setProtocol(QSsl::AnyProtocol);
requests.setSslConfiguration(configSsl);
QNetworkAccessManager *manager5 = new QNetworkAccessManager(this);
QNetworkReply *reply5;
reply5 = manager5->get( requests );
connect(manager5, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*)));
}
void MainWindow::downloadFinished(QNetworkReply *data) {
QFile localFile("fileName");
if (!localFile.open(QIODevice::WriteOnly))
return;
localFile.write(data->readAll());
localFile.close();
}
In your code, You are holding the whole file in memory until the download process finishes (that is when QNetworkAccessManager::finished() signal gets emitted). Of course, this is not the best way to deal with large files.
Remember that QNetworkReply is a QIODevice. This means that you should use the readyRead() signal to save data chunks to the disk as soon as they are received from the network, in order to avoid holding the whole file in memory until the download is finished.
Here is a minimal complete example:
#include <QtNetwork>
int main(int argc, char* argv[]){
QCoreApplication a(argc, argv);
QNetworkAccessManager nam;
QFile file("downloadedFile.xxx");
if(!file.open(QIODevice::ReadWrite)) return 1;
QNetworkRequest request(QUrl("http://download_url/..."));
QNetworkReply* reply = nam.get(request);
QObject::connect(reply, &QNetworkReply::readyRead, [&]{
//this will be called every time a chunk of data is received
QByteArray data= reply->readAll();
qDebug() << "received data of size: " << data.size();
file.write(data);
});
//use the finished signal from the reply object to close the file
//and delete the reply object
QObject::connect(reply, &QNetworkReply::finished, [&]{
qDebug() << "finished downloading";
QByteArray data= reply->readAll();
file.write(data);
file.close();
reply->deleteLater();
a.quit();
});
return a.exec();
}
Update:
I have wrapped the whole thing in a class FileDownloader, which can be used to download a file using QNetworkAccessManager, Here is an example of using this class:
#include <QtWidgets>
#include <QtNetwork>
//downloads one file at a time, using a supplied QNetworkAccessManager object
class FileDownloader : public QObject{
Q_OBJECT
public:
explicit FileDownloader(QNetworkAccessManager* nam, QObject* parent= nullptr)
:QObject(parent),nam(nam)
{
}
~FileDownloader(){
//destructor cancels the ongoing dowload (if any)
if(networkReply){
a_abortDownload();
}
}
//call this function to start downloading a file from url to fileName
void startDownload(QUrl url, QString fileName){
if(networkReply) return;
destinationFile.setFileName(fileName);
if(!destinationFile.open(QIODevice::WriteOnly)) return;
emit goingBusy();
QNetworkRequest request(url);
networkReply= nam->get(request);
connect(networkReply, &QIODevice::readyRead, this, &FileDownloader::readData);
connect(networkReply, &QNetworkReply::downloadProgress,
this, &FileDownloader::downloadProgress);
connect(networkReply, &QNetworkReply::finished,
this, &FileDownloader::finishDownload);
}
//call this function to abort the ongoing download (if any)
void abortDownload(){
if(!networkReply) return;
a_abortDownload();
emit backReady();
}
//connect to the following signals to get information about the ongoing download
Q_SIGNAL void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
Q_SIGNAL void downloadSuccessful();
Q_SIGNAL void downloadError(QString errorString);
//the next two signals are used to indicate transitions between busy and
//ready states of the file downloader, they can be used to update the GUI
Q_SIGNAL void goingBusy();
Q_SIGNAL void backReady();
private:
Q_SLOT void readData(){
QByteArray data= networkReply->readAll();
destinationFile.write(data);
}
Q_SLOT void finishDownload(){
if(networkReply->error() != QNetworkReply::NoError){
//failed download
a_abortDownload();
emit downloadError(networkReply->errorString());
} else {
//successful download
QByteArray data= networkReply->readAll();
destinationFile.write(data);
destinationFile.close();
networkReply->deleteLater();
emit downloadSuccessful();
}
emit backReady();
}
//private function, cleans things up when the download is aborted
//(due to an error or user interaction)
void a_abortDownload(){
networkReply->abort();
networkReply->deleteLater();
destinationFile.close();
destinationFile.remove();
}
QNetworkAccessManager* nam;
QUrl downloadUrl;
QFile destinationFile;
QPointer<QNetworkReply> networkReply;
};
//A sample GUI application that uses the above class
class Widget : public QWidget{
Q_OBJECT
public:
explicit Widget(QWidget* parent= nullptr):QWidget(parent){
layout.addWidget(&lineEditUrl, 0, 0);
layout.addWidget(&buttonDownload, 0, 1);
layout.addWidget(&progressBar, 1, 0);
layout.addWidget(&buttonAbort, 1, 1);
layout.addWidget(&labelStatus, 2, 0, 1, 2);
lineEditUrl.setPlaceholderText("URL to download");
connect(&fileDownloader, &FileDownloader::downloadSuccessful,
this, &Widget::downloadFinished);
connect(&fileDownloader, &FileDownloader::downloadError,
this, &Widget::error);
connect(&fileDownloader, &FileDownloader::downloadProgress,
this, &Widget::updateProgress);
connect(&buttonDownload, &QPushButton::clicked,
this, &Widget::startDownload);
connect(&buttonAbort, &QPushButton::clicked,
this, &Widget::abortDownload);
showReady();
//use goingBusy() and backReady() from FileDownloader signals to update the GUI
connect(&fileDownloader, &FileDownloader::goingBusy, this, &Widget::showBusy);
connect(&fileDownloader, &FileDownloader::backReady, this, &Widget::showReady);
}
~Widget() = default;
Q_SLOT void startDownload(){
if(lineEditUrl.text().isEmpty()){
QMessageBox::critical(this, "Error", "Enter file Url", QMessageBox::Ok);
return;
}
QString fileName =
QFileDialog::getSaveFileName(this, "Destination File");
if(fileName.isEmpty()) return;
QUrl url= lineEditUrl.text();
fileDownloader.startDownload(url, fileName);
}
Q_SLOT void abortDownload(){
fileDownloader.abortDownload();
}
Q_SLOT void downloadFinished(){
labelStatus.setText("Download finished successfully");
}
Q_SLOT void error(QString errorString){
labelStatus.setText(errorString);
}
Q_SLOT void updateProgress(qint64 bytesReceived, qint64 bytesTotal){
progressBar.setRange(0, bytesTotal);
progressBar.setValue(bytesReceived);
}
private:
Q_SLOT void showBusy(){
buttonDownload.setEnabled(false);
lineEditUrl.setEnabled(false);
buttonAbort.setEnabled(true);
labelStatus.setText("Downloading. . .");
}
Q_SLOT void showReady(){
buttonDownload.setEnabled(true);
lineEditUrl.setEnabled(true);
buttonAbort.setEnabled(false);
progressBar.setRange(0,1);
progressBar.setValue(0);
}
QGridLayout layout{this};
QLineEdit lineEditUrl;
QPushButton buttonDownload{"Start Download"};
QProgressBar progressBar;
QPushButton buttonAbort{"Abort Download"};
QLabel labelStatus{"Idle"};
QNetworkAccessManager nam;
FileDownloader fileDownloader{&nam};
};
int main(int argc, char* argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "main.moc"
I'm trying to do a simple GET request (with modified User-Agent), return response to QML and do a JSON parsing.
Actually it only returns page content when loading is complete but it doesn't return it to QML.
Sorry for the noob question. I'm new to this language and I'm trying to learn it :)
Here's my code:
Home.qml
function getRequest() {
[...]
console.log('Request...')
var jsonResult = JSON.parse(connectNet.connectUrl("http://myURL.com/index.php").toString())
lbOutput.text = jsonResult.predictions[0].description.toString()
}
}
connectnet.cpp
#include "connectnet.h"
#include "stdio.h"
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
connectNet::connectNet(QObject *parent) : QObject(parent)
{
}
void connectNet::connectUrl(QString url)
{
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest request;
QNetworkReply *reply = NULL;
request.setUrl(QUrl(url));
request.setRawHeader( "User-Agent" , "FAKE USER AGENT HERE" );
reply = manager->get(request);
connect(manager, SIGNAL(finished(QNetworkReply*)), this,
SLOT(replyFinished(QNetworkReply*)));
}
QString connectNet::replyFinished(QNetworkReply *reply)
{
return reply->readAll();
}
appname.cpp
#ifdef QT_QML_DEBUG
#include <QtQuick>
#endif
#include <sailfishapp.h>
#include "connectnet.h"
int main(int argc, char *argv[])
{
//INIT SETTINGS
QGuiApplication *app = SailfishApp::application(argc, argv);
QQuickView *view = SailfishApp::createView();
connectNet ConnectNet;
view->rootContext()->setContextProperty("connectNet", &ConnectNet);
view->setSource(SailfishApp::pathTo("qml/APPNAME.qml"));
view->showFullScreen();
app->exec();
}
Hope I've well explained what I'm looking for. Thanks for your help.
====================================================
EDIT 20/08/2015: added updated connectnet.h
#ifndef CONNECTNET_H
#define CONNECTNET_H
#include <QObject>
#include <QNetworkReply>
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QUrl>
class ConnectNet : public QObject
{
Q_OBJECT
QNetworkAccessManager m_manager;
public:
ConnectNet(QObject * parent = 0) : QObject(parent) {
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
}
signals:
void replyAvailable(const QString & reply);
public slots:
void sendRequest(const QString url) {
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setRawHeader("User-Agent", "MyLittleAgent");
m_manager.get(request);
}
};
#endif // CONNECTNET_H
this part of code gives a lot of errors :( (screenshot below)
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
compiling erros: http://i.stack.imgur.com/30vWn.jpg
Your problem is that you think synchronously. The connectUrl cannot return a value (and it doesn't), since when it runs the result is not available. What you must do, instead, is for the ConnectNet class to emit a signal when the data is available.
It'd be a horrible idea if you tried to make a synchronous wrapper that did return the value: the QML engine would be stuck as long as it took for the result to be received. You could freeze your application by pulling the network cable at the right moment, or if the server was down. Users hate that, and it's a horrible antipattern that must be expediently eliminated and discouraged.
Here's how your ConnectNet (please, not connectNet, lowercase names are for members!) class could look. Note that the QNetworkAccessManager instance doesn't need to be a pointer.
class ConnectNet : public QObject {
Q_OBJECT
QNetworkAccessManager m_manager;
public:
ConnectNet(QObject * parent = 0) : QObject(parent) {
connect(&m_manager, &QNetworkAccessManager::finished,
[this](QNetworkReply * reply) {
if (reply->error() == QNetworkReply::NoError)
emit replyAvailable(QString::fromUtf8(reply->readAll()));
});
}
Q_SLOT void sendRequest(const QString & url) {
auto request = QNetworkRequest(QUrl(url));
request.setRawHeader("User-Agent", "MyLittleAgent");
m_manager.get(request);
}
Q_SIGNAL void replyAvailable(const QString & reply);
};
Since connectNet instance instance is exposed as a property in the global QML context, you can connect to its signals as follows:
function getRequest() {
connectNet.sendRequest("http://myURL.com/index.php")
}
function resultHandler(result) {
var jsonResult = JSON.parse(result.toString())
lbOutput.text = jsonResult.predictions[0].description.toString()
}
Rectangle { // or any other item
Component.onCompleted: {
connectNet.replyAvailable.connect(resultHandler)
}
...
}
I'm trying to create a test that will use a QNetworkAccessManager to talk with our RESTful api. All I want to do is grab a simple JSon object using a QNetworkAccessManager. My code looks like this:
Connection::Connection(QString const &username, QString const &password, QString const &url, QString const &api) :
_user(username), _pass(password), _url(url) {
_manager = new QNetworkAccessManager(this);
QObject::connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(receiveReply(QNetworkReply*)));
QNetworkRequest request;
request.setUrl(QUrl(_url.append(api)));
request.setRawHeader("Authorization", QString("Basic ").append(QString("%1:%2").arg(_user).arg(_pass)).toUtf8());
request.setHeader(QNetworkRequest::ContentTypeHeader, "text/json");
QNetworkReply *reply = _manager->get(request);
}
void Connection::Connection(QNetworkReply *reply) {
//do some validation of the reply...
_data = QJsonDocument::fromJson(reply->readAll());
}
QJsonDocument Connection::data() const {
return _data;
}
...
#include <gtest/gtest.h>
#include "Connection.h"
#include <QApplication>
TEST(ConnectionTest, creation) {
int argc = 0;
char *argv = 0;
QApplication a(argc, &argv);
Connection *connect = new Connection("user","abc123", https://mytest.com/", "api/");
connect->deleteLater();
while (connect->data().isEmpty()) {
//loop forever
}
}
Originally I was testing by having Connection also be a QWidget. The finished signal would only show if I called connection->show(); Then I have to close the widget manually and the test completes. However, this isn't really useful for automated unit testing.
If I use the above while loop to wait until _data has been set to something then nothing ever happens with the QNetworkAccessManager and it loops forever.
Is there some call that needs to happen before anything will move forward? Why was ->show() 'working?'
You're not spinning the event loop, so don't expect anything asynchronous to work at all.
You need to add a signal to the Connection class that indicates when it's done, and use that signal to quit the event loop:
TEST(ConnectionTest, creation) {
int argc = 0;
char *argv = 0;
QCoreApplication app(argc, &argv);
// No need to allocate the connection on the heap!
Connection connection("user", "abc123", "https://mytest.com/", "api/");
QObject::connect(&connection, &Connection::finished,
&app, &QCoreApplication::quit());
app.exec();
}
You could add the QApplication instance as a member of the ConnectionTest class.
There's also no need to double the number of heap allocations by having the QNetworkAccessManager * manager member. You can have it as a regular member:
class Connection : public QObject {
Q_OBJECT
QNetworkAccessManager manager;
Q_SLOT void receiveReply(QNetworkReply *) {
...
emit finished();
}
public:
explicit
Connection(const QString & user, const QString & password,
const QUrl & url, const QString & path,
QObject * parent = 0) : QObject(parent) { ... }
Q_SIGNAL void finished();
};
I have come across bug that I am not able to see myself. After studing QT and stack sites I wrote following code:
void RateOfExchangeGetter::run(){
mRunning = true;
mAccessManager = new QNetworkAccessManager();
//connect(mAccessManager, SIGNAL(finished(QNetworkReply*)),
// this, SLOT(replyFinished(QNetworkReply*)));
while(mRunning){
QNetworkReply *reply;
for(SiteParser *parser: mSiteParsers){
QNetworkRequest request(QUrl("https://www.google.pl/"));
request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");
qDebug() << "here";
reply = mAccessManager->get(request);
parser->setQNetworkReply(reply);
connect(reply, SIGNAL(readyRead()), parser, SLOT(slotReadyRead()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
parser, SLOT(slotError(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
parser, SLOT(slotSslErrors(QList<QSslError>)));
}
//for test only ->
this->sleep(10);
QByteArray array = reply->read(50);
qDebug() << array;
}
}
As good eye might have already noticed - this code is placed in class that inherits QThread.
For some reason (that I cannot find) I can't receive any data from get function (I know that it is asynchronous), no signals are transmitted, and also after waiting 10 second there are no data available in read. I had also tried to get data via finished signal from QNetworkAccessManager itself but also nothing appeared.
Thanks to anyone who might have a clue what is happening.
You don't have an event loop in your thread's run() method, so there's no chance of any networking code working.
You should put all of your code into a QObject, and move it to a generic thread. The default implementation of QThread::run() spins an event loop.
I also don't see much reason for there to be more than one parser. You can simply let the QNetworkReply accumulate the response in its internal buffer until the request is finished - you can then parse it all in one go, obviating the need for parser state. Use the finished slot instead of readyRead. The readyRead slot only makes sense for large requests.
Below is how it might be done. Note the arming mechanism to prevent races between the worker thread and the main thread. You're guaranteed that the finishedAllRequests signal will be emitted exactly once after a call to arm(). Without this mechanism, the worker thread might be able to process all requests before the connect has a chance to run, and no signal will reach the recipient, or you might get multiple signals as the requests are processed before the next one is added.
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QPointer>
#include <QSslError>
#include <QThread>
#include <QMetaMethod>
#include <QDebug>
class Parser : public QObject {
Q_OBJECT
bool m_armed;
QList<QNetworkReply*> m_replies;
QPointer<QNetworkAccessManager> m_manager;
QMetaMethod m_addRequestImpl, m_armImpl;
Q_SLOT void finished() {
QNetworkReply * reply = static_cast<QNetworkReply*>(sender());
Q_ASSERT(m_replies.contains(reply));
qDebug() << "reply" << reply << "is finished";
// ... use the data
m_replies.removeAll(reply);
if (m_armed && m_replies.isEmpty()) {
emit finishedAllRequests();
m_armed = false;
}
}
Q_SLOT void error(QNetworkReply::NetworkError) {
QNetworkReply * reply = static_cast<QNetworkReply*>(sender());
m_replies.removeAll(reply);
}
Q_SLOT void sslErrors(QList<QSslError>) {
QNetworkReply * reply = static_cast<QNetworkReply*>(sender());
m_replies.removeAll(reply);
}
Q_INVOKABLE void addRequestImpl(const QNetworkRequest & req) {
QNetworkReply * reply = m_manager->get(req);
connect(reply, SIGNAL(finished()), SLOT(finished()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
SLOT(error(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
SLOT(sslErrors(QList<QSslError>)));
m_replies << reply;
}
Q_INVOKABLE void armImpl() {
if (m_replies.isEmpty()) {
emit finishedAllRequests();
m_armed = false;
} else
m_armed = true;
}
static QMetaMethod method(const char * signature) {
return staticMetaObject.method(staticMetaObject.indexOfMethod(signature));
}
public:
// The API is fully thread-safe. The methods can be accessed from any thread.
explicit Parser(QNetworkAccessManager * nam, QObject * parent = 0) :
QObject(parent), m_armed(false), m_manager(nam),
m_addRequestImpl(method("addRequestImpl(QNetworkRequest)")),
m_armImpl(method("armImpl()"))
{}
void addRequest(const QNetworkRequest & req) {
m_addRequestImpl.invoke(this, Q_ARG(QNetworkRequest, req));
}
void arm() {
m_armImpl.invoke(this);
}
Q_SIGNAL void finishedAllRequests();
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread * thread = new QThread(&a);
thread->start();
QNetworkAccessManager mgr;
Parser parser(&mgr);
mgr.moveToThread(thread);
parser.moveToThread(thread);
for (int i = 0; i < 10; ++i) {
QNetworkRequest request(QUrl("https://www.google.pl/"));
request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");
parser.addRequest(request);
}
thread->connect(&parser, SIGNAL(finishedAllRequests()), SLOT(quit()));
a.connect(thread, SIGNAL(finished()), SLOT(quit()));
parser.arm();
int rc = a.exec();
thread->wait();
delete thread; // Otherwise mgr's destruction would fail
return rc;
}
#include "main.moc"
Output:
reply QNetworkReplyHttpImpl(0x1011619e0) is finished
reply QNetworkReplyHttpImpl(0x101102260) is finished
reply QNetworkReplyHttpImpl(0x101041670) is finished
reply QNetworkReplyHttpImpl(0x1011023e0) is finished
reply QNetworkReplyHttpImpl(0x10102fa00) is finished
reply QNetworkReplyHttpImpl(0x101040090) is finished
reply QNetworkReplyHttpImpl(0x101163110) is finished
reply QNetworkReplyHttpImpl(0x10103af10) is finished
reply QNetworkReplyHttpImpl(0x10103e6b0) is finished
reply QNetworkReplyHttpImpl(0x101104c80) is finished