0

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:

  1. Connect to the server
  2. Send an XML message with credentials
  3. Get the response from the server
  4. Parse the XML response to extract the session ID
  5. Send other requests in XML with the session ID in it and parse the XML responses.
  6. 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
Fred T
  • 23
  • 1
  • 6
  • All `waitForXyz` methods should be treated as porting aids for broken code. There are very few circumstances where it's correct to use them, and I'm yet to see any such present themselves in SO questions. Instead of waiting for things, connect a slot to the `readyRead` signal to run your code asynchronously as new data becomes available and collect it in a `QByteArray` buffer. Parse that buffer incrementally until the parser indicates that the topmost element has been closed. – Kuba hasn't forgotten Monica Mar 28 '17 at 20:27
  • I can get the data using this method, however I am not sure how to wait before being able to process the data - sorry if I was unclear, what I am trying to achieve is find a way to know when the data is available to start processing it. – Fred T Mar 28 '17 at 20:48
  • I've said how: connect your slot to the socket's `readyRead` signal. You'll be notified each time a chunk of data arrives. You need to piece the chunks together and parse them. Depending on the type of a parser you're using, the exact procedure will differ. But all of the processing will be done in some slot, and that slot needs to be connected to that signal. There's no waiting of any kind. Your slot will be called each time the data is available - automatically. – Kuba hasn't forgotten Monica Mar 28 '17 at 21:54
  • I just added the code. This is where I am stuck: `sendMessage(request); // Wait for data to arrive - How ?` – Fred T Mar 29 '17 at 15:54

0 Answers0