How to POST data to a server with Qt 5? - c++

I'm trying to send a bit of json data to a web service using Qt 5.1.1. There are many examples out there, but almost all are for Qt 4.x, which had a slightly different API. Here's what I'm trying now:
QUrl url("http://...");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QByteArray jsonInPostFormat = "json=" + QUrl::toPercentEncoding(jsonAsString);
jsonInPostFormat.replace("%20","+");
reply = net->post(request, jsonInPostFormat);
connect(reply, SIGNAL(finished()), this, SLOT(finishedAddComment()));
The above has ... no effect. If I'm doing things correctly, I seem to not get any response from the server. (I call reply->readALL() in finishedAddComment().)
The service has a test form that works correctly. I've dumped the headers that it's sending and looked at the html, which is just a standard form with post method specified.
Am I doing something obviously wrong? Is there a better way, say, using QUrlQuery?
Some way of seeing the raw HTML requests that Qt is actually sending would be super-helpful...
Thanks!
Tyler

Qt5 no longer has the QUrl::encodedQuery() method. Not sure, but from the documentation it might work using QUrl::query() method instead.
Hope it helps.

Related

Using QHttpMultiPart with PUT operation and form fields

I'm having quite a bit of struggle in trying to achieve a simple PUT request in QT. Frankly I am still a starter using this framework so I have much left to learn.
What I am trying to do is make a PUT request with 2 parameters this Cloudflare Workers KV API.
With that being said this is my current code snippet:
QString szUrl = "";
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart valuePart;
valuePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"value\""));
valuePart.setBody(value.toByteArray());
QHttpPart metadataPart;
metadataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"metadata\""));
metadataPart.setBody(metadata.toByteArray());
multiPart->append(valuePart);
multiPart->append(metadataPart);
QNetworkRequest request;
request.setUrl(szUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data");
request.setRawHeader("Authorization", QString("Bearer %1").arg("...").toUtf8());
QNetworkReply* reply = m_NetworkManager->put(request, multiPart);
// delete the multiPart with the reply
multiPart->setParent(reply);
// Process reply (QNetworkAccessManager::finished)
// Process errors (QNetworkAccessManager::sslErrors)
qDebug() << reply->error();
qDebug() << reply->readAll();
qDebug() << reply->errorString();
The above returns always HTTP 400 (bad request) with zero, and I mean ZERO response. I could not retrieve the error response from the API in any way. Just an empty string ("") under debug.
If I execute the example cURL cli code from the API page, it works perfectly. I get a response and it's successful.
My question would be, what on earth am I doing wrong? I read the documentation and wrote the code accordingly, I cannot understand what's happening.
I have searched quite a bit for a possible answer online but unfortunately all issues and examples are with POST requests and file uploads (mostly).
Please advise, it would be much appreciated.
I will answer my own question because one of the reasons the above was not working it's because of my own fault.
Problem 1:
Bad formatting of metadata. Looks like Cloudflare API wants the metadata "compressed".
Problem 2:
Unable to retrieve API error response. Now this is an interesting one because I found countless forum posts and posts even here with the same problem, yet nobody posted the actual solution.
The readAll() function states this issue:
This function has no way of reporting errors; returning an empty
QByteArray can mean either that no data was currently available for
reading, or that an error occurred. This function also has no way of
indicating that more data may have been available and couldn't be
read.
The resolution is to connect QIODevice::readyRead to as per the documentation:
connect(reply, &QIODevice::readyRead, this, [=]() {
QByteArray response = reply->readAll();
qDebug() << response;
});
You now have the server reply regardless of the HTTP error code.
With that being said 2 problems solved in one go, enjoy.

Translate text using google without API

I am trying to write a simple free translator (QT widget) using the online translation service.
The idea is to send the standard get request to an online translator, and then parse the response.
But the reply does not contain the translated text ! I guess this is because the service uses AJAX.
In the example, I am using the google translator, but I get similar results with other translators (yandex, deepl).
I know that there is a way to use the shareware API, but since the project is not commercial at the moment, I do not want to register a bank card.
Is there a browser-like way to get translation without the API and use it for free ?
I have searched for any information, but to my surprise, it was outdated and irrelevant at the moment (since Google closed the free service).
And one more question. When I tried to cast the result to a QString (QString s = reply->readAll().toString() or QString s = reply->readAll().toStdString().c_str()), I got a distorted htlm code (a lot of NUL characters at the beginning of the file). I assume that this is due to a misinterpretation of the escape sequences, but how then to cast the result correctly ? Even in the current version, there is some garbage at the beginning of the file (NUL NUL Уi).
The code I use is:
void getTranslate() {
QNetworkAccessManager manager;
QUrl url("https://translate.google.com/#view=home&op=translate&sl=en&tl=ru&text=Hello%2C%20World%20!");
QNetworkRequest request(url);
QNetworkReply *reply = manager.get(request);
do {
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
} while(!reply->isFinished());
QFile html("out.html");
if (html.open(QIODevice::ReadWrite)) {
QDataStream out(&html);
out << reply->readAll();
}
reply->close();
delete reply;
}
looking at Google Translate it uses AJAX Request, to get the translation. You could try to change the URL to something like this (this is where the ajax request goes to):
https://translate.google.de/translate_a/single?client=webapp&sl=auto&tl=en&hl=de&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=sos&dt=ss&dt=t&dt=gt&otf=2&ssel=0&tsel=0&xid=45662847&kc=1&tk=656516.836633&q=dies%20ist%20ein%20test
This request returns JSON data, which should be easy to parse.
I am not sure what all the parameters are for, but maybe this information is helpful for you.
I belive your problems with screen-scraping approach may be that the translate application uses Ajax to call the server-side and retrieve the translation. The page you get when downloading using QNetworkRequest is merely the JS application, it doesn't actually contain the translation. That doesn't get filled in until after a call has been made from the page to the server.
And that is why it isn't working. Perhaps you could get it working somehow, so let us know how you do it :-)

How to make a simple Http request using Qt? The http request contains percent encoding in the URL

I have one URL something as below:
QUrl url("https://example.com/send/?apikey=somekey&numbers=mobile&sender=XYZ&message=0000%20is%20your%20OTP%20to%20authenticate%20with%20Server:%20abc.com")
Now I call this URL as below:
QNetworkRequest networkRequest(url);
QNetworkAccessManager manager;
QEventLoop event;
QObject::connect(&manager, &QNetworkAccessManager::finished, &event, &QEventLoop::quit);
auto* const pResponse = manager.get(networkRequest);
event.exec();
pResponse->deleteLater();
return pResponse->readAll();
However, the call doesn't succeed. Upon debugging I found out that, the "&message" part from URL which is
&message=0000%20is%20your%20OTP%20to%20authenticate%20with%20Server:%20abc.com
becomes
&message=0000 is your OTP to authenticate with Server: abc.com
Probably that's the cause of the failure. If I copy paste the URL in the browser, it works fine.
How to make this request a success?
Though I have coded as of it's a blocking call, actually my requirement is to just invoke the above URL.
Update: This problem is fixed by itself. The website, where I was making an API call, was following certain template. I was missing a fullstop "." at the end. Upon adding that, it started working.

How to properly do a http GET request using QNetworkAccessManager and QNetworkReply? How does the URL affect the request in Qt?

This is a follow up of this question. At first I thought the issue was resolved after checking out the example from the Qt wiki (I use the same code without a single change). However it appears that it's the URL that is the culprit. I tried using the links provided in this answer to test my http GET request. Using the Http Requester (Firefox addon for Http requests (GET, POST etc.)) and curl shows no issues with this link^:
$~: curl --request GET --url "http://httpbin.org/ip"
For some reason Qt gets stuck and the readyRead()/finished() signals are never emitted.
As a result the request gets cancelled after some time due to socket timeout...For something that is really small and opened by Firefox in less than a second.
I'm far from an expert when it comes to Http stuff. I'd like to know why this behaviour occurs in Qt while there is no sign of it when working with other tools.
EDIT: I've also tested the problematic URLs using Python and its urllib
import urllib.request
res = urllib.request.urlopen("http://httpbin.org/ip").read().decode("utf-8")
import xml.etree.ElementTree as ET
doc = ET.fromstring(res)
and it works just fine. Clearly there is something with Qt going on and/or me missing something when using it.
EDIT2: I've also tried another test service for HTTP requests - https://postman-echo.com. With curl there is no problem:
$~: curl --request GET --url "https://postman-echo.com/get?foo1=bar1&foo2=bar2"
For my surprise though there is no problem with Qt either! The only thing that I see here as a huge difference is that postman-echo.com uses HTTPS while the other URLs I've tried were HTTP. I exclude the https://www.qt.io which was the default URL in the Qt example and worked just fine (though it didn't have any parameters).
Try executing that in an event loop. Here is something similar to what I do in a non-gui application:
QUrl req_url = QUrl(href);
QNetworkRequest request(req_url);
//request.setRawHeader("Content-Type", "application/json;utf8");
//q_nam is QNetworkAccessManager created earlier
QNetworkReply *reply = q_nam->get(request);
QEventLoop event_loop;
connect(q_nam, SIGNAL(finished(QNetworkReply * ) ), &event_loop, SLOT(quit() ) );
event_loop.exec(); // blocks stack until "finished()" has been called
event_loop.processEvents(QEventLoop::ExcludeUserInputEvents, 500 );//what events to processed and for how long
event_loop.exit();
QNetworkReply::NetworkError er = reply->error();
// ....continue handling
I forgot to mention that I'm behind. Frankly I feel rather stupid for missing this and also not checking through the guest network at work (which circumvents the stupid proxy). A colleague of mine tried using HTTPS instead of HTTP (which is the original link). The HTTPS is also something that the proxy just lets go through without any issues. And it worked.
However a more neutral solution is (as my colleagues found out) to use QNetworkProxyFactory::setUseSystemConfiguration(true) which takes the proxy configuration that I have system-wide.

