问题描述
我正在尝试制作通过 REST api 从服务器下载信息的简单应用程序。对于下载数据,我使用 QNetworkAccessManager。我的应用程序在为桌面构建时运行良好,我正在尝试为 WebAssembly 构建它。
我正在使用:
- Qt 5.15.2 版
- Emscripten 版本 1.39.8(由 this 网站编写,是特定 Qt 版本的已知良好版本)
我使用 -feature-thread
从源代码构建了 Qt webassembly 以支持多线程,并且内存/工作线程(线程)设置是
QMAKE_WASM_PTHREAD_POOL_SIZE = 8
QMAKE_WASM_TOTAL_MEMORY = 3GB
当我将其用作本机桌面时,一切正常。当我通过浏览器 (WebAssembly) 启动它时,应用程序启动,GUI 出现,我可以输入连接地址。当我编写一个有效的(支持 CORS 的服务器)时,请求没有问题并返回有效的 JSON 文档。但是当我输入无效地址时,应用程序崩溃。
崩溃是由 Qt (call stack) 中某处的 abort()
函数引起的。此外,应用程序的行为不一致 - 有时第一个请求会崩溃,有时第三个会崩溃。
我遇到了两个可能的错误 - Uncaught RuntimeError: abort(undefined)
和 Uncaught RuntimeError: abort(Runtime error: The application has corrupted its heap memory area (address zero)!)
。
我为 QNetworkAccessManager 尝试了多个教程,但结果相同。
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,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
}
}
}
}
你知道问题出在哪里吗?
感谢您的帮助!
编辑:
当我在 lambda 函数中删除 reply->deleteLater()
时它不会崩溃。
解决方法
一般来说,WebAssembly 对 QNetworkAccessManager 的支持是有限的,因为浏览器负责实际的 HTTP 请求,所以不支持 SSL 处理,例如您不能设置任何客户端证书。此外,多线程支持在 Qt(至少 5.14)中确实有限且有问题,但是您应该可以使用单线程构建。 QML 应用程序通常在 WebAssembly 中以单线程构建运行得很好,我知道因为我是 Felgo 努力的一部分,将生产应用程序带到 wasm,您可以在我们的网络编辑器中测试我们的结果(所有单线程){{3} } 或我们的示例部分 https://felgo.com/web-editor