Welcome!

Welcome to the official BlackBerry Support Community Forums.

This is your resource to discuss support topics with your peers, and learn from each other.

inside custom component

Native Development

Reply
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

How can i send http request sequentially with QNetworkAccessManager?

I am using QNetworkAccessManager to interact with my REST web service, one of the task invole making multiple post requests one after another. I want to make sure successive request should only be made if the previous one is ok (by inspecting in the response body which is a json object). My idea is to make 1 QNetworkAccessManager, hook the finished() signal to the first request handler who will check if the response is ok then make the 2nd request, connect the finished signal to the 2nd request handler ... like a chain of requests and handlers. However, seem like the 2nd response never come back. Here is the code

void InvokedApp::playOnServer(const QString &url){
	QNetworkRequest request;
	request.setUrl(QUrl(server->json_url()));
	//request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");
	request.setRawHeader("Content-Type", "application/json");

	// Connect your custom slot to its finished signal
	// Check the return value for connection errors
	bool res;
	Q_UNUSED(res);

	res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
	              this, SLOT(onClearListFinished()));
	Q_ASSERT(res);

	qDebug() << "clearing list";
	netReply = netManager->post(request, clearList);

	connect(netReply, SIGNAL(error(QNetworkReply::NetworkError)),
			this, SLOT(slotError(QNetworkReply::NetworkError)));
	connect(netReply, SIGNAL(sslErrors(QList<QSslError>)),
			this, SLOT(slotSslErrors(QList<QSslError>)));
}

void InvokedApp::onClearListFinished(){
	// Check for errors and verify that data is available
	if (netReply != NULL &&
		netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		qDebug() << netReply->readAll();
		qDebug() << "add item";

		QNetworkRequest request;
		request.setUrl(QUrl(server->json_url()));
		request.setRawHeader("Content-Type", "application/json");
		bool res;
		Q_UNUSED(res);
		res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
				this, SLOT(onAddSongFinished()));
		Q_ASSERT(res);
		netReply = netManager->post(request, addSong);
	}
	else
	{
		qDebug() << "In onClearListFinished error";
		qDebug() << netReply->readAll();
		noError = false;
	}
	netReply->deleteLater();
}

void InvokedApp::onAddSongFinished(){
	if (netReply != NULL &&	netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		qDebug() << netReply->readAll();
		qDebug() << "start playing";

		QNetworkRequest request;
		request.setUrl(QUrl(server->json_url()));
		request.setRawHeader("Content-Type", "application/json");
		bool res;
		Q_UNUSED(res);
		res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
				this, SLOT(onOpenPlayerFinished()));
		Q_ASSERT(res);
		netReply = netManager->post(request, openPlayer);
	}
	else
	{
		qDebug() << "In onAddSongFinished error";
		qDebug() << netReply->readAll();
		noError = false;
	}
	netReply->deleteLater();
}

void InvokedApp::onOpenPlayerFinished(){
	if (netReply != NULL &&	netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		qDebug() << netReply->readAll();
	}
	else
	{
		qDebug() << "In onOpenPlayerFinished error";
		qDebug() << netReply->readAll();
		noError = false;
	}
	netReply->deleteLater();
}

 And here is the output in console:

"clearing list" 
"{"id":1,"jsonrpc":"2.0","result":"OK"}" 
"add item" 

 I guess it is due to netReply not being deleted from previous response but how can i force delete response rightaway ?

 

Please use plain text.
Developer
Zmey
Posts: 1,512
Registered: ‎12-18-2012
My Device: PlayBook, Z10, DAC

Re: How can i send http request sequentially with QNetworkAccessManager?

Hi,

 

The problem is in this part of code:

 

		netReply = netManager->post(request, addSong); // new reply created
	}
	else
	{
          ...
	}
	netReply->deleteLater(); // Oops, new reply no longer exists :-) This was supposed to delete the previous instance

 

Btw, I suggest using reply's finished() signal instead of QNetworkAccessManger's finished() signal.

 

