I'm working on a module for a project where HTTP GET requests are used to retrieve some XML data, which is then converted to another format and send to a sub-system.
The code I have written so far is below:
CMakeLists.txt:
project(HttpDemo)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_BUILD_TYPE Debug)
#find_package(Qt5Widgets)
find_package(Qt5Core)
find_package(Qt5Network)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
qt5_use_modules(${PROJECT_NAME} Core Network) #Gui Widgets
main.cpp
#include <QtCore>
#include <QtNetwork>
class HttpHandler : public QObject
{
Q_OBJECT
public:
HttpHandler(QObject* parent=Q_NULLPTR) : QObject(parent)
{
QObject::connect(&nm, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
qDebug() << QSslSocket::sslLibraryBuildVersionString();
}
private:
QNetworkAccessManager nm;
public slots:
void post(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
QSslConfiguration sslConf;
sslConf.setProtocol(QSsl::SslV3);
request.setSslConfiguration(sslConf);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencded");
QUrlQuery query;
query.addQueryItem("client_id", "1234");
query.addQueryItem("code", "abcd");
QUrl params;
params.setQuery(query);
nm.post(request, params.toEncoded());
}
void get(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
nm.get(request);
}
void replyFinished(QNetworkReply* reply)
{
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if(statusCode.isValid())
{
// Print or catch the status code
QString status = statusCode.toString(); // or status_code.toInt();
qDebug() << status;
qDebug() << QString::fromStdString(reply->readAll().toStdString());
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpHandler hh;
hh.get("SOME_URL");
return a.exec();
}
#include "main.moc"
With SOME_URL I have tried a lot of links all of which work without any issues in let's say the Http Requester addon for Firefox. I get:
"OpenSSL 1.0.1j 15 Oct 2014"
qt.network.ssl: QSslSocket: cannot resolve SSLv2_client_method
qt.network.ssl: QSslSocket: cannot resolve SSLv2_server_method
According to the authority called the Internet this shouldn't be a problem. One thing's for certain - my replyFinished(QNetworkReply*) slot doesn't get triggered although it is connected to the finished() signal of the QNetworkAccessManager. This means that whatever the reason the signal is not emitted. Changing the QSslConfiguration to a different QSsl::SslProtocol doesn't make a difference in the outcome.
UPDATE (as requested in the comments):
Following code uses readyRead() and dynamically allocated reply. The result - same as above.
#include <QtCore>
#include <QtNetwork>
class HttpHandler : public QObject
{
Q_OBJECT
public:
HttpHandler(QObject* parent=Q_NULLPTR) : QObject(parent)
{
qDebug() << QSslSocket::sslLibraryBuildVersionString();
this->manager = new QNetworkAccessManager(this);
this->reply = Q_NULLPTR;
}
private:
QNetworkAccessManager* manager;
QNetworkReply* reply;
signals:
void finished();
public slots:
void post(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
QSslConfiguration sslConf;
sslConf.setProtocol(QSsl::SslV2);
request.setSslConfiguration(sslConf);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrlQuery query;
query.addQueryItem("client_id", "1234");
query.addQueryItem("code", "abcd");
QUrl params;
params.setQuery(query);
manager->post(request, params.toEncoded());
}
void get(QString urlLink)
{
QUrl url(urlLink);
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
this->reply = manager->get(request);
QObject::connect(reply, SIGNAL(readyRead()), this, SLOT(slotReadyRead()));
}
void slotReadyRead()
{
qDebug() << "Hello"; // I never land here
}
void replyFinished(QNetworkReply* reply)
{
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if(statusCode.isValid())
{
QString status = statusCode.toString(); // or status_code.toInt();
qDebug() << status;
qDebug() << QString::fromStdString(reply->readAll().toStdString());
}
emit finished();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpHandler *hh = new HttpHandler(&a);
QObject::connect(hh, SIGNAL(finished()), &a, SLOT(quit()));
hh->get("http://httpbin.org/ip"); // or any other httpbin.org endpoint
return a.exec();
}
#include "main.moc"
UPDATE 2:
I just found an example in the Qt documentation. Downloaded, compiled and ran the thing - same error but it works.
Issue resolved (see here). Basically the problem was the company's proxy. A colleague of mine gave it a shot and replaced the HTTP with HTTPS (even though the link is HTTP) and it worked all of a sudden. Then it struck us - the company's proxy caches HTTP (and does other things too), which leads to huge delays and if the timeout tolerance is small enough QNetworkAccessManager will return a socket timeout.
Using QNetworkProxyFactory::setUseSystemConfiguration(true) enables the proxy in a way that doesn't make your application dependent on a configuration in your code but rather the configuration of the system.
Related
I'm a newbie in terms both on Qt and C++ but i was trying to play a bit with a spotify web api for uni project. Unfortunately I got stuck on posting my refresh token to api to get new access token. Every time i do that, i'm getting response {"error":"invalid_client"}. I've made sure both my client id and client secret are correct. I think there is an mistake somewhere in my header but i've tried few things and it didn't do much.
Here's my code:
void MainWindow::on_pushButton_2_clicked(){
QNetworkAccessManager * manager = new QNetworkAccessManager(this);
QUrl url("https://accounts.spotify.com/api/token");
QNetworkRequest request(url);
QString header = "my_client_id:my_client_secret";
QByteArray ba;
ba.append(header);
ui->teOutput->appendPlainText(ba.toBase64());
QString full_header= "Authorization: Basic " + ba.toBase64();
//ui->teOutput->appendPlainText(full_header);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::ContentDispositionHeader, full_header);
QUrlQuery params;
params.addQueryItem("grant_type", "refresh_token");
params.addQueryItem("refresh_token", "here_is_my_refresh_token");
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(onFinish(QNetworkReply *)));
manager->post(request, params.query().toUtf8());
//ui->teOutput->appendPlainText(params.query().toUtf8());
}
void MainWindow::onFinish(QNetworkReply *rep)
{
QString data = rep->readAll();
ui->teOutput->appendPlainText(data);
}
And here's how my request should look like (as suggested i've checked that using cUrl)
curl -X "POST" -H "Authorization: Basic xxxxxxxxxxx" -d grant_type=refresh_token -d refresh_token=xxxxxxxxxxxxx https://accounts.spotify.com/api/token
The command -H "Authorization: Basic xxxxxxxxxxx" is equivalent to:
request.setRawHeader("Authorization", "Basic xxxxxxxxxxx");
So you should use instead of request.setHeader(QNetworkRequest::ContentDispositionHeader, full_header);.
Although it is not necessary to implement all the logic since the Spotify API uses OAuth2 that Qt implements in the Qt Network Authorization module.
Only the QNetworkRequest header should be modified when requesting or refreshing the token since that is not part of the Oauth2 standard.
constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
#include <QByteArray>
#include <QUrl>
namespace Constants {
const QByteArray SPOTIFY_CLIENT_ID = "xxxxxxxx";
const QByteArray SPOTIFY_CLIENT_SECRET = "yyyyyyyy";
const QUrl SPOTIFY_AUTHORIZATION_URL = QUrl("https://accounts.spotify.com/authorize");
const QUrl SPOTIFY_ACCESSTOKEN_URL = QUrl("https://accounts.spotify.com/api/token");
}
#endif // CONSTANTS_H
networkaccessmanager.h
#ifndef NETWORKACCESSMANAGER_H
#define NETWORKACCESSMANAGER_H
#include <QNetworkAccessManager>
class NetworkAccessManager : public QNetworkAccessManager
{
public:
using QNetworkAccessManager::QNetworkAccessManager;
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
};
#endif // NETWORKACCESSMANAGER_H
networkaccessmanager.cpp
#include "networkaccessmanager.h"
#include "constants.h"
#include <QtGlobal>
QNetworkReply *NetworkAccessManager::createRequest(QNetworkAccessManager::Operation op,
const QNetworkRequest &request,
QIODevice *outgoingData)
{
QNetworkRequest r(request);
if(r.url() == Constants::SPOTIFY_ACCESSTOKEN_URL)
r.setRawHeader("Authorization",
"Basic " +
QByteArray(Constants::SPOTIFY_CLIENT_ID + ":" + Constants::SPOTIFY_CLIENT_SECRET).toBase64());
return QNetworkAccessManager::createRequest(op, r, outgoingData);
}
spotifywrapper.h
#ifndef SPOTIFYWRAPPER_H
#define SPOTIFYWRAPPER_H
#include <QOAuth2AuthorizationCodeFlow>
#include <QObject>
class SpotifyWrapper : public QObject
{
Q_OBJECT
public:
explicit SpotifyWrapper(QObject *parent = nullptr);
QNetworkReply *me();
public Q_SLOTS:
void grant();
Q_SIGNALS:
void authenticated();
private:
QOAuth2AuthorizationCodeFlow oauth2;
};
#endif // SPOTIFYWRAPPER_H
spotifywrapper.cpp
#include "spotifywrapper.h"
#include "networkaccessmanager.h"
#include "constants.h"
#include <QDesktopServices>
#include <QOAuthHttpServerReplyHandler>
#include <QUrlQuery>
SpotifyWrapper::SpotifyWrapper(QObject *parent) : QObject(parent)
{
QOAuthHttpServerReplyHandler * replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
replyHandler->setCallbackPath("callback");
oauth2.setNetworkAccessManager(new NetworkAccessManager(this));
oauth2.setReplyHandler(replyHandler);
oauth2.setAuthorizationUrl(Constants::SPOTIFY_AUTHORIZATION_URL);
oauth2.setAccessTokenUrl(Constants::SPOTIFY_ACCESSTOKEN_URL);
oauth2.setClientIdentifier(Constants::SPOTIFY_CLIENT_ID);
oauth2.setScope("user-read-private user-read-email");
oauth2.setState("34fFs29kd09");
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser,
&QDesktopServices::openUrl);
connect(&oauth2, &QOAuth2AuthorizationCodeFlow::statusChanged, [=](
QAbstractOAuth::Status status) {
if (status == QAbstractOAuth::Status::Granted){
emit authenticated();
}
});
}
void SpotifyWrapper::grant()
{
oauth2.grant();
}
QNetworkReply *SpotifyWrapper::me()
{
return oauth2.get(QUrl("https://api.spotify.com/v1/me"));
}
main.cpp
#include "spotifywrapper.h"
#include <QNetworkReply>
#include <QGuiApplication>
#include <QTimer>
int main(int argc, char *argv[])
{
QGuiApplication a(argc, argv);
SpotifyWrapper wrapper;
wrapper.grant();
QObject::connect(&wrapper, &SpotifyWrapper::authenticated, [&wrapper](){
qDebug() << "authenticated";
QNetworkReply *reply = wrapper.me();
QObject::connect(reply, &QNetworkReply::finished, [=]() {
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qDebug() << reply->errorString();
return;
}
qDebug() << reply->readAll();
QTimer::singleShot(1000, &QCoreApplication::quit);
});
});
return a.exec();
}
You must also set as Redirect URIs in the SETTINGS of your application to "http://127.0.0.1:1337/callback":
The full example can be found here
I am doing a simple HTTP get request to www.google.co.in but I am getting empty string in response.
Here is my code:
void MainWindow::on_pushButton_clicked()
{
QNetworkAccessManager * mgr = new QNetworkAccessManager(this);
connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(onfinish(QNetworkReply*)));
QUrl url("www.google.co.in");
url.setScheme("http");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
mgr->get(QNetworkRequest(QUrl("www.google.co.in")));
}
void MainWindow::onfinish(QNetworkReply *rep)
{
QByteArray bts = rep->readAll();
QString str(bts);
qDebug() << str;
}
Output is :""
Facing same issue when doing post request to my own server.
In pro file I have done QT += core gui network
You have to add the correct URL, in addition it is recommended that you use the new connection between signals and slots, and finally you must remove the QNetworkReply:
void MainWindow::on_pushButton_clicked()
{
QUrl url("https://www.google.co.in/");
QNetworkAccessManager *mgr = new QNetworkAccessManager(this);
connect(mgr,&QNetworkAccessManager::finished,this,&MainWindow::onfinish);
mgr->get(QNetworkRequest(url));
}
void MainWindow::onfinish(QNetworkReply *rep)
{
QByteArray bts = rep->readAll();
QString str(bts);
qDebug() << str;
rep->deleteLater();
}
I'm writing an embedded RESTful API client using the Qt5 Embedded layer of OpenEmbedded project. I want my client to be able to send a simple async request to notify my server on what's going on and an other scenario is that my client needs to synchronize data from the server.
To do so I wrote two functions, one to send the request and another to get the response. So if I don't care about the response, I only use the first one.
To be able to use the response with the second one, I use a QNetworkReply * as a member of my class. To keep the QNetworkReply alive, I also set the QNetworkAccessManager as a member of my class.
#include <QtNetwork>
class ApiClient : public QObject
{
Q_OBJECT
public:
ApiClient(QObject *parent = 0);
private:
QString host;
QString key;
quint32 replyTimeout;
QNetworkAccessManager manager;
QNetworkReply *reply;
void sendRequest(const QString &method, const QVariantMap ¶ms = QVariantMap());
QVariantMap getResponse() const;
};
The apiclient.cpp file:
#include "apiclient.h"
ApiClient::ApiClient(QObject *parent) : QObject(parent)
{
}
void ApiClient::sendRequest(const QString &method, const QVariantMap ¶ms)
{
QUrl url(QString("%1/%2/").arg(host).arg(method));
QUrlQuery query;
query.addQueryItem("key", key);
if (!params.empty())
{
QMapIterator<QString, QVariant> it(params);
while (it.hasNext())
{
it.next();
query.addQueryItem(it.key(), it.value().toString());
}
}
url.setQuery(query);
qDebug() << "url: " << url.toString();
reply = manager.get(QNetworkRequest(url));
}
QVariantMap ApiClient::getResponse() const
{
if (!reply)
{
qFatal("No request sent!");
}
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
timer.start(replyTimeout);
loop.exec();
if (timer.isActive())
{
timer.stop();
if (reply->error())
{
qFatal("Wrong reply!");
}
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code != 200)
{
qFatal("Invalid server response!");
}
QJsonDocument result = QJsonDocument::fromJson(reply->readAll());
if (result.isNull())
{
qFatal("Invalid JSON!");
}
reply->deleteLater();
return result.object().toVariantMap();
}
disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
reply->abort();
reply->deleteLater();
return QVariantMap();
}
Is that a good way to proceed? How should I manage the QNetworkReply pointer when other signals are emitted (i.e. error(), sslErrors(), ...)?
QNetworkReply will always emit finished(), even when an error occured.
deleteLater() could even be called in a slot connected to that signal, so that part should be fine.
But I would recommend to look into a more asynchronous approach of handling the request, nested event loops like the on in your getResponse() can lead to "interesting" behavior, because you can basically get into re-entrancy situations in a single threaded program.
I want to create a application, by this i can download the file from my ftp server
and show the progress in progressbar. Ive wrote some code, but if im clicking on the button to download the file from ftp server, my application is crashing. Ive become some qDebug answers like:
"no errors request",
"updateDataTransferProgress started"
the file which must be downloaded was created in a folder, but the file is empty.:( What can u do to fix my problem?
Many thanks!
#include "f1.h"
#include "ui_f1.h"
#include "ui_form2.h"
#include "form2.h"
#include <QNetworkAccessManager>
#include <QFile>
#include <QFtp>
#include <QtNetwork>
#include <QMessageBox>
f1::f1(QWidget *parent) :
QFrame(parent),
ui(new Ui::f1)
{
ui->setupUi(this);
// ui->progressBar->setValue(0);
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(onDownServ()));
}
void f1::closeEvent(QCloseEvent *event)
{
F2->helloWorld();
}
f1::~f1()
{
delete ui;
}
void f1::onDownServ()
{
QNetworkAccessManager *nam = new QNetworkAccessManager();
QUrl url2("ftp://test.cz/plugins.txt");
url2.setPassword("test");
url2.setUserName("test");
reply = nam->get(QNetworkRequest(url2));
connect(reply, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(reply, SIGNAL(downloadProgress(qint64, qint64)),this, SLOT(updateDataTransferProgress(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(requestError(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
}
void f1::requestFinished()
{
qDebug() << "finished !";
save->flush();
save->close();
}
void f1::requestError(QNetworkReply::NetworkError)
{
qDebug() << "no errors, request";
}
void f1::readyRead()
{
qDebug() << "ready read!";
save=new QFile("plugins.txt");
if (!save->open(QIODevice::WriteOnly))
return;
save->write(reply->readAll());
}
void f1::updateDataTransferProgress(qint64 done, qint64 total)
{
qDebug() << "updateDataTransferProgress started";
ui->progressBar->setMaximum(100);
ui->progressBar->setValue(done*100/total);
}
QNetworkReply is a sequential-access QIODevice in which whenever more data is received from the network, the readyRead() signal is emitted. So your readyRead() slot will probably get called multiple times as new data comes gradually. So you should not initialize your file in that slot. The file initialization should be done in onDownServ() slot once:
QNetworkAccessManager *nam = new QNetworkAccessManager();
QUrl url2("ftp://test.cz/plugins.txt");
url2.setPassword("test");
url2.setUserName("test");
save=new QFile("plugins.txt");
if (!save->open(QIODevice::WriteOnly))
return;
reply = nam->get(QNetworkRequest(url2));
When you do file initialization in readyRead() slot, it opens file in the first call and subsequent calls are returned as it can not open the new file for write operation. So the readyRead() slot gets called repeatedly and the application crashes.
I try to execute post method in c++ to a url https://..., but I receive connection closed error.
I see that my code works if I use another url like https://www.google.gr.
If I remove the port 8181 I get error: server replied:not found.
My code is
static const char *REQUEST_URL="https://...";
static const char *USER = "....";
static const char *PASSWORD = "....";
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
finishedexecuted=0;
QByteArray postData;
postData.append("username=...");
postData.append("password= ...");
m_network = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl url=QUrl(REQUEST_URL);
request.setUrl(url);
QSslConfiguration config = request.sslConfiguration();
QList<QSslCertificate> certs =
QSslCertificate::fromPath("pistopoiitiko.crt");
config.setCaCertificates(certs);
request.setSslConfiguration(config);
QNetworkReply *reply = m_network->post(request,postData);
downloadTime.start();
QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
SLOT(slotSetProgress(qint64,qint64)));
QObject::connect(m_network, SIGNAL(finished(QNetworkReply *)),
SLOT(slotRequestFinished(QNetworkReply *)));
connect(m_network,
SIGNAL(sslErrors(QNetworkReply *, const QList<QSslError> &)),
this,
SLOT(sslError(QNetworkReply*, const QList<QSslError> &)));
}
void MainWindow::sslError(QNetworkReply* reply,
const QList<QSslError> &errors )
{...}
void MainWindow::slotRequestFinished(QNetworkReply *reply)
{
...
if (reply->error() > 0) {
m_label->setText("Error number = " + reply->errorString());
}
...
}
void MainWindow::slotSetProgress(qint64 received, qint64 total)
{...}
Any ideas?
i achieved to overcome this problem with this code
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setProtocol(QSsl::SslV3);
then i received ssl errors that i passed with ignoresslerrors()