Server Sent Events (SSE) with Qt - qt

I already saw several libraries for Server Sent Events, unfortunately, not for Qt. I also looked at the specification of SSE (just plain HTTP) and it seems that implementing SSE in Qt would require to:
Use QNetworkAccessManager in streaming mode (download)
Accept the content type header of SSE: application/events-stream
Reconnect when the connection is lost or closed
Attach a slot to the QNAM when new bytes are received (check for data : {...})
I'm not sure if it's so "easy"? Did I miss something?

I created a small demo with Qt and Server Sent Events.
The demo connects to a given EventSource URL (first argument) and prints every event to the command line.
Qt supports SSE out of the box since SSE is pure HTTP with a reconnection layer on top of it.
Prepare the request: set the text/event-stream accept header, allow redirects, disable the cache.
QNetworkRequest Network::Manager::prepareRequest(const QUrl &url)
{
QNetworkRequest request(url);
request.setRawHeader(QByteArray("Accept"), QByteArray(ACCEPT_HEADER));
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached
return request;
}
Connect the readyRead signal to a slot.
void Network::Manager::getResource(const QUrl &url)
{
qDebug() << "GET resource:" << url;
QNetworkRequest request = this->prepareRequest(url);
m_reply = this->QNAM()->get(request);
connect(m_reply, SIGNAL(readyRead()), this, SLOT(streamReceived()));
}
Every time a new event is received by the QNetworkAccessManager, you can read it using readAll. We reset the retries counter after every successful event.
void Network::Manager::streamReceived()
{
qDebug() << "Received event from stream";
qDebug() << QString(m_reply->readAll()).simplified().replace("data: ", "");
qDebug() << "-----------------------------------------------------";
m_retries = 0;
}
In case we lost the connection or the connection times out, the finished() signal is triggered of the QNetworkAccessManager. We try to reconnect to the event source (We connected this slot to the signal when we created our QNetworkAccessManager instance):
void Network::Manager::streamFinished(QNetworkReply *reply)
{
qDebug() << "Stream finished:" << reply->url();
qDebug() << "Reconnecting...";
if(m_retries < MAX_RETRIES) {
m_retries++;
this->getResource(reply->url());
}
else {
qCritical() << "Unable to reconnect, max retries reached";
}
}
You can find the demo here: https://github.com/DylanVanAssche/Qt-Server-Sent-Events-Demo

Related

QNetworkAccessManager HTTP Put request not working