Qt 4.8.4 how to check if file exists on http server

i need to check the if file exists on http server,
i have the full path and when i try it via browser all works
but when i try in code to do :
if(QFile::exists("http://www.foo.com/hidden/Support/myapp_1.1.2_installer.exe" ))
{
qDebug("file exists");
return true;
}
else
{
qDebug("file not exists");
}
as it writen here :
http://www.qtcentre.org/archive/index.php/t-43712.html?s=b9ae49962c9219aec93b43c514e2ba33
it allways returns me false no matter what ..
what im doing wrong and is it the right way to do this ?
The function QFile::exists is not able to create HTTP requests, which would be necessary to achieve what you are trying to do. The forum discussion you linked to works, because the guy is trying to access a network drive; this is naturally supported by the operating system.
To check whether the file exists, you will have to go the long way around - here is an explanation of how to communicate with a web server: http://developer.nokia.com/Community/Wiki/Creating_an_HTTP_network_request_in_Qt
The Qt class QFile can only deal with files on local filesystem.
You can try out using Qt Network module, probably like this:
QNetworkAccessManager *nam = new QNetworkAccessManager(this);
....
QNetworkRequest req(QUrl("http://www.foo.com/hidden/Support/myapp_1.1.2_installer.exe"));
QNetworkReply *reply = nam->get(req);
connect(reply, SIGNAL(metaDataChanged()),
this, SLOT(slotMetaDataChanged()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotNetworkError(QNetworkReply::NetworkError)));
NOTE THAT if you only want to check for the file's existence, you DON'T want to connect to the finished(QNetworkReply*) signal, because the signal will only be emitted when the network reply has finished processing. That is, the signal will only be emitted after the file is totally downloaded if the file exists.
Then,
slotMetaDataChanged() is called whenever you received new HTTP response headers, you can then check the QNetworkRequest::HttpStatusCodeAttribute for response HTTP codes like 200(OK) or 404(Not Found). In your case, if the returned HTTP code is 200, the file exists.
slotNetworkError() is called when the network request encounters an error, like "Host Not Found" or "Connection Refused", it's up to you to handle these situations in this slot.
The way you are trying to do this, is totally wrong. QFile isn't able to query a webserver. What you need to do is use the QNetworkAccessManager class. With this you can try to download your myapp_1.1.2_installer.exe. If the file does not exist, you will get an error message.
Why the poster on qtcente.org claims it worked for him... no idea. Maybe because his address was a local one. But it still smells fishy.
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply*)),
this, SLOT(replyFinished(QNetworkReply*)));
manager->head(QNetworkRequest(QUrl("www.foo.com/hidden/Support/myapp_1.1.2_installer.exe")));
The QNetworkReply in the replyFinished slot has the method NetworkError QNetworkReply::error() const.
You should get a QNetworkReply::ContentNotFoundError if your file does not exist.
Edit: As several comments pointed out, just to learn the existence of a file on a remote server using 'get' and connecting to replyFinished might not be the best of ideas. Might be ok for very small files, but definitely overkill for large blobs of data. I changed the 'get' request into a 'head'. Turner's solution will work, mine should now be an acceptable alternative.