From 643410136004825cb395e342f5c4ff90e85de4fe Mon Sep 17 00:00:00 2001 From: Lorn Potter Date: Sat, 7 Sep 2019 07:06:30 +1000 Subject: wasm: refactor network to use fetch API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This has better support for threaded use, and gets rid of bind use. This requires emscripten 1.38.37 and above Task-number: QTBUG-76891 Change-Id: Ic30a6820c2ce945c314751c06cfc356914a71217 Reviewed-by: Morten Johan Sørvig --- src/network/access/qnetworkaccessmanager.cpp | 23 +- src/network/access/qnetworkreplywasmimpl.cpp | 311 ++++++++++----------------- src/network/access/qnetworkreplywasmimpl_p.h | 14 +- 3 files changed, 132 insertions(+), 216 deletions(-) (limited to 'src') diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index e7e6e6b1a4..9e0389e08d 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -1410,16 +1410,7 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera bool isLocalFile = req.url().isLocalFile(); QString scheme = req.url().scheme(); -#ifdef Q_OS_WASM - // Support http, https, and relateive urls - if (scheme == QLatin1String("http") || scheme == QLatin1String("https") || scheme.isEmpty()) { - QNetworkReplyWasmImpl *reply = new QNetworkReplyWasmImpl(this); - QNetworkReplyWasmImplPrivate *priv = reply->d_func(); - priv->manager = this; - priv->setup(op, req, outgoingData); - return reply; - } -#endif +#ifndef Q_OS_WASM // fast path for GET on file:// URLs // The QNetworkAccessFileBackend will right now only be used for PUT @@ -1506,7 +1497,7 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera } #endif } - +#endif QNetworkRequest request = req; if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() && outgoingData && !outgoingData->isSequential()) { @@ -1524,6 +1515,16 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookies)); } } +#ifdef Q_OS_WASM + // Support http, https, and relative urls + if (scheme == QLatin1String("http") || scheme == QLatin1String("https") || scheme.isEmpty()) { + QNetworkReplyWasmImpl *reply = new QNetworkReplyWasmImpl(this); + QNetworkReplyWasmImplPrivate *priv = reply->d_func(); + priv->manager = this; + priv->setup(op, request, outgoingData); + return reply; + } +#endif #if QT_CONFIG(http) // Since Qt 5 we use the new QNetworkReplyHttpImpl diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp index 8c0f9bdf55..af8b39bab6 100644 --- a/src/network/access/qnetworkreplywasmimpl.cpp +++ b/src/network/access/qnetworkreplywasmimpl.cpp @@ -50,180 +50,10 @@ #include #include -#include -#include +#include QT_BEGIN_NAMESPACE -using namespace emscripten; - -static void q_requestErrorCallback(val event) -{ - if (event.isNull() || event.isUndefined()) - return; - - val xhr = event["target"]; - if (xhr.isNull() || xhr.isUndefined()) - return; - - quintptr func = xhr["data-handler"].as(); - QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(func); - Q_ASSERT(reply); - - int statusCode = xhr["status"].as(); - - QString reasonStr = QString::fromStdString(xhr["statusText"].as()); - - reply->setReplyAttributes(func, statusCode, reasonStr); - - if (statusCode >= 400 && !reasonStr.isEmpty()) - reply->emitReplyError(reply->statusCodeFromHttp(statusCode, reply->request.url()), reasonStr); -} - -static void q_progressCallback(val event) -{ - if (event.isNull() || event.isUndefined()) - return; - - val xhr = event["target"]; - if (xhr.isNull() || xhr.isUndefined()) - return; - - QNetworkReplyWasmImplPrivate *reply = - reinterpret_cast(xhr["data-handler"].as()); - Q_ASSERT(reply); - - if (xhr["status"].as() < 400) - reply->emitDataReadProgress(event["loaded"].as(), event["total"].as()); -} - -static void q_loadCallback(val event) -{ - if (event.isNull() || event.isUndefined()) - return; - - val xhr = event["target"]; - if (xhr.isNull() || xhr.isUndefined()) - return; - - QNetworkReplyWasmImplPrivate *reply = - reinterpret_cast(xhr["data-handler"].as()); - Q_ASSERT(reply); - - int status = xhr["status"].as(); - if (status >= 300) { - q_requestErrorCallback(event); - return; - } - QString statusText = QString::fromStdString(xhr["statusText"].as()); - int readyState = xhr["readyState"].as(); - - if (status == 200 || status == 203) { - QString responseString; - const std::string responseType = xhr["responseType"].as(); - if (responseType.length() == 0 || responseType == "document" || responseType == "text") { - responseString = QString::fromStdWString(xhr["responseText"].as()); - } else if (responseType == "json") { - responseString = - QString::fromStdWString(val::global("JSON").call("stringify", xhr["response"])); - } else if (responseType == "arraybuffer" || responseType == "blob") { - // handle this data in the FileReader, triggered by the call to readAsArrayBuffer - val blob = xhr["response"]; - if (blob.isNull() || blob.isUndefined()) - return; - - val reader = val::global("FileReader").new_(); - if (reader.isNull() || reader.isUndefined()) - return; - - reader.set("onload", val::module_property("qt_QNetworkReplyWasmImplPrivate_readBinary")); - reader.set("data-handler", xhr["data-handler"]); - - reader.call("readAsArrayBuffer", blob); - val::global("Module").delete_(reader); - } - - - if (readyState == 4) { // done - reply->setReplyAttributes(xhr["data-handler"].as(), status, statusText); - if (!responseString.isEmpty()) { - QByteArray responseStringArray = responseString.toUtf8(); - reply->dataReceived(responseStringArray, responseStringArray.size()); - } - } - } - if (status >= 400 && !statusText.isEmpty()) - reply->emitReplyError(reply->statusCodeFromHttp(status, reply->request.url()), statusText); -} - -static void q_responseHeadersCallback(val event) -{ - if (event.isNull() || event.isUndefined()) - return; - - val xhr = event["target"]; - if (xhr.isNull() || xhr.isUndefined()) - return; - - if (xhr["readyState"].as() == 2) { // HEADERS_RECEIVED - std::string responseHeaders = xhr.call("getAllResponseHeaders"); - if (!responseHeaders.empty()) { - QNetworkReplyWasmImplPrivate *reply = - reinterpret_cast(xhr["data-handler"].as()); - Q_ASSERT(reply); - - reply->headersReceived(QString::fromStdString(responseHeaders)); - } - } -} - -static void q_readBinary(val event) -{ - if (event.isNull() || event.isUndefined()) - return; - - val fileReader = event["target"]; - if (fileReader.isNull() || fileReader.isUndefined()) - return; - - QNetworkReplyWasmImplPrivate *reply = - reinterpret_cast(fileReader["data-handler"].as()); - Q_ASSERT(reply); - - if (reply->state == QNetworkReplyPrivate::Finished || reply->state == QNetworkReplyPrivate::Aborted) - return; - - // Set up source typed array - val result = fileReader["result"]; // ArrayBuffer - if (result.isNull() || result.isUndefined()) - return; - - val Uint8Array = val::global("Uint8Array"); - val sourceTypedArray = Uint8Array.new_(result); - - // Allocate and set up destination typed array - const quintptr size = result["byteLength"].as(); - QByteArray buffer(size, Qt::Uninitialized); - - val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"], - reinterpret_cast(buffer.data()), size); - destinationTypedArray.call("set", sourceTypedArray); - reply->dataReceived(buffer, buffer.size()); - - event.delete_(fileReader); - Uint8Array.delete_(sourceTypedArray); - - QCoreApplication::processEvents(); -} - -EMSCRIPTEN_BINDINGS(qtNetworkModule) { - function("qt_QNetworkReplyWasmImplPrivate_requestErrorCallback", q_requestErrorCallback); - function("qt_QNetworkReplyWasmImplPrivate_progressCallback", q_progressCallback); - function("qt_QNetworkReplyWasmImplPrivate_loadCallback", q_loadCallback); - function("qt_QNetworkReplyWasmImplPrivate_responseHeadersCallback", q_responseHeadersCallback); - function("qt_QNetworkReplyWasmImplPrivate_readBinary", q_readBinary); -} - QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate() : QNetworkReplyPrivate() , managerPrivate(0) @@ -236,11 +66,7 @@ QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate() QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate() { - m_xhr.set("onerror", val::null()); - m_xhr.set("onload", val::null()); - m_xhr.set("onprogress", val::null()); - m_xhr.set("onreadystatechange", val::null()); - m_xhr.set("data-handler", val::null()); + emscripten_fetch_close(m_fetch); } QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent) @@ -373,7 +199,11 @@ void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusC void QNetworkReplyWasmImplPrivate::doAbort() const { - m_xhr.call("abort"); + emscripten_fetch_close(m_fetch); +} + +constexpr int getArraySize (int factor) { + return 2 * factor + 1; } void QNetworkReplyWasmImplPrivate::doSendRequest() @@ -381,38 +211,68 @@ void QNetworkReplyWasmImplPrivate::doSendRequest() Q_Q(QNetworkReplyWasmImpl); totalDownloadSize = 0; - m_xhr = val::global("XMLHttpRequest").new_(); - std::string verb = q->methodName().toStdString(); + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, q->methodName().constData()); - m_xhr.call("open", verb, request.url().toString().toStdString()); + QList headersData = request.rawHeaderList(); + int arrayLength = getArraySize(headersData.count()); - m_xhr.set("onerror", val::module_property("qt_QNetworkReplyWasmImplPrivate_requestErrorCallback")); - m_xhr.set("onload", val::module_property("qt_QNetworkReplyWasmImplPrivate_loadCallback")); - m_xhr.set("onprogress", val::module_property("qt_QNetworkReplyWasmImplPrivate_progressCallback")); - m_xhr.set("onreadystatechange", val::module_property("qt_QNetworkReplyWasmImplPrivate_responseHeadersCallback")); + if (headersData.count() > 0) { + const char* customHeaders[arrayLength]; + int i = 0; + for (i; i < headersData.count() * 2; (i = i + 2)) { + customHeaders[i] = headersData[i].constData(); + customHeaders[i + 1] = request.rawHeader(headersData[i]).constData(); + } + customHeaders[i] = nullptr; + attr.requestHeaders = customHeaders; + } - m_xhr.set("data-handler", val(quintptr(reinterpret_cast(this)))); + if (outgoingData) { // data from post request + // handle extra data + QByteArray extraData; + extraData = outgoingData->readAll(); // is there a size restriction here? + if (!extraData.isEmpty()) { + attr.requestData = extraData.constData(); + attr.requestDataSize = extraData.size(); + } + } - QByteArray contentType = request.rawHeader("Content-Type"); + // username & password + if (!request.url().userInfo().isEmpty()) { + attr.userName = request.url().userName().toUtf8(); + attr.password = request.url().password().toUtf8(); + } - // handle extra data - val dataToSend = val::null(); - QByteArray extraData; + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE | EMSCRIPTEN_FETCH_REPLACE; - if (outgoingData) // data from post request - extraData = outgoingData->readAll(); + QNetworkRequest::CacheLoadControl CacheLoadControlAttribute = + (QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt(); - if (!extraData.isEmpty()) { - dataToSend = val(typed_memory_view(extraData.size(), - reinterpret_cast - (extraData.constData()))); + if (CacheLoadControlAttribute == QNetworkRequest::AlwaysCache) { + attr.attributes += EMSCRIPTEN_FETCH_NO_DOWNLOAD; } - m_xhr.set("responseType", val("blob")); - // set request headers - for (auto header : request.rawHeaderList()) { - m_xhr.call("setRequestHeader", header.toStdString(), request.rawHeader(header).toStdString()); + if (CacheLoadControlAttribute == QNetworkRequest::PreferCache) { + attr.attributes += EMSCRIPTEN_FETCH_APPEND; } - m_xhr.call("send", dataToSend); + + if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork || + request.attribute(QNetworkRequest::CacheSaveControlAttribute, false).toBool()) { + attr.attributes -= EMSCRIPTEN_FETCH_PERSIST_FILE; + } + + attr.onsuccess = QNetworkReplyWasmImplPrivate::downloadSucceeded; + attr.onerror = QNetworkReplyWasmImplPrivate::downloadFailed; + attr.onprogress = QNetworkReplyWasmImplPrivate::downloadProgress; + attr.onreadystatechange = QNetworkReplyWasmImplPrivate::stateChange; + attr.timeoutMSecs = 2 * 6000; // FIXME + attr.userData = reinterpret_cast(this); + + QString dPath = QStringLiteral("/home/web_user/") + request.url().fileName(); + attr.destinationPath = dPath.toUtf8(); + + m_fetch = emscripten_fetch(&attr, request.url().toString().toUtf8()); } void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString) @@ -511,7 +371,6 @@ void QNetworkReplyWasmImplPrivate::headersReceived(const QString &bufferString) if (!bufferString.isEmpty()) { QStringList headers = bufferString.split(QString::fromUtf8("\r\n"), Qt::SkipEmptyParts); - for (int i = 0; i < headers.size(); i++) { QString headerName = headers.at(i).split(QString::fromUtf8(": ")).at(0); QString headersValue = headers.at(i).split(QString::fromUtf8(": ")).at(1); @@ -589,6 +448,56 @@ void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData() } } +void QNetworkReplyWasmImplPrivate::downloadSucceeded(emscripten_fetch_t *fetch) +{ + QByteArray buffer(fetch->data, fetch->numBytes); + + QNetworkReplyWasmImplPrivate *reply = + reinterpret_cast(fetch->userData); + if (reply) { + reply->dataReceived(buffer, buffer.size()); + } +} + +void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch) +{ + if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) { + size_t headerLength = emscripten_fetch_get_response_headers_length(fetch); + char *dst = nullptr; + emscripten_fetch_get_response_headers(fetch, dst, headerLength + 1); + std::string str = dst; + QNetworkReplyWasmImplPrivate *reply = + reinterpret_cast(fetch->userData); + reply->headersReceived(QString::fromStdString(str)); + } +} + +void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch) +{ + QNetworkReplyWasmImplPrivate *reply = + reinterpret_cast(fetch->userData); + Q_ASSERT(reply); + + if (fetch->status < 400) + reply->emitDataReadProgress((fetch->dataOffset + fetch->numBytes), fetch->totalBytes); +} + +void QNetworkReplyWasmImplPrivate::downloadFailed(emscripten_fetch_t *fetch) +{ + QNetworkReplyWasmImplPrivate *reply = reinterpret_cast(fetch->userData); + Q_ASSERT(reply); + + QString reasonStr = QString::fromUtf8(fetch->statusText); + + reply->setReplyAttributes(reinterpret_cast(fetch->userData), fetch->status, reasonStr); + + if (fetch->status >= 400 && !reasonStr.isEmpty()) + reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()), reasonStr); + + if (fetch->status >= 400) + emscripten_fetch_close(fetch); // Also free data on failure. +} + //taken from qhttpthreaddelegate.cpp QNetworkReply::NetworkError QNetworkReplyWasmImplPrivate::statusCodeFromHttp(int httpStatusCode, const QUrl &url) { diff --git a/src/network/access/qnetworkreplywasmimpl_p.h b/src/network/access/qnetworkreplywasmimpl_p.h index e1e6bf4e24..5463ce8a98 100644 --- a/src/network/access/qnetworkreplywasmimpl_p.h +++ b/src/network/access/qnetworkreplywasmimpl_p.h @@ -61,8 +61,7 @@ #include #include -#include -#include +#include QT_BEGIN_NAMESPACE @@ -135,10 +134,17 @@ public: QIODevice *outgoingData; QSharedPointer outgoingDataBuffer; - emscripten::val m_xhr = emscripten::val::null(); - void doAbort() const; + void doAbort() const; + + static void downloadProgress(emscripten_fetch_t *fetch); + static void downloadFailed(emscripten_fetch_t *fetch); + static void downloadSucceeded(emscripten_fetch_t *fetch); + static void stateChange(emscripten_fetch_t *fetch); static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url); + + emscripten_fetch_t *m_fetch; + Q_DECLARE_PUBLIC(QNetworkReplyWasmImpl) }; -- cgit v1.2.3