Inside of signal handler the reply can be accessed using sender() function:

 

QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
...
reply->deleteLater();

This way member variable will not be needed at all.

 


Andrey Fidrya, @zmeyc on twitter
Please use plain text.
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

Re: How can i send http request sequentially with QNetworkAccessManager?

Thank you, I though when i called netReply->deleteLater(), the memory location pointed to by netReply will be free right away. Anyway, i played around with the code for a couple hour and i got it work but it looks ugly since i have to manually delete the reply after every request. Here is the new code, it would be great if you can give me some insignt how to improve it

void InvokedApp::playOnServer(const QString &url){
	QNetworkRequest request;
	request.setUrl(QUrl(server->json_url()));
	request.setRawHeader("Content-Type", "application/json");

	bool res;
	Q_UNUSED(res);
	res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
	              this, SLOT(onClearListFinished()));
	Q_ASSERT(res);

	netReply = netManager->post(request, clearList);    //first reply
}

void InvokedApp::onClearListFinished(){
	if (netReply != NULL &&	netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		QNetworkRequest request;
		request.setUrl(QUrl(server->json_url()));
		request.setRawHeader("Content-Type", "application/json");

		//remove previous handler from network, connect a new handler
		bool res;
		Q_UNUSED(res);
		res = disconnect(netManager, SIGNAL(finished(QNetworkReply*)),
			             this, SLOT(onClearListFinished()));
		Q_ASSERT(res);
		res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
				this, SLOT(onAddSongFinished()));
		Q_ASSERT(res);

		while (!netReply->isFinished())         //i am not sure if it is safe to manually delete now
			qDebug() << "waiting for reply from clearing list";
		delete netReply;     // delete the first reply
		netReply = netManager->post(request, addSong);    // 2nd reply
	}
	else
	{
		qDebug() << "In onClearListFinished error";
		qDebug() << netReply->readAll();
		noError = false;
		netReply->deleteLater();
	}
}

void InvokedApp::onAddSongFinished(){
	if (netReply != NULL &&	netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		QNetworkRequest request;
		request.setUrl(QUrl(server->json_url()));
		request.setRawHeader("Content-Type", "application/json");

		// disconnect previous handler, connect new handler to network
		bool res;
		Q_UNUSED(res);
		res = disconnect(netManager, SIGNAL(finished(QNetworkReply*)),
						this, SLOT(onAddSongFinished()));
		Q_ASSERT(res);
		res = connect(netManager, SIGNAL(finished(QNetworkReply*)),
				this, SLOT(onOpenPlayerFinished()));
		Q_ASSERT(res);

		while (!netReply->isFinished())
			qDebug() << "waiting for reply from adding song";
		delete netReply; // delete 2nd reply
		netReply = netManager->post(request, openPlayer); // 3rd reply
	}
	else
	{
		qDebug() << "In onAddSongFinished error";
		qDebug() << netReply->readAll();
		noError = false;
		netReply->deleteLater();
	}
}

void InvokedApp::onOpenPlayerFinished(){
	if (netReply != NULL &&	netReply->bytesAvailable() > 0 &&
		netReply->error() == QNetworkReply::NoError)
	{
		while (!netReply->isFinished())
			qDebug() << "waiting for reply from opening player";
		//delete netReply;        //THIS IS VERY ODD, THE SAME PRINCIPLE AS ABOVE BUT IF I USE THIS INSTEAD OF DELETE LATTER, I GOT SEGFAULT SOMETIMES
		netReply->deleteLater();
	}
	else
	{
		qDebug() << "In onOpenPlayerFinished error";
		qDebug() << netReply->readAll();
		noError = false;
		netReply->deleteLater();
	}
}

 

Please use plain text.
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

Re: How can i send http request sequentially with QNetworkAccessManager?

Can you also give an example how to use reply finish() signal? As I understand, the connect code stay the same, but the netReply

netReply = netManager->post(request, clearList);

 become

