简体   繁体   中英

Qt app using QNetworkAccessManager crashes when built for WebAssembly

I am trying to make simple application that downloads info from server via REST api. For downloading data, I use QNetworkAccessManager . My application works well when built for desktop and I am trying to build it for WebAssembly .

I am using:

  • Qt version 5.15.2
  • Emscripten version 1.39.8 (as written by this website to be known-good versions for specific Qt version)

I have built Qt webassembly from source with -feature-thread to support multithreading, and memory / workers (threads) settings is

 QMAKE_WASM_PTHREAD_POOL_SIZE = 8
 QMAKE_WASM_TOTAL_MEMORY = 3GB

When I use it as native desktop, everything works ok. When I launch it via browser (WebAssembly), application starts, the GUI shows up and I can input connection address. When I write a valid one (server that supports CORS), the request goes without problem and returns valid JSON document. But when I type in invalid address, the application crashes .

The crash is caused by abort() function somewhere in Qt ( call stack ). Also, the application behave inconsistently - sometimes crash happen for first request, sometimes for third.

There are two possible errors I encountered - Uncaught RuntimeError: abort(undefined) and Uncaught RuntimeError: abort(Runtime error: The application has corrupted its heap memory area (address zero)!) .

I tried multiple tutorials for QNetworkAccessManager , but with same result.

main.cpp

#include "DummyRequestManager.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    DummyRequestManager c;
    c.init();

    return app.exec();
}

DummyRequestManager.hpp

#ifndef DUMMYREQUESTMANAGER_H
#define DUMMYREQUESTMANAGER_H

#include <QQuickView>
#include <QObject>
#include <QNetworkAccessManager>
#include <QJsonDocument>
#include <memory>

enum HttpRespStatus
{
    NoResponse = 0,
    Ok = 200,
    NoContent = 204,
    BadRequest = 400,
    Unauthorized = 401,
    Forbidden = 403,
    NotFound = 404,
    MethodNotAllowed = 405,
    NotAcceptable = 406, // Shouldn't be used.
    Conflict = 409,
    Gone = 410,
    UnsupportedMediaType = 415,
    InternalError = 500,
    NotImplemented = 501,
    ServiceUnavailable = 503
};


class DummyRequestManager : public QNetworkAccessManager
{
    Q_OBJECT
    Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
public:
    DummyRequestManager();

    void init();

    QString url() const { return url_; }

public slots:
    QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData);
    QNetworkReply* get(QNetworkRequest &request);

    void sendRequest();

public slots:
    // Setters
    void setUrl(QString url);

signals:
    void urlChanged(QString url);

private:
    int port_{0};
    QString scheme_{"https"};
    QString url_{""};
    QString finalUrl_{""};

    QQuickView view_;
};

#endif // DUMMYREQUESTMANAGER_H

DummyRequestManager.cpp

#include "DummyRequestManager.h"
#include <QQmlContext>
#include <QNetworkReply>
#include <QJsonObject>

DummyRequestManager::DummyRequestManager()
{
    connect(this, &DummyRequestManager::urlChanged, this, [=](){
        QStringList split = url_.split(":");
        QString addr = "";
        if(split.size() == 2)
        {
            addr = split.first();
            port_ = split.last().toInt();
            finalUrl_ = QString("https://%1:%2/api").arg(addr).arg(QString::number(port_));
            qDebug() << finalUrl_;
        }
    });
}

void DummyRequestManager::init()
{
    port_ = 8080;

    // WARNING Code below is supposed to be only for testing, it's necessary to add certificate
#ifndef QT_NO_SSL
    QSslConfiguration sslConf = QSslConfiguration::defaultConfiguration();
    sslConf.setPeerVerifyMode(QSslSocket::VerifyNone);
    QSslConfiguration::setDefaultConfiguration(sslConf);
#endif

    scheme_ = "https";

    view_.rootContext()->setContextProperty("dummy", this);

    view_.setSource(QUrl(QStringLiteral("qrc:/main.qml")));
    view_.show();
}

QNetworkReply *DummyRequestManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
{
    return QNetworkAccessManager::createRequest(op, request, outgoingData);
}

QNetworkReply *DummyRequestManager::get(QNetworkRequest &request)
{
    qDebug() << "request url" << request.url();
    request.setRawHeader("accept", "application/json");

    request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
    return QNetworkAccessManager::get(request);
}

void DummyRequestManager::sendRequest()
{

    QUrl url(finalUrl_ + QString("/info"));
    url.setScheme(scheme_);
    QNetworkRequest request(url);

    QNetworkReply *reply = get(request);
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        qDebug() << reply->header(QNetworkRequest::ContentTypeHeader).toString();
        qDebug() << reply->header(QNetworkRequest::LastModifiedHeader).toDateTime().toString();;
        qDebug() << reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
        qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        qDebug() << reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();

        QByteArray data = reply->readAll();
        QJsonDocument doc = QJsonDocument::fromJson(data);
        qDebug().noquote() << doc.toJson(QJsonDocument::Indented);

        reply->deleteLater();
    });

}

void DummyRequestManager::setUrl(QString url)
{
    if (url_ == url)
        return;

    url_ = url;
    emit urlChanged(url_);
}

main.qml

import QtQuick 2.12

Item {
    width: 640
    height: 480
    visible: true

    Rectangle {
        id: centerRect
        width: 200
        height: 200
        anchors.centerIn: parent
        color: "blue"

        MouseArea {
            anchors.fill: parent
            onClicked: {
                dummy.sendRequest()
            }
        }
        Text {
            anchors.centerIn: parent
            text: qsTr("Send")
            color: "white"
        }
    }

    Rectangle {
        color: "gray"
        width: centerRect.width
        height: 50
        anchors.top: centerRect.top
        anchors.horizontalCenter: centerRect.horizontalCenter
        TextInput {
            anchors.fill: parent
            onTextChanged: {
                dummy.url = text
            }
        }
    }
}

Do you know where the problem could be?

Thank you for your help!

EDIT: It do not crash when I remove reply->deleteLater() in lambda function.

In general the QNetworkAccessManager support is limited in WebAssembly, because the browser is in charge of the actual HTTP request, so SSL handling is not supported, you can not set any client side certs for instance. Also multi thread support is really limited and buggy in Qt (at least 5.14), but you should be fine with a single threaded build. QML apps in general run just fine in WebAssembly in single threaded builds, I know that because I was part of the Felgo efort to bring production apps to wasm, you can test our results (all single threaded) in our web editor https://felgo.com/web-editor or our samples section http://felgo.com/try-wasm

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM