From this webpage, I have code.
When I use "http://httpbin.org/get", everything is OK.
But when I use my own url, for example "http://my-json-server.typicode.com/typicode/demo/db", I'm getting an error:
Unable to retrieve request headers
Where is my fault?
void RequestHeaders::getRequest()
{
//const QUrl url("http://httpbin.org/get"); // OK
const QUrl url("http://my-json-server.typicode.com/typicode/demo/db"); // Not OK
QNetworkRequest request(url);
QNetworkReply* reply = m_networkAccessManager->get(request);
bool ok = connect(reply, SIGNAL(finished()), this, SLOT(onGetReply()));
Q_ASSERT(ok);
Q_UNUSED(ok);
}
void RequestHeaders::onGetReply()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QString response;
const QByteArray buffer(reply->readAll());
bb::data::JsonDataAccess ja;
const QVariant jsonva = ja.loadFromBuffer(buffer);
const QMap<QString, QVariant> jsonreply = jsonva.toMap();
QMap<QString, QVariant>::const_iterator it = jsonreply.find("headers");
if (it != jsonreply.end()) {
const QMap<QString, QVariant> headers = it.value().toMap();
for (QMap<QString, QVariant>::const_iterator hdrIter = headers.begin();
hdrIter != headers.end(); ++hdrIter) {
if (hdrIter.value().toString().trimmed().isEmpty())
continue;
response += QString::fromLatin1("%1: %2\r\n").arg(hdrIter.key(),
hdrIter.value().toString());
}
}
for (it = jsonreply.begin(); it != jsonreply.end(); it++) {
if (it.value().toString().trimmed().isEmpty())
continue;
response += QString::fromLatin1("%1: %2\r\n").arg(it.key(), it.value().toString());
}
reply->deleteLater();
if (response.trimmed().isEmpty()) {
response = tr("Unable to retrieve request headers");
}
emit complete(response);
}
Your onGetReply handler is parsing the server's HTTP response body as JSON, searching it for a "headers" child field, and if found then extracts that child's own child fields into a local response variable.
http://httpbin.org/get responds with a JSON object containing a "headers" child object that has child fields in it. So your response variable ends up not being empty.
http://my-json-server.typicode.com/typicode/demo/db responds with a JSON object that does not contain any "headers" child. So your response variable is left empty.
You need to either:
fix your server to respond with JSON that actually matches what your code is expecting.
fix your onGetReply() code to handle the JSON that your server is actually sending.
Related
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.
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.
My objective
To exchange refresh token for access token from google using OAuth 2.
My code
bool Google_Account::Refresh_Access_Token_Using_Refresh_Token()
{
// Prepare Url
QUrl url(tr("https://www.googleapis.com/oauth2/v4/token"));
// Create request
QNetworkRequest request(url);
request.setRawHeader("Host:","www.googleapis.com");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// Create request body ClientID, ClientSecret, RefreshTokenString are class data members
QString RequestBody = tr("client_secret=%1&").arg(ClientSecret) +
tr("grant_type=refresh_token&")+
tr("refresh_token=%1&").arg(RefreshTokenString)+
tr("client_id=%1").arg(ClientID);
QByteArray array = RequestBody.toUtf8();
// Get reply
QNetworkReply *reply = mQNAM.post(request, array); // mQNAM is QNetworkAccessManager
// Set timeout to reply while waiting for reply finished
bool stop = false;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, [&](){
qDebug()<<"Time out";
stop = true;
});
timer.start(5000);
// Wait till the response is completed
while(!reply->isFinished()){
QCoreApplication::processEvents();
if(stop){
qDebug()<<"Going to abort";
reply->abort();
}
}
// Check for reply
if(reply->isFinished()){
if(reply->error() != QNetworkReply::NoError){
qDebug()<<reply->readAll();
emit setMessage("Error: "+reply->errorString());
delete reply;
return false;
}
else{
QByteArray array = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(array);
QJsonObject obj = document.object();
access_token = obj.value("access_token").toString(); //access_token is class data variable
delete reply;
return true;
}
}
else{
delete reply;
return false;
}
}
The problem is that if I run this code in my windows 7 pc(Qt 5.11.1) everything is fine I get the access token but if i run in my raspberry pi(raspbian Qt 5.7) I get Error 400, Bad request from google. I tried using the access_token got from my windows pc and made other request such as to get the file list from google drive, they are working fine in raspberry, but only this I am having problem. What am I doing wrong?
P.S the code is refactored to the specific details only, in reality I am getting the client id and other keys from QSettings
This smells like you're getting a 400 code when Qt is requesting a token, because the login code you get when the flow returns to your app is URL-encoded. We wrote about How To Authenticate with Google SSO in Qt with some code samples including this bit that injects a parameter modifier in the flow:
google->setModifyParametersFunction([](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
// Percent-decode the "code" parameter so Google can match it
if (stage == QAbstractOAuth::Stage::RequestingAccessToken) {
QByteArray code = parameters->value("code").toByteArray();
(*parameters)["code"] = QUrl::fromPercentEncoding(code);
}
});
Do that before the initial request, but as suggested by #Giancarlo above, I'd rewrite your code to work asynchronously. It's simpler, and more reliable.
I try to implement a REST client in order to get a Service Ticket from my server. For those of you who don't know CAS: A Service Ticket can be requested by showing a TGT. The TGT can be requested by a successful login basically. Maybe that is not even relevant.
I quess I have a error in my connect. My server is not even reacting to that connection and the reply is emptry. However for some reason reply->error() == QNetworkReply::NoErroris true.
What am I doing wrong?
bool Client::validateTGT(QString const & tgt) const
{
bool isValid = false;
QUrl url = QUrl("https://localhost:8943/cas/v1/tickets/" + tgt);
QUrl postData;
postData.addQueryItem("service", "https://test.de");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
//QNetworkAccessManager *networkManager = new QNetworkAccessManager();
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)), Qt::AutoConnection);
QNetworkReply *reply = manager->post(request, postData.encodedQuery());
QByteArray replyData = reply->readAll();
QString s_data = QString::fromAscii(replyData.data());
if (reply->error() == QNetworkReply::NoError)
{
isValid = true;
}
return isValid;
}
EDIT: replyFinished as requested
.h:
public slots:
void replyFinished(QNetworkReply *);
.cpp:
void CCASRESTClient::replyFinished(QNetworkReply *reply)
{
QByteArray replyData = reply->readAll();
serviceTicket = QString::fromAscii(replyData.data());
}
The slot replyFinished is called by the event loop when the reply has arrived from the server, this happens far after your function validateTGT has returned.
The manager object receives the reply and then emits the finished signal, this is when the slot replyFinished is called. It doesn't make sense to return a value from there. Just ask yourself, who is the caller function that will get this return value? validateTGT has already returned, thus it is not getting anything.
Your slot needs to be declared to return void, and you should do whatever you want to do with the reply in your replyFinished slot.
In general, if you have any slot that returns a value, the return value cannot be retrieved unless this slot was called as a normal function (Obviously this is not the case here).
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);
}