I am creating a small program that will pull information from a website in order to learn. Specifically, I am trying to pull the archived winning numbers from this lotto website.
https://www.ohiolottery.com/WinningNumbers/KenoDrawings/KenoDrawingsArchive.aspx?date=01%2f01%2f2010
Here is the code I currently have (with QT += network in the pro file):
Retriever.h
#ifndef RETRIEVER_H
#define RETRIEVER_H
#include <QObject>
#include <QtNetwork>
#include <QDebug>
class Retriever : public QObject
{
Q_OBJECT
public:
explicit Retriever(QObject *parent = 0);
void fetch();
public slots:
void replyFinished(QNetworkReply* reply);
private:
QNetworkAccessManager* manager;
};
#endif // RETRIEVER_H
Retriever.cpp
#include "retriever.h"
Retriever::Retriever(QObject *parent) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
}
void Retriever::fetch()
{
QString stackoverflow = "http://stackoverflow.com";
QString lotto = "https://www.ohiolottery.com/WinningNumbers/KenoDrawings/KenoDrawingsArchive.aspx?date=01%2f01%2f2010";
manager->get(QNetworkRequest(QUrl(lotto)));
}
void Retriever::replyFinished(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) qDebug() << "no error";
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << reply->url();
qDebug() << reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
qDebug() << statusCode;
if (reply->atEnd()) qDebug() << "already at end";
while (!reply->atEnd()) {
QByteArray data = reply->readLine();
QString::fromUtf8(data.data(), data.size());
QString str(data);
qDebug() << str;
}
}
When I run this with stackoverflow, it works fine. But when I run this with the lotto website, I get the following output from qDebug:
no error
QUrl("https://www.ohiolottery.com/WinningNumbers/KenoDrawings/KenoDrawingsArchive.aspx?date=01%2F01%2F2010")
QUrl("/mobile")
301
already at end
There seems to be a few issues. First, there is no error but it thinks its a mobile site? Then it thinks its already at the end of the content (I'm assuming that its because of 301 status code). I'm not familiar with retrieving information from websites, so I'm not sure how to deal with this error. I copied the url exactly from the website, so I don't know why its redirecting. How can I resolve this error? More specifically, how can I get the correct URL to give to the QNetworkAccessManager?
Status 301 tells you have to fetch data from another url which in your case is /mobile.
You need to set user-agent request header to a web browser, because that website thinks you are a mobile client.
QNetworkRequest request(QUrl(lotto));
request.setHeader(QNetworkRequest::UserAgentHeader, QVariant("Mozilla/5.0 "));
manager->get(request);
Related
I have the following code to get the response of the url using QNetworkAccessManager and using QNetworkReply for getting the response code. I am getting the onReplyfinished()slot properly while testing this in windows 8. I am not getting the onReplyfinished() while using the application in Windows 10.
NetManager.h :
class NetManager:public QNetworkAccessManager
{
Q_OBJECT
public:
NetManager(QObject* inParent = 0);
~NetManager();
public slots:
void onReplyfinished();
private:
QNetworkAccessManager *AManager;
QNetworkReply *NReply;
QString urlStr;
};
NetManager.cpp :
NetManager::NetManager( QObject* inParent ) : QNetworkAccessManager(
inParent )
{
AManager = new QNetworkAccessManager(this);
urlStr= "https://sampleurl.com/";
qDebug() << urlStr;
QUrl url(urlStr);
QNetworkRequest NetRequest((url));
NReply= AManager->get(NetRequest);
connect(NReply, SIGNAL(finished()), this, SLOT(onReplyfinished()));
}
void NetManager::onReplyfinished()
{
qDebug () << "in getting response";
}
Thanks in advance
do not forget to add
QT += network in .pro file
and also if you are receiving ssl problems do not forget to copy libcryptoand libsslinto your project directory
I'm testing my code for compatibility with HTTP 3xx status codes (redirects).
I'm interested in codes 301, 302, 303, 307 and 308.
All of those work fine with my code, except 308.
My client testcase is Qt/C++ based, and my testing server is python-based. I'll post the code of both.
client.cpp:
#include <QGuiApplication>
#include <QObject>
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QNetworkAccessManager webCtrl;
QObject::connect(&webCtrl, &QNetworkAccessManager::finished, [&](QNetworkReply* reply) {
if(reply->error() != QNetworkReply::NoError) {
qDebug() << "got error";
}
QByteArray data = reply->readAll();
qDebug() << "got" << data.length() << "bytes";
});
QNetworkRequest request(QUrl("http://localhost:8080/not_working"));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
webCtrl.get(request);
return app.exec();
}
test_server.py:
#!/usr/bin/env python
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os
class MyHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/working':
self.send_response(200)
self.send_header('Content-type','text-html')
self.end_headers()
self.wfile.write("hey")
elif self.path == '/not_working':
self.send_response(308)
self.send_header('Location','/working')
self.end_headers()
server_address = ('127.0.0.1', 8080)
httpd = HTTPServer(server_address, MyHTTPRequestHandler)
httpd.serve_forever()
I run the server, then while it's running, I run the client and I get got 0 bytes in the console. If I change the response from 308 to, say, 301, it works fine (prints got 3 bytes).
Any idea why?
Note: The redirect works fine in Chrome, so my server code is likely correct.
Note: It seems like it's documented as unsupported. From the docs:
This signal is emitted if the QNetworkRequest::FollowRedirectsAttribute was set in the request and the server responded with a 3xx status (specifically 301, 302, 303, 305 or 307 status code) with a valid url in the location header, indicating a HTTP redirect.
(emphasis mine)
I'd still like to know why, though.
For anyone who has the same problem, here's my FileDownloader class with support for 308.
filedownloader.cpp:
#include "filedownloader.h"
FileDownloader::FileDownloader(QUrl imageUrl, QObject *parent) :
QObject(parent)
{
m_imageUrl = imageUrl;
connect(
&m_webCtrl, SIGNAL (finished(QNetworkReply*)),
this, SLOT (onDownloaded_internal(QNetworkReply*))
);
QNetworkRequest request(imageUrl);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
m_webCtrl.get(request);
}
FileDownloader::~FileDownloader() {
}
void FileDownloader::onDownloaded_internal(QNetworkReply* reply) {
if(reply->error() != QNetworkReply::NoError) {
qDebug() << "error " << reply->error();
emit error();
return;
}
if(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 308) {
handleRedirect308(reply);
return;
}
QByteArray data = reply->readAll();
reply->deleteLater();
emit downloaded(data);
deleteLater();
}
void FileDownloader::handleRedirect308(QNetworkReply *reply) {
QByteArray header = reply->rawHeader("location");
QUrl url(QString::fromUtf8(header));
url = m_imageUrl.resolved(url);
QNetworkRequest request(url);
qDebug() << "308 to " << url;
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
m_webCtrl.get(request);
}
filedownloader.h:
#ifndef FILEDOWNLOADER_H
#define FILEDOWNLOADER_H
#include <QObject>
#include <QByteArray>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
class FileDownloader : public QObject {
Q_OBJECT
public:
explicit FileDownloader(QUrl imageUrl, QObject *parent = 0);
virtual ~FileDownloader();
signals:
void downloaded(QByteArray const& data);
void error();
private slots:
void onDownloaded_internal(QNetworkReply* reply);
private:
void handleRedirect308(QNetworkReply* reply);
QNetworkAccessManager m_webCtrl;
QUrl m_imageUrl;
};
#endif // FILEDOWNLOADER_H
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
I am trying to upload a simple test text file to a FTP server. In order to achieve this I am using QNetworkAccessManager, since QFtp has been deprecated in Qt 5.1.
I created a test.txt file in the programs directory and using QFile I am opening it as QIODevice::ReadWrite | QIODevice::Text.
The problem is when I set the connection and tell the QNetworkAccessManager to upload a file the program crashes ("FTPConnectionTest does not respond"). It happens both when I am trying to use an external FTP server or a local one created with FileZilla.
I connected all signals emitted by the reply (functions: uploadFinish, uploadProgress, uploadError) however no feedback is beeing captured.
Question: Is this problem lying on the side of FTP server or am I doing something wrong in my code?
Code snipped below:
Main.cpp
#include <QCoreApplication>
#include <ftp.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Ftp ftp;
return a.exec();
}
ftp.cpp
#include "ftp.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QFile>
#include <QUrl>
#include <QDebug>
Ftp::Ftp()
{
QFile file("test.txt");
if (file.open(QIODevice::ReadWrite | QIODevice::Text)) {
url = QUrl("ftp://127.0.0.1/test.txt");
url.setUserName("user");
url.setPassword("password");
qDebug() << "URL set" << url;
QNetworkAccessManager* nam = new QNetworkAccessManager();
qDebug() << "nam set";
QNetworkReply *rep = nam->put(QNetworkRequest(url), &file);
qDebug() << "after rep";
connect(rep, SIGNAL(finished()), this, SLOT(uploadFinish()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(uploadError(QNetworkReply::NetworkError)));
connect(rep, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(uploadProgress(qint64,qint64)));
}
else qDebug() << "failed to open";
}
void Ftp::uploadFinish()
{
qDebug() << "finished uploading file";
}
void Ftp::uploadProgress(qint64 a, qint64 b)
{
qDebug() << a << "/" << b;
}
void Ftp::uploadError(QNetworkReply::NetworkError state)
{
qDebug() << "State" << state;
}
See the QNetworkAccessManager::put documentation:
data must be opened for reading when this function is called and must remain valid until the finished() signal is emitted for this reply.
Your file object falls out of scope when the constructor finishes execution, so QNetworkAccessManager probably tries to read from object that is already deleted. You need to make file a class member variable or create it using QFile* file = new QFile().
I have a problem geting application token for Microsoft translator with QNetworkAccessManager. This is my code:
QUrl serverUrl("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
QNetworkRequest request(serverUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QUrl postData;
postData.addQueryItem("grant_type", "client_credentials");
postData.addQueryItem("client_id", ClientID);
postData.addQueryItem("client_secret", ClientSecret);
postData.addQueryItem("scope", "http://api.microsofttranslator.com");
networkAccessManager->post(request, postData.encodedQuery());
and when a finished(QNetworkReply*) signal is emitted I found that server reply contains no data at all, it is just empty! Does anyone know the solution of this problem?
UPD I checked a reply for errors and got the following: "Error creating ssl context". So I added SSL libs to my project, but got next error - "Unknown error". Any ideas?
I have used this class subclassed from QNetworkAccessManager to ignore SSL errors:
sslqnetworkaccessmanager.h
#ifndef SSLQNETWORKACCESSMANAGER_H
#define SSLQNETWORKACCESSMANAGER_H
#include <QtNetwork>
#include <QtCore>
class SslQNetworkAccessManager : public QNetworkAccessManager
{
Q_OBJECT
public:
explicit SslQNetworkAccessManager(QObject *parent = 0);
protected:
QNetworkReply* createRequest(Operation op, const QNetworkRequest & request, QIODevice * outgoingData = 0);
};
#endif // SSLQNETWORKACCESSMANAGER_H
sslqnetworkaccessmanager.cpp
#include "sslqnetworkaccessmanager.h"
SslQNetworkAccessManager::SslQNetworkAccessManager(QObject *parent)
: QNetworkAccessManager(parent)
{
}
QNetworkReply* SslQNetworkAccessManager::createRequest(Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
{
QNetworkRequest req(request);
QNetworkReply* reply = QNetworkAccessManager::createRequest(op, req, outgoingData);
reply->ignoreSslErrors();
return reply;
}
This class overrides the createRequest method and ignores any SSL errors that may occur with the QNetworkReply returned.