I am trying to implement a library to connect to an external resource and interact with it via an XML-based API. However I don't know how to get all the data before processing it. I am using QTcpSocket on Windows.
The way I have to interact with the resource is as follow:
- Connect to the server
- Send an XML message with credentials
- Get the response from the server
- Parse the XML response to extract the session ID
- Send other requests in XML with the session ID in it and parse the XML responses.
- Use the result in my application
Of course, depending on the request the message will not have the same structure so I need to parse the XML in the same function, right? And if so, how can I wait for all the data to arrive before parsing it?
Furthermore, as mentioned, it is under a Windows environment so I cannot use "waitForReadyRead" since as stated in the documentation:
This function may fail randomly on Windows. Consider using the event loop and the readyRead() signal if your software will run on Windows.
How does it work?
Thanks, Fred
Edit Here is my current code:
Client.cpp
#include "client.h"
#include <pugixml.hpp>
#include <map>
#include <sstream>
#include <string>
#include "task.h" // Generate the API message, not relevant
Client::Client(const QString& server, const QString& port, QObject* parent):
QObject(parent)
{
connectToServer(server, port);
connect(&socket_, &QTcpSocket::readyRead, this, &Client::getResult);
}
void Client::connectToServer(const QString& server, const QString& port)
{
bool ok;
int portNumber = port.toInt(&ok);
if (ok) {
if (socket_.state() == QTcpSocket::SocketState::ConnectedState)
socket_.disconnectFromHost();
socket_.connectToHost(server, portNumber);
} else {
throw tr("Cannot connect to server %1 on port %2. Make sure the provided information are correct.")
.arg(server)
.arg(port);
}
}
void Client::throwOnError(const pugi::xml_document& doc)
{
pugi::xpath_node_set errors = doc.select_nodes("/EXECUTION/TASK/RESULTSET/RESULT/MESSAGE");
std::string error_message = "";
for (pugi::xpath_node_set::const_iterator it = errors.begin(); it != errors.end(); ++it)
{
pugi::xml_node node = it->node();
if (std::string(node.attribute("type").value()) == "Error" ||
std::string(node.attribute("type").value()) == "Warning")
error_message += node.child_value();
}
if (!error_message.empty())
throw std::exception(error_message.c_str());
}
void Client::sendMessage(const QString &message)
{
outMessage = message;
result_.clear();
socket_.write(message.toUtf8());
}
void Client::getResult()
{
emit startReading();
while (socket_.bytesAvailable() > 0) {
result_.append(socket_.readAll());
socket_.flush();
}
resultMessage = QString(result_);
emit finishedReading();
}
void Client::login(const QString& user, const QString& password, const QString& project)
{
std::map<QString, QString> whereFields {{"userName", user}, {"password", password}};
QString request = prepareMessage("Login", "Security", std::map<QString, QString>(), whereFields); // Generates the XML message for the API
sendMessage(request);
// Wait for data to arrive - How ?
std::stringstream xmlResult = getXmlData(result_); // Remove the header from the API response and convert the QByteArray to a std::stringstream
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load(xmlResult);
throwOnError(doc);
pugi::xpath_node session = doc.select_node("/EXECUTION/TASK/RESULTSET/DATASETS/DATASET/secId");
sessionId_ = QString::fromStdString(session.node().first_child().value());
projectName_ = project;
emit taskCompleted();
}
Client.h
#ifndef Client_H
#define Client_H
#include <QObject>
#include <QTcpSocket>
#include <QByteArray>
#include <map>
#include <set>
#include "pugixml.hpp"
class Client : public QObject
{
Q_OBJECT
public:
Client(QObject* parent = 0) : QObject(parent) {}
Client(const QString& server, const QString& port, QObject* parent = 0);
// Connection
void connectToServer(const QString& server, const QString& port);
void login(const QString& user, const QString& password, const QString& project);
void logout();
QString const getSessionId() { return sessionId_; }
void throwOnError(const pugi::xml_document& doc);
QString sessionId() const { return sessionId_; }
QString outMessage; // For testing
QString resultMessage; // For testing
signals:
void ready();
void startReading();
void finishedReading();
void taskCompleted();
private slots:
void getResult();
private:
void sendMessage(const QString& message);
QTcpSocket socket_;
QString sessionId_;
QString projectName_;
QByteArray result_;
};
#endif // Client_H