Here's the situation - I attach some file (JPEG image) to the HTTP POST request so it should be sent to the local server via Wi-Fi and server should return some result (simple text).
Here's the problem I faced doing this in my Qt-application for Nokia N9 (Meego 1.2 Harmattan).
After request is sent, proceed by the server and answer is sent back (I can see log on the server) there's a huge delay (about 1 min) before data from server reaches the handset.
If answer returns in several parts - the delay is before first part and others are getting very fast (as it should be with the first one too).
The same code I use in the same app for Symbian^3 (Symbian Anna) on Nokia C6-01 and it works just fine - all the data returns in a couple of seconds (tested in the same network with the same server and request). Also I have several GET requests sending from this app to the same server and all of them works fine too. So it might be the only Meego problem.
Snippets:
void PostDownloader::sendPostJpgImage(QString url, QImage image) {
if(mainReply)
return;
char boundary[] = "AyV04a234DsHeKHcvNds";
image = image.convertToFormat(QImage::Format_RGB888);
QByteArray body;
QBuffer buffer(&body);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "JPG");
buffer.close();
QByteArray b;
b.append("--").append(boundary).append("\r\n");
b.append("Content-Disposition: form-data; name=\"jpgfile\"; filename=\"camera\"\r\n");
b.append("Content-Type: image/jpeg\r\n");
b.append("\r\n");
b.append(body);
b.append("\r\n");
b.append("--").append(boundary).append("--");
QNetworkRequest req = QNetworkRequest(QUrl(url));
req.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QString("multipart/form-data; boundary=")+boundary));
req.setHeader(QNetworkRequest::ContentLengthHeader, QString::number(b.size()));
req.setRawHeader("Connection", "Close");
req.setRawHeader("Cache-Control", "no-cache");
req.setRawHeader("Keep-Alive", "1");
mainReply = manager->post(req, b); //POST
connect(mainReply, SIGNAL(readyRead()), this, SLOT(dataReceived()));
connect(mainReply, SIGNAL(finished()), this, SLOT(finished()));
}
So the delay is before calling the dataReceived() slot. How can this can be solved? What can you advice?
Thanks in advance.
Related
Is there a way with QWebEngineView to intercept an http request, and to serve it server-less from the app ?
I heard about QWebEngineUrlRequestInterceptor and acceptNavigationRequest(), but they provide only inspection on requests, and redirection for get... But I would like to make the http response from the Qt app.
(I added pyqt in the tags because I would use it from python, but a c++ answer is acceptable too)
To intercept http request you will need to use this code:
// on app startup
QWebEngineProfile.defaultProfile().installUrlSchemeHandler(new
QByteArray("https"), new QWebEngineUrlSchemeHandler() {
#Override public void requestStarted(QWebEngineUrlRequestJob job) {
final String url = job.requestUrl().url();
if (**some url not ok condition**) {
job.fail(QWebEngineUrlRequestJob.Error.UrlInvalid);
}
String data = loadSomeData();
if (data != null) {
QBuffer buffer = new QBuffer();
// this is IMPORTANT! or you will have memory leaks
job.destroyed.connect(buffer::disposeLater);
buffer.open(QIODeviceBase.OpenModeFlag.WriteOnly);
buffer.write(data.getBytes(StandardCharsets.UTF_8));
buffer.close();
job.reply(new QByteArray("text/html"), buffer);
}
}
});
My versions is for QTJambi(Java), but it's not hard to convert it to C++/Python
The qt documentation says the redirection is only for GET request. However, when trying it out (PyQt6==6.4.0) we found out that this is actually not true. If you redirect a POST request in the WebEngine to a local webserver listening on localhost, you will actually receive the request including the payload.
Perhaps this is because Webkit doesn't include the payload for redirect and Chromium does? (I couldn't find docs stating the difference.)
from PyQt6.QtCore import QUrl
from PyQt6.QtWebEngineCore import QWebEngineUrlRequestInterceptor
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def interceptRequest(self, info):
method = info.requestMethod()
if method == "POST":
if info.requestUrl().url() == "https://my-url-to-something":
info.redirect(QUrl("http://127.0.0.1:8000"))
I have some problems with connecting a qt client to an embedded jetty server.
At first, I use the following components:
Qt 4.4.3 (compiled with active openssl support)
jetty 8.8.1
java 6
I know, the versions are not most recent, but because of licencing issues and customer wishes I can not use newer one.
So, the scenario is that a qt client has to send http GET and POST requests to the jetty server. As long I use simple http with the QHttp object it works fine, the problems start when I switch to SSL.
My first try was to use the QSslSocket object for the GET request:
// Load certs + private key to socket
_pSocket = new QSslSocket(this);
_pSocket->setLocalCertificate(_certificate);
_pSocket->setPrivateKey(_privatekey);
_pSocket->addDefaultCaCertificate(_cacertificate);
connect (_pSocket, SIGNAL(encrypted()), this, SLOT(_encrypted()));
_pSocket->connectToHostEncrypted("localhost", 8000);
with the following slot function for the encrypted state:
void TestClient::_encrypted() {
QString _path("/testpath/list");
QByteArray buffer("GET ");
buffer.append(_path).append(" HTTP/1.1\r\n");
_pSocket->write(buffer);
}
Here I have my first problem:
This results in the following string, which is as far as I see compliant to RFC 2616:
"GET /testpath/list HTTP/1.1\r\n"
For some reason, the jetty server has a problem with that, keeping in a loop till the client close the connection because of a time out.
But if I use the following string, it works perfect:
"GET /testpath/list\r\n"
Here is my first question: Do you now an explanation for this behaviour ? I can live with it, but I want to know the reason
My second problem is the POST request, this fails always.
These examples I already tried:
"POST /testpath/receive/\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/ HTTP/1.1\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/\r\n\r\n{"data":"hello world ?!"}\r\n"
"POST /testpath/receive/ HTTP/1.1\r\n\r\n{"data":"hello world ?!"}\r\n"
I have the feeling, that the body is every time empty, so my server crashes because he tries to parse an empty string as json.
At least, the following log shows that:
2013-11-19 17:11:51.671, INFO, foo.bar.RepositoryHandler, qtp11155366-16 - /testpath/receive request type : /receive
2013-11-19 17:11:51.811, ERROR, foo.bar.RepositoryHandler, qtp11155366-16 - /testpath/receive missing or unknown elements in JSON request. Check JSON against documentation
2013-11-19 17:11:51.874, WARN, org.eclipse.jetty.server.AbstractHttpConnection, qtp11155366-16 - /testpath/receive /testpath/receive
java.lang.NullPointerException: null
at foo.bar.RepositoryHandler.decodeViewingRequest(RepositoryHandler.java:366) ~[MyServer.jar:na]
at foo.bar.RepositoryHandler.handle(RepositoryHandler.java:182) ~[MyServer.jar:na]
So, all together, I think I have several major errors in my requests. But which ?
My second try was to use the QHttp object and change the QSocket it uses with a QSslSocket I already initiated.
Here's the code of the main function:
QSslSocket* _pSocket;
QHttp* _pHttp;
int _id;
QBuffer* _pBuffer;
QByteArray _data;
_pSocket = new QSslSocket(this);
_pSocket->setLocalCertificate(_certificate);
_pSocket->setPrivateKey(_privatekey);
_pSocket->addDefaultCaCertificate(_cacertificate);
QUrl url;
url.setScheme("https");
url.setHost("localhost");
url.setPort(8001);
url.setPath("/testpath/receive");
connect (_pSocket, SIGNAL(encrypted()), this, SLOT(_encrypted()));
connect(_pHttp,SIGNAL(requestFinished(int,bool)),this,SLOT(_requestFinished(int,bool)));
connect(_pHttp,SIGNAL(done(bool)),this,SLOT(_done(bool)));
_pBuffer = new QBuffer(&_data);
_pHttp->setSocket(_pSocket);
_pSocket->connectToHostEncrypted(strHost, strPort.toInt());
_id = _pHttp->get(url.toString(),_pBuffer);
And the callbacks:
void _requestFinished(int id, bool error) {
if(id = _id)
qDebug() << "data=" << _data;
}
void _encrypted() {
qDebug() << "encrypted";
}
void _done(bool error) {
logInfo() << "_done";
if(_pHttp) {
_pHttp->abort();
delete _pHttp;
_pHttp = 0;
}
if(_pBuffer) {
delete _pBuffer;
_pBuffer = 0;
}
if(_pSocket) {
_pSocket->disconnectFromHost();
delete _pSocket;
_pSocket = 0;
}
}
I think, I only have to change the position of the _pHttp->get call, perhaps in the _encrypted callback, but I'm not sure.
Some good advise ?
Thanks,
Robert
Your HTTP request is incomplete, per RFC2616.
"GET /testpath/list HTTP/1.1\r\n"
That is invalid.
Try this instead.
"GET /testpath/list HTTP/1.1\r\n" + /* request line (required) */
"Host: localhost\r\n" + /* host header (required minimum) */
"\r\n" /* terminating CR + LF (required) */
As outlined in Section 5.1.2
The most common form of Request-URI is that used to identify a
resource on an origin server or gateway. In this case the absolute
path of the URI MUST be transmitted (see section 3.2.1, abs_path) as
the Request-URI, and the network location of the URI (authority) MUST
be transmitted in a Host header field. For example, a client wishing
to retrieve the resource above directly from the origin server would
create a TCP connection to port 80 of the host "www.w3.org" and send
the lines:
GET /pub/WWW/TheProject.html HTTP/1.1
Host: www.w3.org
The Request-URI line and Host header Header are mandated.
i'm struggling with a code snippet for days now, i was wondering if someone could help me understand this code snippet. i'm not asking for code whatsoever, just someone to explain to me this please : (a uri appear to be the complete url to a service)
void RestClient::_prepareRequest( QNetworkRequest& a_request, const QString& a_uri ){
QSslConfiguration config(QSslConfiguration::defaultConfiguration());
config.setProtocol(QSsl::SslV3);
config.setSslOption(QSsl::SslOptionDisableServerNameIndication, true);
a_request.setSslConfiguration(config);
a_request.setRawHeader("Accept", "application/xml");
a_request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
QByteArray l_api_key; l_api_key.append( toQString( m_api_key) );
QByteArray l_request_hash;
l_request_hash.append( toQString( _buildRequestHash( toStlString(a_uri) ) ) );
a_request.setRawHeader("EMApikey", l_api_key );
a_request.setRawHeader("EMRequestHash", l_request_hash );
a_request.setUrl( QUrl( a_uri ) );
}
So what you've got there is a function taking two parameters, a reference to a QNetworkRequest and a constant reference to a QString for the URI you wish to access. The next lines sets QSslConfiguration to get the default SSL configuration for Qt's network access, and stores it in config. It then sets some further QSsl options and then sets the a_request's SSL settings to be provided by the config you've just set.
Next up it sets some HTTP headers for the request, so these are reasonably standardised, so the Accept references what kind of information is acceptable for the response from the server which in this case is xml (Accept header documentation). The Content-type tells the receiving server what sort of data you're sending in the request body.
The final stage sets a non-standard HTTP header, which is for the application API access key, after that it sets the URL you originally passed and the function is complete. After that the QNetworkRequest can be used with QNetworkAccessManager to send a request to a server, with an API key encoded in, and you'll receive an XML response in return.
I am attempting to create an application that can login to a website. The specific website is:
http://adfast.biz
Here is the code I am currently using:
void MainWindow::http_finish(QNetworkReply *reply)
{
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code >= 300 && code < 400)
{ //HTTP 3XX codes are redirections
QUrl redirectTo = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
reply->manager()->get(QNetworkRequest(redirectTo));
return;
}
if (reply->error() == QNetworkReply::NoError)
{
QString Msg = QString::fromUtf8(reply->readAll());
if (cdone == 0)
{//Runs only once, causing a reload of the main page
++cdone;
QUrl URL("http://adfast.biz");
QNetworkRequest QNR(URL);
reply->manager()->get(QNR);
QMessageBox::information(0,"1)" + QString::number(code),Msg);
return;
}
QMessageBox::information(0,"2)" + QString::number(code),Msg);
}
else
{
QMessageBox::information(0,"Error:",reply->errorString());
}
reply->deleteLater();
}
void MainWindow::on_Send_clicked()
{
QNetworkAccessManager* MNAM = new QNetworkAccessManager(this); //Stored within QNetworkReply->manager()
connect(MNAM,SIGNAL(finished(QNetworkReply*)),this,SLOT(http_finish(QNetworkReply*)));
QUrlQuery postData;
postData.addQueryItem("email","mail#mail.net");
postData.addQueryItem("senha","Password");
postData.addQueryItem("logar","ok");
QUrl URL(ui->TXT_Input->toPlainText());
URL.setQuery(postData);
QNetworkRequest QNR(URL);
QNR.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
MNAM->post(QNR,URL.toEncoded());
}
I am guessing that I either am sending the information the wrong way or that I need to possibly manage cookies? Both responses come back with HTTP status code: 200. The first comes with no source the second comes with the full web-source but is NOT logged in. I am positive that the user-data being sent is correct, but not that it is being sent correctly.
Edit:
I've changed a little, with no luck. First I have added a cookie-jar using:
QNetworkAccessManager* MNAM = new QNetworkAccessManager(this); //Stored within QNetworkReply->manager()
QNetworkCookieJar* cJar = new QNetworkCookieJar;
MNAM->setCookieJar(cJar);
connect(MNAM,SIGNAL(finished(QNetworkReply*)),this,SLOT(http_finish(QNetworkReply*)));
Then, I tested to see if any cookies are being received using the following code at the top of MainWindow::http_finish:
QList<QNetworkCookie> cookies = reply->manager()->cookieJar()->cookiesForUrl(QUrl("http://adfast.biz/"));
QMessageBox::information(0,"Cookies",QString::number(cookies.count()));
I want to add that the post is being sent to: http://adfast.biz/login (That is the value of: ui->TXT_Input->toPlainText()) But it seems that I can't get this to login at all. And I am not sure what I'm missing.
So, I did manage to login into this site with testguest#yahoo.com and mypassword12. But I can only offer very quick and dirty code, which explains how. The 'polishing' you must do yourself. :-)
QFile f("/tmp/cookie.txt");
f.open(QIODevice::ReadOnly);
QDataStream s(&f);
while(!s.atEnd()){
QByteArray c;
s >> c;
QList<QNetworkCookie> list = QNetworkCookie::parseCookies(c);
qDebug() << "eee" << list;
jar->insertCookie(list.at(0));
}
connect(MNAM,SIGNAL(finished(QNetworkReply*)),
this,SLOT(http_finish(QNetworkReply*)));
QUrlQuery postData;
postData.addQueryItem("email","testguest#yahoo.com");
postData.addQueryItem("senha","mypassword12");
postData.addQueryItem("logar","ok");
//QUrl URL("http://adfast.biz/");
//QUrl URL("http://adfast.biz/anuncios_telexfree");
URL.setQuery(postData);
QNetworkRequest QNR(URL);
QNR.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
MNAM->get(QNR);
Above code reads the cookies, which are written in the http_finish slot below into /tmp/cookie.txt.
The first time you run this code, you must uncomment the first URL, the second time, when you have your cookies, the second URL. Ignore that I made your post into a get. I did it just for debugging reasons.
void MainWindow::http_finish(QNetworkReply *reply){
qDebug() << reply->readAll();
QList<QNetworkCookie> list =
MNAM->cookieJar()->cookiesForUrl(QUrl("http://adfast.biz/"));
QFile f("/tmp/cookie.txt");
f.open(QIODevice::ReadWrite);
for(int i = 0; i < list.size(); ++i){
QDataStream s(&f);
s << list.at(i).toRawForm();
}
f.close();
}
The code above writes the cookies from http://adfast.biz/ into /tmp/cookie.txt. Below an example of a cookie, which I received:
2-Sep-2013 16:41:39 GMT; domain=adfast.biz; path=/3 16:41:39 GMT;
domain=adfast.biz; path=/n=adfast.biz; path=/
Summary: You must connect to http://adfast.biz/ to get your cookie. When you have it, you must again connect, but this time to http://adfast.biz/anuncios_telexfree.
You may need to add a QNetworkCookieJar to your QNetworkAccessManager for keeping and sending cookies between requests and responses. You can set your cookie jar by using QNetworkAccessManager::setCookieJar(QNetworkCookieJar* cookieJar). Have a look at the requests being sent using HttpFox in Firefox and you can see if there are any cookies being sent back and forth.
Try to connect:
connect(&MNAM,SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
this,SLOT(provideAuthentication(QNetworkReply*,QAuthenticator*)));
And create a slot:
void TrackerClient::provideAuthentication(QNetworkReply *reply,
QAuthenticator *auth){
Q_UNUSED(reply);
auth->setUser(<your username>);
auth->setPassword(<your password>);
}
Ok, second try. The idea to add a cookiejar is correct, but just adding the jar is not enough. I tried the following with another site, which requires login. I did work. For your site I don't have login and password, and since it is not English, it is a little bit too hard for me for a quick help. :-)
How the login procedure works on "my" site.
You go to the main url, e.g. www.mysite.something.
The site asks you for a cookie. You have none.
Your are redirected (status 302 temporary moved) to a
page www.mysite.something/takelogin.php <--- example.
You enter your credentials. You already do this in your postdata.addQuery calls.
You send your post. If your creds are ok, the site sends you a cookie.
So far so good. What did you wrong? Now that you have the cookie you must again go to www.mysite.something. You are not automatically logged in and redirected to the main page of your site. And keep in mind, that cookies in QNetworkCookieJar are not stored on disk. They are only kept in memory. So, if your jar/MNAM is deleted, everything is gone.
You can easily store your received cookies to disk using the toRawForm() method of QNetworkCookie. To restore the cookie use the static QNetworkCooky::parseCookies(const QByteArray &cookieString) method.
Oh, almost forgot: The main url www.mysite.something did not send any cookies. I had to follow the redirect www.mysite.something/takelogin.php.
Disclaimer: It worked on my site. I don't know, if this is general for all sites, which require a login.
Checked with wrong creds: I was redirected to adfast.biz/login. This seems to be your redirect login page. More I cannot do without real creds.
I've got some class to interfere with HTTP-server.
Here is meaningfull code parts:
const QString someClass::BASEURL = QString("http://127.0.0.1:8000/?");
someClass::someClass():
manager(new QNetworkAccessManager(this))
{
}
QNetworkReply *someClass::run(QString request)
{
qDebug() << request;
QEventLoop loop;
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
QNetworkReply *res = manager->get(QNetworkRequest(QUrl(BASEURL + request)));
loop.exec();
return res;
}
When I call method run(), sometimes (not every time) the are two identical GET-requests
(I looked with tcpdump). qDebug() executes 1 time.
Is there some error in my code? I can't see any possible explanation.
UPDATE:
After some tcpdump ouptut research.
After second request it sends packet with RST flag as an answer to FIN.
But I still can see no difference in TCP-streams that triggers the problem and that doesn't.
F.e. here is wireshark's output. Stream 8 went well. Stream 11 was duplicated with Stream 12.
I'm stuck with this. Maybe it's some protocol errors from server-size, I'm not sure. Or maybe it's a bug in QNetworkAccessManager.
Have you tried rewriting you code to be more asynchronous without using QEventLoop in a local scope? Your code looks good to me, but there might be some weird QT bug that you running into in the way it queues up requests for processing and using QEventLoop in the local scope. I usually use QNetworkAccessManager in the following manner to send GET and POST requests:
void someClass::run(QString request)
{
qDebug() << request;
QObject::connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(on_request_complete(QNetworkReply*)));
QNetworkReply *res = manager->get(QNetworkRequest(QUrl(BASEURL + request)));
}
void someClass::on_request_complete(QNetworkReply* response)
{
// Do stuff with your response here
}