netManager->post(request, clearList);

 and then inside the slot i use 

QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());

 to get the reply and do the checking in there, is that all?

Please use plain text.
Developer
Zmey
Posts: 1,512
Registered: ‎12-18-2012
My Device: PlayBook, Z10, DAC

Re: How can i send http request sequentially with QNetworkAccessManager?

[ Edited ]

I though when i called netReply->deleteLater(), the memory location pointed to by netReply will be free right away.

 

That's correct, but the variable was overwritten with a new value before deleteLater() was called, so the old reply was never freed.

 

I've tried to fix the code, but I'm typing this in browser without checking, so it might require tweaks. I've also used reply's finished() signal in it.

 

Delete 'netReply' declaration in header file as it's no longer used.

 

void InvokedApp::playOnServer(const QString &url)
{
	QNetworkRequest request;
	request.setUrl(QUrl(server->json_url()));
	//request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");
	request.setRawHeader("Content-Type", "application/json");

	qDebug() << "clearing list";
	QNetworkReply *reply = netManager->post(request, clearList);

        bool res = QObject::connect(reply, SIGNAL(finished()),
          this, SLOT(onClearListFinished()));
        Q_ASSERT(res);

	QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
			this, SLOT(slotError(QNetworkReply::NetworkError)));
	QObject::connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
			this, SLOT(slotSslErrors(QList<QSslError>)));
}

void InvokedApp::onClearListFinished()
{
        QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
        
	// Check for errors and verify that data is available
	if (reply->bytesAvailable() > 0 &&
		reply->error() == QNetworkReply::NoError)
	{
		qDebug() << reply->readAll();
		qDebug() << "add item";

		QNetworkRequest request;
		request.setUrl(QUrl(server->json_url()));
		request.setRawHeader("Content-Type", "application/json");
QNetworkReply *nextReply = netManager->post(request, addSong); bool res = QObject::connect(nextReply, SIGNAL(finished()), this, SLOT(onAddSongFinished())); Q_ASSERT(res); } else { qDebug() << "In onClearListFinished error"; qDebug() << reply->readAll(); noError = false; } reply->deleteLater(); } void InvokedApp::onAddSongFinished() { QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); if (reply->bytesAvailable() > 0 && reply->error() == QNetworkReply::NoError) { qDebug() << reply->readAll(); qDebug() << "start playing"; QNetworkRequest request; request.setUrl(QUrl(server->json_url())); request.setRawHeader("Content-Type", "application/json");
QNetworkReply *nextReply = netManager->post(request, openPlayer); bool res = QObject::connect(nextReply, SIGNAL(finished()), this, SLOT(onOpenPlayerFinished())); Q_ASSERT(res); } else { qDebug() << "In onAddSongFinished error"; qDebug() << reply->readAll(); noError = false; } reply->deleteLater(); } void InvokedApp::onOpenPlayerFinished()
{ QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender()); if (reply->bytesAvailable() > 0 && reply->error() == QNetworkReply::NoError) { qDebug() << reply->readAll(); } else { qDebug() << "In onOpenPlayerFinished error"; qDebug() << reply->readAll(); noError = false; } reply->deleteLater(); }

 

 

 


Andrey Fidrya, @zmeyc on twitter
Please use plain text.
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

Re: How can i send http request sequentially with QNetworkAccessManager?

wow, that's much simpler than my second implementation. So you keep the same QNetworkAccessManager but store connect different handler to each reply. One thing i want to clarify, i just read somewhere that deleteLatter() actually wait until control get back to main app to free all reply object. If that is the case, then is it better to call delete directly on reply before making the next call to save some meory?

Another question is that since i am dealing with the same web sevice, most f the time the request url never change but only the request body is different (json), if keep the same QNetworkRequest object, just replacing the body and reuse it across all request, would it affect the reply somehow? I am asking this because there is a method in the reply to get the request object so i dont know how does it know which request is for which response

 