After an exhaustive search on the web about this issue, none of the answers found solved it.
Using the technology Qt_5_15_2_MSVC2019_64/Microsoft Visual C++ Compiler,
I'm trying to send a JSON file through HTTP put request from QNetworkAccessManager to a custom QTcpServer, but when doing so, only HTTP headers are received (even twice) but not the JSON file in itself.
Snippet Code:
void Sender::putParameters(const QString& p_parameters) {
QJsonDocument docJson = QJsonDocument::fromJson(p_parameters.toUtf8());
QByteArray data = docJson.toJson();
QUrl url= QUrl("http://127.0.0.1:80/api/devices/1285/parameters");
QNetworkAccessManager* nam = new QNetworkAccessManager(this); // Even tried to put as member variable but still did not work
QNetworkRequest networkReq(url);
networkReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
networkReq.setRawHeader("Content-Length", QByteArray::number(data.size()));
QObject::connect(nam, &QNetworkAccessManager::finished, this,
&Sender::validateParameters);
nam->put(networkReq, data);
if(docJson.isEmpty())
qDebug() << "JSON was empty";
}
Result QTcpServer:
With Poco library there's no issue it works, or sending HTTP put request via Postman it does work.
#dabbler
Sorry for the late answer but here is the code snippet of the Server :
enter image description here
With postMan and Poco it's working perfectly, so I don't know if the issue is then related to the server, and for the client side #Dmitry you're right I should use QJsonDocument::isNull instead (but I used isEmpty() just for demonstration purpose that I wasn't sending an invalid Json which is not the case, the json is valid).
Yes my bad #Syfer
So I have the class Server inheriting from QTcpServer and overriding function incomingConnection such as : void incomingConnection(qintptr) Q_DECL_OVERRIDE;
Here is the code snippet:
Server::Server(QObject* parent) : QTcpServer(parent)
{
if(listen(QHostAddress::Any, 80))
qDebug() << "Ok listening port 80";
else
qCritical() << "Fail listening";
}
void Server::incomingConnection(qintptr p_intPtrSocket)
{
QTcpSocket* socketClient = new QTcpSocket(this);
socketClient->setSocketDescriptor(p_intPtrSocket);
connect(socketClient, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
}
void Server::onReadyRead()
{
QTcpSocket* socketClient = (QTcpSocket*)sender();
QString fullClientRequest = QString::fromUtf8(socketClient->readAll());
QStringList clientReq = fullClientRequest.split("\n");
qDebug() << "client response: " << clientReq;
}

QEventLoop slot not called with WASM

I am trying to run the following code:
std::string RequestLogin()
{
std::cout << ">> User Login method..." << std::endl;
QNetworkRequest request(this->serverUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QJsonObject payload;
payload.insert("userName", this->user);
payload.insert("password", this->password);
QByteArray byte_payload = QJsonDocument(payload).toJson();
QEventLoop loop;
bool a = connect(&(this->manager), SIGNAL(finished(QNetworkReply*)), &loop, SLOT(quit()));
// send post call
std::cout << "sending the post call" << std::endl;
QNetworkReply *reply = (this->manager).post(request, byte_payload);
loop.exec();
QByteArray reponse = reply->readAll();
std::cout << reponse.toStdString() << std:: endl;
return response.toStdString();
}
This method runs perfectly when I compile it as a c++ file with QT. But I am facing issue while build the wasm and calling this method. The event loop's quit slot is not called and so the response that I am waiting to get is not received.
The browser becomes unresponsive. On firefox, when I get notification to kill/wait for the page to respond, if I click kill, then in network tab I see the network rest completed successfully.
In the browser window the console did not get the response printed.
Does anybody know how to solve this issue?
For my requirement, I need to create methods in C++ which does some rest call, and on the response I need to do some computation before returning the values to Javascript.

read and write to qtcpsocket using qdatastream

There are various methods of reading and writing from a QTcpSocket using a QDatastream as seen here
The difference is, I will be sending more than "one packet" or blocks.
A basic implementation on the server (sending) side and client (recieving) is seen below - only the actual sending and receiving snippets are shown
More Info, What I tried:
When writing to a QTcpSocket, I attempted to use the QTcpSocket::canReadLine() however this fails straightup after the QTcpSocket::waitForReadReady() signal fires.
I then tried QDataStream::atEnd() in a while loop which causes a system crash :(
The code below shows my latest attempt of going through the QDataStream docs, and utilzing the commitTransaction where it states
If no full packet is received, this code restores the stream to the initial position, after which you need to wait for more data to arrive.
Under the heading Using Read Transactions. But ofcourse, this just reads one block that is sent, i.e the first block.
Question:
When writing to a QTcpSocket multiple times, and flushing the socket each time to send that data, how can I read this from a QTcpSocket as it is send, keep the original "send structure"?
The example below only reads the first block and ends. I would like to read the block containing "Response 2" and "Response 3".
Code Implementations:
//server.h
//...
QTcpSocket *clientSocket = nullptr;
QDataStream in;
//...
//server.cpp
//...
in.setDevice(clientSocket);
in.setVersion(QDataStream::Qt_4_0);
in.startTransaction();
QString nextFortune;
in >> nextFortune;
if (in.commitTransaction())
ui->lblOut->setText(nextFortune);
if (clientSocket != nullptr) {
if (!clientSocket->isValid()) {
qDebug() << "tcp socket invalid";
return;
}
if (!clientSocket->isOpen()) {
qDebug() << "tcp socket not open";
return;
}
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << QString(QString("Response:") + nextFortune);
if (!clientSocket->write(block)){
QMessageBox::information(this, tr("Server"),tr("Could not send message"));
}
clientSocket->flush();
// block.clear();
out << QString("Response number 2");
if (!clientSocket->write(block)){
QMessageBox::information(this, tr("Server"),tr("Could not send message"));
}
clientSocket->flush();
// block.clear();
out << QString("Response number 3 here, and this is the end!");
if (!clientSocket->write(block)){
QMessageBox::information(this, tr("Server"),tr("Could not send message"));
}
clientSocket->flush();
clientSocket->disconnectFromHost();
}
//...
And the client side
//client.h
//...
QTcpSocket *tcp_con = nullptr;
QDataStream in;
//...
//client.cpp
//...
if(!tcp_con->waitForReadyRead()){
qDebug(log_lib_netman_err) << "tcp con timeout for reading";
tcp_con->disconnectFromHost();
return ReturnObject(ReturnCode::SocketError, QString());
}
in.setDevice(tcp_con);
in.setVersion(QDataStream::Qt_4_0);
in.startTransaction();
QList<QString> data_rcv = QList<QString>();
QString s;
// while (tcp_con->canReadLine()) {
// in >> s;
// data_rcv.push_back(s);
// }
// while (!in.read) {
in >> s;
data_rcv.push_back(s);
// }
while (!in.commitTransaction()){
qDebug(log_lib_netman_info) << "waiting for more data";
in >> s;
data_rcv.push_back(s);
// qDebug(log_lib_netman_err) << "Unable to send data to server";
// tcp_con->disconnectFromHost();
// return ReturnObject(ReturnCode::FailedReceiving, QString());
}
// if (s.isEmpty()) {
// qDebug(log_lib_netman_err) << "Empty response recieved";
// tcp_con->disconnectFromHost();
// return ReturnObject(ReturnCode::NoDataRecieved, QString());
// }
tcp_con->disconnectFromHost();
return ReturnObject(ReturnCode::ReceivedSuccess, data_rcv);
Help would be greatly appreciated!

Qt5 WebSockets test app not connecting to test service

I wanted to open a qt websocket to the test service in ws://echo.websocket.org but I got the error QAbstractSocket::RemoteHostClosedError
I am connecting the signal error(QAbstractSocket::SocketError socketError) to a slot in my code in order to read the error number then look for it in here
My code looks like this
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Controller w;
w.initializeWebSocket("ws://echo.websocket.org", true);
w.show();
return a.exec();
}
Controller::Controller(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
}
void Controller::initializeWebSocket(QString url, bool debug)
{
m_webSocketURL = url;
m_webSocketDebug = debug;
if(m_webSocketDebug)
std::cout << "WebSocket server: " << m_webSocketURL.toStdString() << std::endl;
QObject::connect(&m_webSocket, SIGNAL(connected()), this, SLOT(onConnected()));
QObject::connect(&m_webSocket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
QObject::connect(&m_webSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
QObject::connect(&m_webSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(onTextMessageReceived(QString)));
m_webSocket.open(QUrl(m_webSocketURL));
}
void Controller::onConnected()
{
if (m_webSocketDebug)
std::cout << "WebSocket connected" << std::endl;
m_webSocket.sendTextMessage(QStringLiteral("Rock it with HTML5 WebSocket"));
}
void Controller::onDisconnected()
{
if (m_webSocketDebug)
std::cout << "WebSocket disconnected" << std::endl;
}
void Controller::onError(QAbstractSocket::SocketError error)
{
std::cout << error << std::endl;
}
void Controller::onTextMessageReceived(QString message)
{
if (m_webSocketDebug)
std::cout << "Message received:" << message.toStdString() << std::endl;
m_webSocket.close();
}
Im new to websockets so I have no idea where could the problem be. Can anyone give advise?
Opening websocket at "ws://echo.websocket.org" works for me just fine.
These handlers are sufficient in my project:
connect(&webSocket, SIGNAL(connected()), this, SLOT(onConnected()));
connect(&webSocket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(&webSocket, SIGNAL(textMessageReceived(const QString&)), this, SLOT(onTextMessageReceived(const QString&)));
I also just realized that I don't connect error() signal yet the program code is quite reliable for more than a year already and in case of disconnect there is a connection restore kick in. Maybe I should connect error() signal as well for infrequent strange cases.
The error QAbstractSocket::RemoteHostClosedError can be just a correct thing to get. Try to get the echo within reasonable time. The websocket farm we use in our project is holding the connection for up to 50 minutes so we do ping-pong between the client and the server to keep the connection live before this period expires.
// you can try that immediately after opening the web socket and also using some QTimer
m_webSocket.sendTextMessage("Pong!");
Try that and see the text reply as long as you are playing some public echo service.
Well, I verified your code and it seems to work fine. The error you give indicates a host related issue. It may be due to firewall, isp or other blocks/issues.
WebSocket server: ws://echo.websocket.org
WebSocket connected
Message received:Rock it with HTML5 WebSocket
WebSocket disconnected
I do like to point out that it's preferred to keep a pointer to a QWebSocket 'object'. It's very convenient to declare m_webSocket as QWebSocket *, and add m_webSocket = new QWebSocket(this). It's good practice to treat objects as objects. You don't want to accidentally try to 'copy' an QWebSocket directly. Also, due to the internals of Qt, you may eventually run into problems if this "Controller" object is destroyed while the QWebSocket is still attached to other objects (although I think Qt is prepared for it).

qt post and get request

I'm trying to log in to a webpage and then download some data that only members can download.
I've performed the post request and then the get request, for some reason the get request was finished first.
Is there any way to wait for the post to be finished or make it done first?
edit:
void downloader::LoginToHost()
{
QNetworkRequest request(QUrl("http://www.example.com/login.php"));
request.setHeader(QNetworkRequest::ContentTypeHeader,QVariant("application/x-www-form-urlencoded"));
QByteArray data("email=example&password=");
manager->post(request,data);
}
void downloader::Do_Request(QUrl url)
{
manager->get(QNetworkRequest(url));
}
void downloader::finished(QNetworkReply * reply)
{
QMessageBox mess;
if(reply->error() == QNetworkReply::NoError)
{
std::ofstream source("source-code.txt");
QString content = reply->readAll();
source << content.toStdString() << std::endl;
source.close();
mess.setText("Done download!");
}
else mess.setText(reply->errorString());
mess.exec();
}
Send the post and don't send the get until the post returns and triggers the finished signal in QNetworkReply.
Assuming there are two requests, one for the post and one for the get, which you've set up, here's an outline of what you can do: -
QNetworkRequst postRequest;
QNetworkRequest getRequest;
Sending the postRequest returns a QNetworkReply...
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager;
QNetworkReply postReply = networkAccessManager->post(postRequest, someData);
QNetworkReply getReply = nullptr; // C++ 11 nullptr
You can then connect to the postReply finished signal: -
// using a connection to a lambda function, send the get request when post has finished
connect(postReply, &QNetworkReply::finished, [this, networkAccessManager, getRequest, getReply](){
// handle the reply...
...
// send the getRequest
getReply = networkAccessManager->get(getRequest);
});

Resources