summaryrefslogtreecommitdiffstats
path: root/src/network/access
diff options
context:
space:
mode:
authorLorn Potter <lorn.potter@gmail.com>2018-09-28 10:51:54 +1000
committerLorn Potter <lorn.potter@gmail.com>2018-10-09 20:44:24 +0000
commit49f1f944bcd72586cc76622fd42c904974506f91 (patch)
tree5020010c66e8341907b7461ca8cf09f584f98025 /src/network/access
parentb7c5c2e65bd00d2b6729a54ae7ba5ddd4e891a03 (diff)
wasm: rewrite QNetworkReplyWasmImpl to remove EM_ASM
and fix handling of incoming binary data Change-Id: I31e97505ad4ff64cf8e380df5d0d6b70c3cd60b0 Reviewed-by: Ryan Chu <ryan.chu@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'src/network/access')
-rw-r--r--src/network/access/qnetworkreplywasmimpl.cpp382
-rw-r--r--src/network/access/qnetworkreplywasmimpl_p.h16
2 files changed, 183 insertions, 215 deletions
diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp
index 9c2ff8fb89..23ca62acd4 100644
--- a/src/network/access/qnetworkreplywasmimpl.cpp
+++ b/src/network/access/qnetworkreplywasmimpl.cpp
@@ -49,10 +49,135 @@
#include <private/qnetworkaccessmanager_p.h>
#include <private/qnetworkfile_p.h>
-#include <iostream>
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
+using namespace emscripten;
+
+static void q_requestErrorCallback(val event)
+{
+ val xhr = event["target"];
+
+ quintptr func = xhr["data-handler"].as<quintptr>();
+ QNetworkReplyWasmImplPrivate *reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(func);
+ Q_ASSERT(reply);
+
+ int statusCode = xhr["status"].as<int>();
+
+ QString reasonStr = QString::fromStdString(xhr["statusText"].as<std::string>());
+
+ 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)
+{
+ val xhr = event["target"];
+
+ QNetworkReplyWasmImplPrivate *reply =
+ reinterpret_cast<QNetworkReplyWasmImplPrivate*>(xhr["data-handler"].as<quintptr>());
+ Q_ASSERT(reply);
+
+ if (xhr["lengthComputable"].as<bool>() && xhr["status"].as<int>() < 400)
+ reply->emitDataReadProgress(xhr["loaded"].as<qint64>(), xhr["total"].as<qint64>());
+
+}
+
+static void q_loadCallback(val event)
+{
+ val xhr = event["target"];
+
+ QNetworkReplyWasmImplPrivate *reply =
+ reinterpret_cast<QNetworkReplyWasmImplPrivate*>(xhr["data-handler"].as<quintptr>());
+ Q_ASSERT(reply);
+
+ int status = xhr["status"].as<int>();
+ if (status >= 300) {
+ q_requestErrorCallback(event);
+ return;
+ }
+ QString statusText = QString::fromStdString(xhr["statusText"].as<std::string>());
+ if (status == 200 || status == 203) {
+ QString responseString;
+ const std::string responseType = xhr["responseType"].as<std::string>();
+ if (responseType.length() == 0 || responseType == "document" || responseType == "text") {
+ responseString = QString::fromStdWString(xhr["responseText"].as<std::wstring>());
+ } else if (responseType == "json") {
+ responseString =
+ QString::fromStdWString(val::global("JSON").call<std::wstring>("stringify", xhr["response"]));
+ } else if (responseType == "arraybuffer" || responseType == "blob") {
+ // handle this data in the FileReader, triggered by the call to readAsArrayBuffer
+ val reader = val::global("FileReader").new_();
+ reader.set("onload", val::module_property("QNetworkReplyWasmImplPrivate_readBinary"));
+ reader.set("data-handler", xhr["data-handler"]);
+ reader.call<void>("readAsArrayBuffer", xhr["response"]);
+ }
+
+ int readyState = xhr["readyState"].as<int>();
+
+ if (readyState == 4) { // done
+ reply->setReplyAttributes(xhr["data-handler"].as<quintptr>(), status, statusText);
+ if (!responseString.isEmpty())
+ reply->dataReceived(responseString.toUtf8(), responseString.size());
+ }
+ }
+ if (status >= 400 && !statusText.isEmpty())
+ reply->emitReplyError(reply->statusCodeFromHttp(status, reply->request.url()), statusText);
+}
+
+static void q_responseHeadersCallback(val event)
+{
+ val xhr = event["target"];
+
+ if (xhr["readyState"].as<int>() == 2) { // HEADERS_RECEIVED
+ std::string responseHeaders = xhr.call<std::string>("getAllResponseHeaders");
+ if (!responseHeaders.empty()) {
+ QNetworkReplyWasmImplPrivate *reply =
+ reinterpret_cast<QNetworkReplyWasmImplPrivate*>(xhr["data-handler"].as<quintptr>());
+ Q_ASSERT(reply);
+
+ reply->headersReceived(QString::fromStdString(responseHeaders));
+ }
+ }
+}
+
+static void q_readBinary(val event)
+{
+ val fileReader = event["target"];
+
+ QNetworkReplyWasmImplPrivate *reply =
+ reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fileReader["data-handler"].as<quintptr>());
+ Q_ASSERT(reply);
+
+ // Set up source typed array
+ val result = fileReader["result"]; // ArrayBuffer
+ val Uint8Array = val::global("Uint8Array");
+ val sourceTypedArray = Uint8Array.new_(result);
+
+ // Allocate and set up destination typed array
+ const quintptr size = result["byteLength"].as<quintptr>();
+ QByteArray buffer(size, Qt::Uninitialized);
+
+ val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"],
+ reinterpret_cast<quintptr>(buffer.data()), size);
+ destinationTypedArray.call<void>("set", sourceTypedArray);
+ reply->dataReceived(buffer, buffer.size());
+}
+
+
+EMSCRIPTEN_BINDINGS(network_module) {
+ function("QNetworkReplyWasmImplPrivate_requestErrorCallback", q_requestErrorCallback);
+ function("QNetworkReplyWasmImplPrivate_progressCallback", q_progressCallback);
+ function("QNetworkReplyWasmImplPrivate_loadCallback", q_loadCallback);
+ function("QNetworkReplyWasmImplPrivate_responseHeadersCallback", q_responseHeadersCallback);
+ function("QNetworkReplyWasmImplPrivate_readBinary", q_readBinary);
+}
+
QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate()
: QNetworkReplyPrivate()
, managerPrivate(0)
@@ -172,226 +297,80 @@ void QNetworkReplyWasmImplPrivate::setup(QNetworkAccessManager::Operation op, co
doSendRequest();
}
-void QNetworkReplyWasmImplPrivate::onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int buffer, int bufferSize)
-{
- QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
-
- const QString reasonStr = QString::fromUtf8(reinterpret_cast<char *>(statusReason));
-
- switch (readyState) {
- case 0://unsent
- break;
- case 1://opened
- break;
- case 2://headers received
- break;
- case 3://loading
- break;
- case 4: {//done
- handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
- if (!reasonStr.isEmpty())
- handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr);
-
- if (statusCode >= 400) {
- if (!reasonStr.isEmpty())
- handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr);
- } else {
- handler->dataReceived(reinterpret_cast<char *>(buffer), bufferSize);
- }
- }
- break;
- };
- }
-
-void QNetworkReplyWasmImplPrivate::onProgressCallback(void* data, int bytesWritten, int total, uint timestamp)
-{
- Q_UNUSED(timestamp);
-
- QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
- handler->emitDataReadProgress(bytesWritten, total);
-}
-
-void QNetworkReplyWasmImplPrivate::onRequestErrorCallback(void* data, int statusCode, int statusReason)
+void QNetworkReplyWasmImplPrivate::setReplyAttributes(quintptr data, int statusCode, const QString &statusReason)
{
- QString reasonStr = QString::fromUtf8(reinterpret_cast<char *>(statusReason));
-
QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
+ Q_ASSERT(handler);
handler->q_func()->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
- if (!reasonStr.isEmpty())
- handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonStr);
-
- if (statusCode >= 400) {
- if (!reasonStr.isEmpty())
- handler->emitReplyError(handler->statusCodeFromHttp(statusCode, handler->request.url()), reasonStr);
- }
-}
-
-void QNetworkReplyWasmImplPrivate::onResponseHeadersCallback(void* data, int headers)
-{
- QNetworkReplyWasmImplPrivate *handler = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(data);
- handler->headersReceived(reinterpret_cast<char *>(headers));
+ if (!statusReason.isEmpty())
+ handler->q_func()->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, statusReason);
}
void QNetworkReplyWasmImplPrivate::doSendRequest()
{
Q_Q(QNetworkReplyWasmImpl);
totalDownloadSize = 0;
- jsRequest(QString::fromUtf8(q->methodName()), // GET POST
- request.url().toString(),
- (void *)&onLoadCallback,
- (void *)&onProgressCallback,
- (void *)&onRequestErrorCallback,
- (void *)&onResponseHeadersCallback);
-}
-
-/* const QString &body, const QList<QPair<QByteArray, QByteArray> > &headers ,*/
-void QNetworkReplyWasmImplPrivate::jsRequest(const QString &verb, const QString &url,
- void *loadCallback, void *progressCallback,
- void *errorCallback, void *onResponseHeadersCallback)
-{
- QString extraDataString;
-
- QByteArray extraData;
- if (outgoingData)
- extraData = outgoingData->readAll();
- if (extraData.size() > 0)
- extraDataString.fromUtf8(extraData);
+ val xhr = val::global("XMLHttpRequest").new_();
+ std::string verb = q->methodName().toStdString();
- if (extraDataString.size() >= 0 && verb == QStringLiteral("POST") && extraDataString.startsWith(QStringLiteral("?")))
- extraDataString.remove(QStringLiteral("?"));
+ QUrl url;
+ QString extraDataString;
- // Probably a good idea to save any shared pointers as members in C++
- // so the objects they point to survive as long as you need them
+ if (request.url().hasQuery()) { //strip query from url
+ extraDataString = request.url().query(QUrl::FullyEncoded);
+ QString urlStr = request.url().toString();
+ url.setUrl(urlStr.left(urlStr.indexOf("?")));
+ } else {
+ url = request.url();
+ }
+ xhr.call<void>("open", verb, url.toString().toStdString());
- QStringList headersList;
- for (auto header : request.rawHeaderList())
- headersList << QString::fromUtf8(header + ":" + request.rawHeader(header));
+ xhr.set("onerror", val::module_property("QNetworkReplyWasmImplPrivate_requestErrorCallback"));
+ xhr.set("onload", val::module_property("QNetworkReplyWasmImplPrivate_loadCallback"));
+ xhr.set("onprogress", val::module_property("QNetworkReplyWasmImplPrivate_progressCallback"));
+ xhr.set("onreadystatechange", val::module_property("QNetworkReplyWasmImplPrivate_responseHeadersCallback"));
- EM_ASM_ARGS({
- var verb = Pointer_stringify($0);
- var url = Pointer_stringify($1);
- var onLoadCallbackPointer = $2;
- var onProgressCallbackPointer = $3;
- var onErrorCallbackPointer = $4;
- var onHeadersCallback = $5;
- var handler = $8;
+ xhr.set("data-handler", val(quintptr(reinterpret_cast<void *>(this))));
- var dataToSend;
- var extraRequestData = Pointer_stringify($6); // request parameters
- var headersData = Pointer_stringify($7);
+ QByteArray contentType = request.rawHeader("Content-Type");
- var xhr;
- xhr = new XMLHttpRequest();
- xhr.responseType = 'arraybuffer';
+ // handle extra data
+ val dataToSend = val::null();
+ QByteArray extraData;
- xhr.open(verb, url, true); //async
+ if (outgoingData) // data from post request
+ extraData = outgoingData->readAll();
- function handleError(xhrStatusCode, xhrStatusText) {
- var errorPtr = allocate(intArrayFromString(xhrStatusText), 'i8', ALLOC_NORMAL);
- Runtime.dynCall('viii', onErrorCallbackPointer, [handler, xhrStatusCode, errorPtr]);
- _free(errorPtr);
+ if (contentType.contains("text") ||
+ contentType.contains("json") ||
+ contentType.contains("form")) {
+ if (extraData.size() > 0)
+ extraDataString.fromUtf8(extraData);
+ }
+ if (contentType.contains("json")) {
+ if (!extraDataString.isEmpty()) {
+ xhr.set("responseType", val("json"));
+ dataToSend = val(extraDataString.toStdString());
}
+ }
+ if (contentType.contains("form")) { //construct form data
+ if (!extraDataString.isEmpty()) {
+ val formData = val::global("FormData").new_();
+ QStringList formList = extraDataString.split('&');
- if (headersData) {
- var headers = headersData.split("&");
- for (var i = 0; i < headers.length; i++) {
- var header = headers[i].split(":")[0];
- var value = headers[i].split(":")[1];
-
- if (verb === 'POST' && value.toLowerCase().includes('json')) {
- if (extraRequestData) {
- xhr.responseType = 'json';
- dataToSend = extraRequestData;
- }
- }
- if (verb === 'POST' && value.toLowerCase().includes('form')) {
- if (extraRequestData) {
- var formData = new FormData();
- var extra = extraRequestData.split("&");
- for (var i = 0; i < extra.length; i++) {
- formData.append(extra[i].split("=")[0],extra[i].split("=")[1]);
- }
- dataToSend = formData;
- }
- }
- xhr.setRequestHeader(header, value);
+ for (auto formEntry : formList) {
+ formData.call<void>("append", formEntry.split('=')[0].toStdString(), formEntry.split('=')[1].toStdString());
}
+ dataToSend = formData;
}
-
- xhr.onprogress = function(e) {
- switch (xhr.status) {
- case 200:
- case 206:
- case 300:
- case 301:
- case 302: {
- var date = xhr.getResponseHeader('Last-Modified');
- date = ((date != null) ? new Date(date).getTime() / 1000 : 0);
- Runtime.dynCall('viiii', onProgressCallbackPointer, [handler, e.loaded, e.total, date]);
- }
- break;
- }
- };
-
- xhr.onreadystatechange = function() {
- if (this.readyState == this.HEADERS_RECEIVED) {
- var responseStr = this.getAllResponseHeaders();
- if (responseStr.length > 0) {
- var ptr = allocate(intArrayFromString(responseStr), 'i8', ALLOC_NORMAL);
- Runtime.dynCall('vii', onHeadersCallback, [handler, ptr]);
- _free(ptr);
- }
- }
- };
-
- xhr.onload = function(e) {
- if (xhr.status >= 300) { //error
- handleError(xhr.status, xhr.statusText);
- } else {
- if (this.status == 200 || this.status == 203) {
- var datalength;
- var byteArray = 0;
- var buffer;
- if (this.responseType.length === 0 || this.responseType === 'document') {
- byteArray = new Uint8Array(this.responseText);
- } else if (this.responseType === 'json') {
- var jsonResponse = JSON.stringify(this.response);
- buffer = allocate(intArrayFromString(jsonResponse), 'i8', ALLOC_NORMAL);
- datalength = jsonResponse.length;
- } else if (this.responseType === 'arraybuffer') {
- byteArray = new Uint8Array(xhr.response);
- }
- if (byteArray != 0 ) {
- datalength = byteArray.length;
- buffer = _malloc(datalength);
- HEAPU8.set(byteArray, buffer);
- }
- var reasonPtr = allocate(intArrayFromString(this.statusText), 'i8', ALLOC_NORMAL);
- Runtime.dynCall('viiiiii', onLoadCallbackPointer, [handler, this.status, reasonPtr, this.readyState, buffer, datalength]);
- _free(buffer);
- _free(reasonPtr);
- }
- }
- };
-
- xhr.onerror = function(e) {
- handleError(xhr.status, xhr.statusText);
- };
- //TODO other operations, handle user/pass, handle binary data, data streaming
- xhr.send(dataToSend);
-
- }, verb.toLatin1().data(),
- url.toLatin1().data(),
- loadCallback,
- progressCallback,
- errorCallback,
- onResponseHeadersCallback,
- extraDataString.size() > 0 ? extraDataString.toLatin1().data() : extraData.data(),
- headersList.join(QStringLiteral("&")).toLatin1().data(),
- this
- );
+ }
+ // set request headers
+ for (auto header : request.rawHeaderList()) {
+ xhr.call<void>("setRequestHeader", header.toStdString(), request.rawHeader(header).toStdString());
+ }
+ xhr.call<void>("send", dataToSend);
}
void QNetworkReplyWasmImplPrivate::emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString)
@@ -414,10 +393,10 @@ void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qi
percentFinished = (bytesReceived / bytesTotal) * 100;
- emit q->downloadProgress(bytesReceived, totalDownloadSize);
+ emit q->downloadProgress(bytesReceived, bytesTotal);
}
-void QNetworkReplyWasmImplPrivate::dataReceived(char *buffer, int bufferSize)
+void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bufferSize)
{
Q_Q(QNetworkReplyWasmImpl);
@@ -481,11 +460,10 @@ static int parseHeaderName(const QByteArray &headerName)
}
-void QNetworkReplyWasmImplPrivate::headersReceived(char *buffer)
+void QNetworkReplyWasmImplPrivate::headersReceived(const QString &bufferString)
{
Q_Q(QNetworkReplyWasmImpl);
- QString bufferString = QString::fromUtf8(buffer);
if (!bufferString.isEmpty()) {
QStringList headers = bufferString.split(QString::fromUtf8("\r\n"), QString::SkipEmptyParts);
diff --git a/src/network/access/qnetworkreplywasmimpl_p.h b/src/network/access/qnetworkreplywasmimpl_p.h
index a707390503..69c90de41a 100644
--- a/src/network/access/qnetworkreplywasmimpl_p.h
+++ b/src/network/access/qnetworkreplywasmimpl_p.h
@@ -95,7 +95,6 @@ public:
private:
QByteArray methodName() const;
-
};
class QNetworkReplyWasmImplPrivate: public QNetworkReplyPrivate
@@ -106,19 +105,12 @@ public:
QNetworkAccessManagerPrivate *managerPrivate;
void doSendRequest();
-
- void jsRequest(const QString &verb, const QString &url, void *, void *, void *, void *);
-
- static void onLoadCallback(void *data, int statusCode, int statusReason, int readyState, int textBuffer, int size);
- static void onProgressCallback(void *data, int done, int bytesTotal, uint timestamp);
- static void onRequestErrorCallback(void *data, int statusCode, int statusReason);
- static void onStateChangedCallback(int status);
- static void onResponseHeadersCallback(void *data, int headers);
+ static void setReplyAttributes(quintptr data, int statusCode, const QString &statusReason);
void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &);
void emitDataReadProgress(qint64 done, qint64 total);
- void dataReceived(char *buffer, int bufferSize);
- void headersReceived(char *buffer);
+ void dataReceived(const QByteArray &buffer, int bufferSize);
+ void headersReceived(const QString &bufferString);
void setup(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
QIODevice *outgoingData);
@@ -148,6 +140,4 @@ public:
QT_END_NAMESPACE
-//Q_DECLARE_METATYPE(QNetworkRequest::KnownHeaders)
-
#endif // QNETWORKREPLYWASMIMPL_H