Please use plain text.
Developer
Zmey
Posts: 1,512
Registered: ‎12-18-2012
My Device: PlayBook, Z10, DAC

Re: How can i send http request sequentially with QNetworkAccessManager?

i just read somewhere that deleteLatter() actually wait until control get back to main app to free all reply object.

 

No, deleteLater() simply deletes the object on which it's called. It's a method of QObject class.

 It works like "delete varName;" but postpones the deletion until the next cycle of message loop.

 

 

QNetworkAccessManager's docs say that replies must be deleted in finished() signal's handler using deleteLater(), not delete. I guess it's because it needs to reference the reply again after the handler is finished.

 

If that is the case, then is it better to call delete directly on reply before making the next call to save some meory?

 

 

If you mean deleteLater(), then calling it before making the next call won't change anything, because the reply will still be deleted on the next cycle of message loop, not instantly.

 

QNetworkAccessReply doesn't take much memory, so I wouldn't worry about multiple instances existing at the same time. Just make sure all of them are properly deleted in their finished() handlers using deleteLater().

 

Another question is that since i am dealing with the same web sevice, most f the time the request url never change but only the request body is different (json), if keep the same QNetworkRequest object, just replacing the body and reuse it across all request, would it affect the reply somehow? I am asking this because there is a method in the reply to get the request object so i dont know how does it know which request is for which response

 

I'm not sure if QNetworkRequest can be reused. However, it's passed to post() by value (copied), so if it works, I think there should be no problems, it will just make a copy for each QNetworkReply.

 

 


Andrey Fidrya, @zmeyc on twitter
Please use plain text.
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

Re: How can i send http request sequentially with QNetworkAccessManager?

Thank you for the clarification. In the previous msg, what i mean by deleting reply directly before making the next request is to call :

 

delete netReply;

//make new request and get new reply
netReply = netManager->post(request, openAddSong);

 I guess the doc doesnt recomend this because the way they handle respond need the reply object alive so they can use it latter in the slot.

Please use plain text.
New Developer
lnthai2002
Posts: 20
Registered: ‎03-23-2014
My Device: z10

Re: How can i send http request sequentially with QNetworkAccessManager?

hi, i have another question regarding the control flow. When we send a request (get/post..) and connect the response (or QNetworkAccessManager) 's finished signal to a slot, does the control continue executing the current method and the slot is run on a seperate thread ? What happen if I need a value from the slot ? For example:

bool App::purchase{
	QNetworkRequest request;
	request.setUrl(QUrl(server->json_url()));
	request.setRawHeader("Content-Type", "application/json");

	QNetworkReply *response = netManager->post(request, transaction);

	bool res = QObject::connect(response, SIGNAL(finished()),
				    this, SLOT(onTransactionFinished()));

	Q_ASSERT(res);		//DOES CONTROL STOP HERE AND WAIT UNTIL THE SLOT RETURN?

	if (paid){		// IS GUARANTEE THAT paid HAS THE VALUE SET IN THE SLOT?
		shipItem();
	}
}

void App::onTransactionFinished(){
	//get response
	if(response is ok){
		paid = true; // SET INSTANCE VAR
	}
}

 

Please use plain text.
Developer
Zmey
Posts: 1,512
Registered: ‎12-18-2012
My Device: PlayBook, Z10, DAC

Re: How can i send http request sequentially with QNetworkAccessManager?

Hi,

post() method will not block, it will continue executing.

onTransationFinished will be called later asynchronously (on the same thread).

You can call shipItem() from there.

 

It's important to perform lengthy calculations in a separate thread to avoid blocking the message loop. Functions on the main thread should exit as soon as possible, otherwise everything will be blocked including network operations. When parsing large chunks of data, I usually spawn a new thread in finished() signal's handler.

 

So, in other words, networking is asynchronous but performed on the same thread.

In some apps which work with the network actively it even makes sense to move QNetworkAccessManager itself to a separate thread, but in most cases this is not needed.

 


Andrey Fidrya, @zmeyc on twitter
Please use plain text.