From 6bf91e10e5e54400a86ea2d60979960ba5bca3c8 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Wed, 14 Jun 2023 19:32:01 +0200 Subject: [PATCH] Network: Implement WebService with emscripten fetch API on wasm --- examples/WebServices/main.cpp | 6 +- include/Nazara/Network/Network.hpp | 2 + include/Nazara/Network/WebRequest.hpp | 35 ++++-- include/Nazara/Network/WebRequest.inl | 36 +++++- include/Nazara/Network/WebRequestResult.hpp | 29 ++++- include/Nazara/Network/WebRequestResult.inl | 26 +++-- include/Nazara/Network/WebService.hpp | 21 ++++ include/Nazara/Network/WebService.inl | 2 + src/Nazara/Network/Network.cpp | 15 ++- src/Nazara/Network/WebRequest.cpp | 116 +++++++++++++++++--- src/Nazara/Network/WebRequestResult.cpp | 38 +++++-- src/Nazara/Network/WebService.cpp | 92 +++++++++++++++- xmake.lua | 25 +++-- 13 files changed, 369 insertions(+), 74 deletions(-) diff --git a/examples/WebServices/main.cpp b/examples/WebServices/main.cpp index 5ddcbb363..70800f99f 100644 --- a/examples/WebServices/main.cpp +++ b/examples/WebServices/main.cpp @@ -8,15 +8,15 @@ int main() std::unique_ptr webService = Nz::Network::Instance()->InstantiateWebService(); - std::unique_ptr webRequest = webService->CreateGetRequest("https://www.perdu.com", [&](const Nz::WebRequestResult& result) + std::unique_ptr webRequest = webService->CreateGetRequest("https://test.digitalpulse.software", [&](const Nz::WebRequestResult& result) { if (result) { - std::cout << "Got a " << result.GetReponseCode() << " response(" << result.GetDownloadedSize() << " bytes, " << result.GetDownloadSpeed() << " bytes / s" << ")" << std::endl; + std::cout << "Got a " << result.GetStatusCode() << " response(" << result.GetDownloadedSize() << " bytes, " << result.GetDownloadSpeed() << " bytes / s" << ")" << std::endl; std::cout << result.GetBody() << std::endl; } else - std::cout << "Web request failed (code " << result.GetReponseCode() << "): " << result.GetErrorMessage() << std::endl; + std::cout << "Web request failed (code " << result.GetStatusCode() << "): " << result.GetErrorMessage() << std::endl; Nz::ApplicationBase::Instance()->Quit(); }); diff --git a/include/Nazara/Network/Network.hpp b/include/Nazara/Network/Network.hpp index c90d09906..7d8338aeb 100644 --- a/include/Nazara/Network/Network.hpp +++ b/include/Nazara/Network/Network.hpp @@ -37,7 +37,9 @@ namespace Nz }; private: +#ifndef NAZARA_PLATFORM_WEB std::unique_ptr m_curlLibrary; +#endif static Network* s_instance; }; diff --git a/include/Nazara/Network/WebRequest.hpp b/include/Nazara/Network/WebRequest.hpp index 534428fbb..8f3483b8e 100644 --- a/include/Nazara/Network/WebRequest.hpp +++ b/include/Nazara/Network/WebRequest.hpp @@ -7,6 +7,8 @@ #ifndef NAZARA_NETWORK_WEBREQUEST_HPP #define NAZARA_NETWORK_WEBREQUEST_HPP +#include +#include #include #include #include @@ -15,7 +17,11 @@ #include #include +#ifndef NAZARA_PLATFORM_WEB struct curl_slist; +#else +struct emscripten_fetch_attr_t; +#endif namespace Nz { @@ -38,10 +44,10 @@ namespace Nz inline void SetDataCallback(DataCallback callback); inline void SetHeader(std::string header, std::string value); - void SetJSonContent(std::string_view encodedJSon); + void SetJSonContent(std::string encodedJSon); void SetMaximumFileSize(UInt64 maxFileSize); inline void SetResultCallback(ResultCallback callback); - void SetServiceName(const std::string_view& serviceName); + void SetServiceName(std::string serviceName); void SetURL(const std::string& url); void SetupGet(); @@ -50,21 +56,36 @@ namespace Nz WebRequest& operator=(const WebRequest&) = delete; WebRequest& operator=(WebRequest&&) = default; - static std::unique_ptr Get(const std::string& url, ResultCallback callback = nullptr); - static std::unique_ptr Post(const std::string& url, ResultCallback callback = nullptr); - private: inline bool OnBodyResponse(const char* data, std::size_t length); +#ifndef NAZARA_PLATFORM_WEB CURL* Prepare(); - inline void TriggerCallback(); - inline void TriggerCallback(std::string errorMessage); +#else + inline emscripten_fetch_t* GetFetchHandle() const; + inline Nz::Time GetRequestTime() const; + emscripten_fetch_t* Prepare(emscripten_fetch_attr_t* fetchAttr); + inline void StopClock(); +#endif + inline void TriggerErrorCallback(std::string errorMessage); + inline void TriggerSuccessCallback(); +#ifdef NAZARA_PLATFORM_WEB + std::string m_httpMethod; + std::string m_url; + std::vector m_requestHeaders; +#endif + std::string m_content; std::string m_responseBody; std::unordered_map m_headers; WebService& m_webService; DataCallback m_dataCallback; +#ifndef NAZARA_PLATFORM_WEB MovablePtr m_curlHandle; MovablePtr m_headerList; +#else + HighPrecisionClock m_clock; + MovablePtr m_fetchHandle; +#endif ResultCallback m_resultCallback; bool m_isUserAgentSet; }; diff --git a/include/Nazara/Network/WebRequest.inl b/include/Nazara/Network/WebRequest.inl index 993af4594..af7cba358 100644 --- a/include/Nazara/Network/WebRequest.inl +++ b/include/Nazara/Network/WebRequest.inl @@ -32,16 +32,42 @@ namespace Nz return m_dataCallback(data, length); } - inline void WebRequest::TriggerCallback() +#ifdef NAZARA_PLATFORM_WEB + inline emscripten_fetch_t* WebRequest::GetFetchHandle() const { - m_resultCallback(WebRequestResult(m_webService, m_curlHandle.Get(), std::move(m_responseBody))); - m_responseBody.clear(); + return m_fetchHandle; } - inline void WebRequest::TriggerCallback(std::string errorMessage) + inline Time WebRequest::GetRequestTime() const { - m_resultCallback(WebRequestResult(m_webService, std::move(errorMessage))); + return m_clock.GetElapsedTime(); + } + + inline void WebRequest::StopClock() + { + m_clock.Pause(); + } +#endif + + inline void WebRequest::TriggerErrorCallback(std::string errorMessage) + { +#ifndef NAZARA_PLATFORM_WEB + m_resultCallback(WebRequestResult(m_webService, Nz::Err(std::move(errorMessage)), m_curlHandle.Get())); +#else + m_resultCallback(WebRequestResult(m_webService, Nz::Err(std::move(errorMessage)), m_fetchHandle.Get(), m_clock.GetElapsedTime())); +#endif + } + + inline void WebRequest::TriggerSuccessCallback() + { +#ifndef NAZARA_PLATFORM_WEB + m_resultCallback(WebRequestResult(m_webService, Nz::Ok(std::move(m_responseBody)), m_curlHandle.Get())); +#else + m_resultCallback(WebRequestResult(m_webService, Nz::Ok(std::move(m_responseBody)), m_fetchHandle.Get(), m_clock.GetElapsedTime())); +#endif + m_responseBody.clear(); } } #include +#include "WebRequest.hpp" diff --git a/include/Nazara/Network/WebRequestResult.hpp b/include/Nazara/Network/WebRequestResult.hpp index ef61dbb6b..f56ba61c3 100644 --- a/include/Nazara/Network/WebRequestResult.hpp +++ b/include/Nazara/Network/WebRequestResult.hpp @@ -8,12 +8,18 @@ #define NAZARA_NETWORK_WEBREQUESTRESULT_HPP #include +#include #include #include +#include #include #include +#ifndef NAZARA_PLATFORM_WEB using CURL = void; +#else +struct emscripten_fetch_t; +#endif namespace Nz { @@ -30,10 +36,10 @@ namespace Nz inline std::string& GetBody(); inline const std::string& GetBody() const; - Nz::UInt64 GetDownloadedSize() const; - Nz::UInt64 GetDownloadSpeed() const; + UInt64 GetDownloadedSize() const; + UInt64 GetDownloadSpeed() const; inline const std::string& GetErrorMessage() const; - long GetReponseCode() const; + UInt32 GetStatusCode() const; inline bool HasSucceeded() const; @@ -43,12 +49,23 @@ namespace Nz WebRequestResult& operator=(WebRequestResult&&) = delete; private: - inline WebRequestResult(WebService& webService, CURL* curl, std::string body); - inline WebRequestResult(WebService& webService, std::string errMessage); +#ifndef NAZARA_PLATFORM_WEB + inline WebRequestResult(WebService& webService, Result&& bodyResult, CURL* curl); +#else + inline WebRequestResult(WebService& webService, Result&& bodyResult, emscripten_fetch_t* fetchHandle, Time downloadTime); +#endif +#ifndef NAZARA_PLATFORM_WEB CURL* m_curlHandle; +#else + emscripten_fetch_t* m_fetchHandle; +#endif WebService& m_webService; - std::string m_bodyOrErr; + Result m_bodyResult; +#ifdef NAZARA_PLATFORM_WEB + Time m_downloadTime; +#endif + }; } diff --git a/include/Nazara/Network/WebRequestResult.inl b/include/Nazara/Network/WebRequestResult.inl index 98f13615d..5c520e556 100644 --- a/include/Nazara/Network/WebRequestResult.inl +++ b/include/Nazara/Network/WebRequestResult.inl @@ -2,53 +2,55 @@ // This file is part of the "Nazara Engine - Network module" // For conditions of distribution and use, see copyright notice in Config.hpp -#include #include +#include namespace Nz { - inline WebRequestResult::WebRequestResult(WebService& webService, CURL* curl, std::string body) : +#ifndef NAZARA_PLATFORM_WEB + inline WebRequestResult::WebRequestResult(WebService& webService, Result&& bodyResult, CURL* curl) : m_curlHandle(curl), m_webService(webService), - m_bodyOrErr(std::move(body)) + m_bodyResult(std::move(bodyResult)) { } - - inline WebRequestResult::WebRequestResult(WebService& webService, std::string errMessage) : - m_curlHandle(nullptr), +#else + inline WebRequestResult::WebRequestResult(WebService& webService, Result&& bodyResult, emscripten_fetch_t* fetchHandle, Time downloadTime) : + m_fetchHandle(fetchHandle), m_webService(webService), - m_bodyOrErr(std::move(errMessage)) + m_bodyResult(std::move(bodyResult)), + m_downloadTime(downloadTime) { } +#endif inline std::string& WebRequestResult::GetBody() { assert(HasSucceeded()); - return m_bodyOrErr; + return m_bodyResult.GetValue(); } inline const std::string& WebRequestResult::GetBody() const { assert(HasSucceeded()); - return m_bodyOrErr; + return m_bodyResult.GetValue(); } inline const std::string& WebRequestResult::GetErrorMessage() const { assert(!HasSucceeded()); - return m_bodyOrErr; + return m_bodyResult.GetError(); } inline bool WebRequestResult::HasSucceeded() const { - return m_curlHandle != nullptr; + return m_bodyResult.IsOk(); } inline WebRequestResult::operator bool() const { return HasSucceeded(); } - } #include diff --git a/include/Nazara/Network/WebService.hpp b/include/Nazara/Network/WebService.hpp index 5237747aa..def54f65f 100644 --- a/include/Nazara/Network/WebService.hpp +++ b/include/Nazara/Network/WebService.hpp @@ -10,8 +10,12 @@ #include #include #include +#include +#include +#ifndef NAZARA_PLATFORM_WEB using CURLM = void; +#endif namespace Nz { @@ -24,7 +28,11 @@ namespace Nz friend WebRequestResult; public: +#ifndef NAZARA_PLATFORM_WEB WebService(const CurlLibrary& library); +#else + WebService(); +#endif WebService(const WebService&) = delete; WebService(WebService&&) = delete; ~WebService(); @@ -44,12 +52,25 @@ namespace Nz WebService& operator=(WebService&&) = delete; private: +#ifndef NAZARA_PLATFORM_WEB inline const CurlLibrary& GetCurlLibrary() const; +#endif std::string m_userAgent; +#ifndef NAZARA_PLATFORM_WEB std::unordered_map> m_activeRequests; const CurlLibrary& m_curl; MovablePtr m_curlMulti; +#else + struct FinishedRequest + { + std::unique_ptr request; + bool succeeded; + }; + + std::unordered_map> m_activeRequests; + std::vector m_finishedRequests; +#endif }; } diff --git a/include/Nazara/Network/WebService.inl b/include/Nazara/Network/WebService.inl index 9b274df32..c60f129d2 100644 --- a/include/Nazara/Network/WebService.inl +++ b/include/Nazara/Network/WebService.inl @@ -36,10 +36,12 @@ namespace Nz return m_userAgent; } +#ifndef NAZARA_PLATFORM_WEB const CurlLibrary& WebService::GetCurlLibrary() const { return m_curl; } +#endif } #include diff --git a/src/Nazara/Network/Network.cpp b/src/Nazara/Network/Network.cpp index 0709aa1a5..db85265a1 100644 --- a/src/Nazara/Network/Network.cpp +++ b/src/Nazara/Network/Network.cpp @@ -7,11 +7,15 @@ #include #include #include -#include #include #include #include + +#ifndef NAZARA_PLATFORM_WEB +#include +#endif + #if defined(NAZARA_PLATFORM_WINDOWS) #include #elif defined(NAZARA_PLATFORM_POSIX) @@ -42,18 +46,21 @@ namespace Nz if (!NetPacket::Initialize()) throw std::runtime_error("failed to initialize packets"); +#ifndef NAZARA_PLATFORM_WEB if (config.webServices) { m_curlLibrary = std::make_unique(); if (!m_curlLibrary->Load()) throw std::runtime_error("failed to initialize curl"); } +#endif } Network::~Network() { - // Uninitialize module here +#ifndef NAZARA_PLATFORM_WEB m_curlLibrary.reset(); +#endif NetPacket::Uninitialize(); SocketImpl::Uninitialize(); @@ -61,6 +68,7 @@ namespace Nz std::unique_ptr Network::InstantiateWebService() { +#ifndef NAZARA_PLATFORM_WEB if (!m_curlLibrary) { std::unique_ptr curlLibrary = std::make_unique(); @@ -71,6 +79,9 @@ namespace Nz } return std::make_unique(*m_curlLibrary); +#else + return std::make_unique(); +#endif } Network* Network::s_instance = nullptr; diff --git a/src/Nazara/Network/WebRequest.cpp b/src/Nazara/Network/WebRequest.cpp index b23be915e..831c8a43b 100644 --- a/src/Nazara/Network/WebRequest.cpp +++ b/src/Nazara/Network/WebRequest.cpp @@ -3,12 +3,20 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include //< include last because of curl/curl.h #include +#include + +#ifndef NAZARA_PLATFORM_WEB +#include +#else +#include +#endif + #include namespace Nz { +#ifndef NAZARA_PLATFORM_WEB WebRequest::WebRequest(WebService& webService) : m_webService(webService), m_isUserAgentSet(false) @@ -17,17 +25,30 @@ namespace Nz m_curlHandle = libcurl.easy_init(); } +#else + WebRequest::WebRequest(WebService& webService) : + m_webService(webService), + m_isUserAgentSet(false) + { + } +#endif WebRequest::~WebRequest() { +#ifndef NAZARA_PLATFORM_WEB auto& libcurl = m_webService.GetCurlLibrary(); if (m_curlHandle) libcurl.easy_cleanup(m_curlHandle); if (m_headerList) libcurl.slist_free_all(m_headerList); +#else + if (m_fetchHandle) + emscripten_fetch_close(m_fetchHandle); +#endif } +#ifndef NAZARA_PLATFORM_WEB void WebRequest::ForceProtocol(Nz::NetProtocol protocol) { assert(protocol != Nz::NetProtocol::Unknown); @@ -51,28 +72,43 @@ namespace Nz break; } } - - void WebRequest::SetJSonContent(std::string_view encodedJSon) +#else + void WebRequest::ForceProtocol(Nz::NetProtocol /*protocol*/) { - auto& libcurl = m_webService.GetCurlLibrary(); + // Ignored + } +#endif + void WebRequest::SetJSonContent(std::string encodedJSon) + { SetHeader("Content-Type", "application/json"); - libcurl.easy_setopt(m_curlHandle, CURLOPT_POSTFIELDSIZE_LARGE, curl_off_t(encodedJSon.size())); - libcurl.easy_setopt(m_curlHandle, CURLOPT_COPYPOSTFIELDS, encodedJSon.data()); + + m_content = std::move(encodedJSon); + +#ifndef NAZARA_PLATFORM_WEB + auto& libcurl = m_webService.GetCurlLibrary(); + libcurl.easy_setopt(m_curlHandle, CURLOPT_POSTFIELDSIZE_LARGE, SafeCast(m_content.size())); + libcurl.easy_setopt(m_curlHandle, CURLOPT_POSTFIELDS, m_content.data()); +#endif } - void WebRequest::SetMaximumFileSize(Nz::UInt64 maxFileSize) +#ifndef NAZARA_PLATFORM_WEB + void WebRequest::SetMaximumFileSize(UInt64 maxFileSize) { auto& libcurl = m_webService.GetCurlLibrary(); - curl_off_t maxSize = maxFileSize; + curl_off_t maxSize = SafeCast(maxFileSize); libcurl.easy_setopt(m_curlHandle, CURLOPT_MAXFILESIZE_LARGE, maxSize); } - - void WebRequest::SetServiceName(const std::string_view& serviceName) +#else + void WebRequest::SetMaximumFileSize(UInt64 maxFileSize) { - auto& libcurl = m_webService.GetCurlLibrary(); + // TODO: Implement using EMSCRIPTEN_FETCH_STREAM_DATA + } +#endif + void WebRequest::SetServiceName(std::string serviceName) + { if (!serviceName.empty()) { //TODO Nz::StackString? @@ -81,37 +117,50 @@ namespace Nz userAgent.append(" - "); userAgent.append(serviceName.data(), serviceName.size()); - libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, userAgent.data()); + SetHeader("User-Agent", std::move(userAgent)); } else - libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, m_webService.GetUserAgent().c_str()); + SetHeader("User-Agent", m_webService.GetUserAgent()); m_isUserAgentSet = true; } void WebRequest::SetURL(const std::string& url) { +#ifndef NAZARA_PLATFORM_WEB auto& libcurl = m_webService.GetCurlLibrary(); - libcurl.easy_setopt(m_curlHandle, CURLOPT_URL, url.data()); +#else + m_url = url; +#endif } void WebRequest::SetupGet() { +#ifndef NAZARA_PLATFORM_WEB auto& libcurl = m_webService.GetCurlLibrary(); - libcurl.easy_setopt(m_curlHandle, CURLOPT_HTTPGET, long(1)); +#else + m_httpMethod = "GET"; +#endif } void WebRequest::SetupPost() { +#ifndef NAZARA_PLATFORM_WEB auto& libcurl = m_webService.GetCurlLibrary(); - libcurl.easy_setopt(m_curlHandle, CURLOPT_POST, long(1)); +#else + m_httpMethod = "POST"; +#endif } +#ifndef NAZARA_PLATFORM_WEB CURL* WebRequest::Prepare() { + if (!m_isUserAgentSet) + SetHeader("User-Agent", m_webService.GetUserAgent()); + auto& libcurl = m_webService.GetCurlLibrary(); if (!m_headers.empty()) @@ -126,8 +175,41 @@ namespace Nz } if (!m_isUserAgentSet) - libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, m_webService.GetUserAgent().c_str()); + SetHeader("User-Agent", m_webService.GetUserAgent()); return m_curlHandle; } +#else + emscripten_fetch_t* WebRequest::Prepare(emscripten_fetch_attr_t* fetchAttr) + { + if (!m_isUserAgentSet) + SetHeader("User-Agent", m_webService.GetUserAgent()); + + if (m_httpMethod.size() >= Nz::CountOf(fetchAttr->requestMethod)) + throw std::runtime_error("request method is too big"); + + if (m_url.empty()) + throw std::runtime_error("no url set"); + + std::strcpy(fetchAttr->requestMethod, m_httpMethod.c_str()); + + fetchAttr->requestData = m_content.data(); + fetchAttr->requestDataSize = m_content.size(); + + if (!m_requestHeaders.empty()) + { + for (auto&& [header, value] : m_headers) + { + m_requestHeaders.push_back(header.c_str()); + m_requestHeaders.push_back(value.c_str()); + } + m_requestHeaders.push_back(nullptr); + + fetchAttr->requestHeaders = m_requestHeaders.data(); + } + + m_fetchHandle = emscripten_fetch(fetchAttr, m_url.c_str()); + return m_fetchHandle; + } +#endif } diff --git a/src/Nazara/Network/WebRequestResult.cpp b/src/Nazara/Network/WebRequestResult.cpp index 1d0cfbb65..4fe833ff7 100644 --- a/src/Nazara/Network/WebRequestResult.cpp +++ b/src/Nazara/Network/WebRequestResult.cpp @@ -3,38 +3,55 @@ // For conditions of distribution and use, see copyright notice in Config.hpp #include -#include //< include last because of curl/curl.h +#include #include +#include + +#ifndef NAZARA_PLATFORM_WEB +#include +#else +#include +#endif + #include namespace Nz { - Nz::UInt64 WebRequestResult::GetDownloadedSize() const + UInt64 WebRequestResult::GetDownloadedSize() const { - assert(HasSucceeded()); + NazaraAssert(HasSucceeded(), "web request failed"); +#ifndef NAZARA_PLATFORM_WEB auto& libcurl = m_webService.GetCurlLibrary(); curl_off_t downloadedSize = 0; libcurl.easy_getinfo(m_curlHandle, CURLINFO_SIZE_DOWNLOAD_T, &downloadedSize); - return downloadedSize; + return SafeCast(downloadedSize); +#else + return m_fetchHandle->numBytes; +#endif } - std::size_t WebRequestResult::GetDownloadSpeed() const + UInt64 WebRequestResult::GetDownloadSpeed() const { - assert(HasSucceeded()); +#ifndef NAZARA_PLATFORM_WEB + NazaraAssert(HasSucceeded(), "web request failed"); auto& libcurl = m_webService.GetCurlLibrary(); curl_off_t downloadSpeed = 0; libcurl.easy_getinfo(m_curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &downloadSpeed); - return downloadSpeed; + return SafeCast(downloadSpeed); +#else + return 1000u * m_bodyResult.GetValue().size() / m_downloadTime.AsMilliseconds(); +#endif } - long WebRequestResult::GetReponseCode() const + UInt32 WebRequestResult::GetStatusCode() const { +#ifndef NAZARA_PLATFORM_WEB assert(HasSucceeded()); auto& libcurl = m_webService.GetCurlLibrary(); @@ -42,6 +59,9 @@ namespace Nz long responseCode; libcurl.easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &responseCode); - return responseCode; + return SafeCast(responseCode); +#else + return m_fetchHandle->status; +#endif } } diff --git a/src/Nazara/Network/WebService.cpp b/src/Nazara/Network/WebService.cpp index 8576422d3..e3bbdf6e5 100644 --- a/src/Nazara/Network/WebService.cpp +++ b/src/Nazara/Network/WebService.cpp @@ -5,12 +5,17 @@ #include #include #include -#include //< include last because of curl/curl.h +#ifndef NAZARA_PLATFORM_WEB +#include +#else +#include +#endif #include #include namespace Nz { +#ifndef NAZARA_PLATFORM_WEB WebService::WebService(const CurlLibrary& curl) : m_curl(curl) { @@ -20,9 +25,16 @@ namespace Nz m_curlMulti = m_curl.multi_init(); } +#else + WebService::WebService() : + m_userAgent("Nazara WebService - emscripten_fetch") + { + } +#endif WebService::~WebService() { +#ifndef NAZARA_PLATFORM_WEB if (m_curlMulti) { for (auto&& [handle, request] : m_activeRequests) @@ -30,10 +42,12 @@ namespace Nz m_curl.multi_cleanup(m_curlMulti); } +#endif } - + void WebService::Poll() { +#ifndef NAZARA_PLATFORM_WEB assert(m_curlMulti); int reportedActiveRequest; @@ -59,9 +73,9 @@ namespace Nz WebRequest& request = *it->second; if (m->data.result == CURLE_OK) - request.TriggerCallback(); + request.TriggerSuccessCallback(); else - request.TriggerCallback(m_curl.easy_strerror(m->data.result)); + request.TriggerErrorCallback(m_curl.easy_strerror(m->data.result)); m_curl.multi_remove_handle(m_curlMulti, handle); @@ -69,13 +83,29 @@ namespace Nz } } while (m); +#else + if (!m_finishedRequests.empty()) + { + for (auto&& [request, succeeded] : m_finishedRequests) + { + if (succeeded) + request->TriggerSuccessCallback(); + else + request->TriggerErrorCallback(request->GetFetchHandle()->statusText); + } + + m_finishedRequests.clear(); + } +#endif } void WebService::QueueRequest(std::unique_ptr&& request) { - assert(m_curlMulti); assert(request); +#ifndef NAZARA_PLATFORM_WEB + assert(m_curlMulti); + CURL* handle = request->Prepare(); curl_write_callback writeCallback = [](char* ptr, std::size_t size, std::size_t nmemb, void* userdata) -> std::size_t @@ -95,5 +125,57 @@ namespace Nz m_activeRequests.emplace(handle, std::move(request)); m_curl.multi_add_handle(m_curlMulti, handle); +#else + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + + attr.onsuccess = [](emscripten_fetch_t* fetch) + { + WebService* service = static_cast(fetch->userData); + + auto it = service->m_activeRequests.find(fetch); + if (it == service->m_activeRequests.end()) + { + NazaraError("received emscripten fetch onsuccess with unbound request"); + return; + } + + std::unique_ptr& request = it->second; + request->StopClock(); + request->OnBodyResponse(fetch->data, SafeCast(fetch->numBytes)); + + service->m_finishedRequests.push_back({ + std::move(request), + true + }); + }; + + attr.onerror = [](emscripten_fetch_t* fetch) + { + WebService* service = static_cast(fetch->userData); + + auto it = service->m_activeRequests.find(fetch); + if (it == service->m_activeRequests.end()) + { + NazaraError("received emscripten fetch onsuccess with unbound request"); + return; + } + + std::unique_ptr& request = it->second; + request->StopClock(); + + service->m_finishedRequests.push_back({ + std::move(request), + false + }); + }; + + attr.userData = this; + + emscripten_fetch_t* handle = request->Prepare(&attr); + m_activeRequests.emplace(handle, std::move(request)); +#endif } } diff --git a/xmake.lua b/xmake.lua index 8dfe864d5..b1649ac00 100644 --- a/xmake.lua +++ b/xmake.lua @@ -129,11 +129,17 @@ local modules = { Deps = {"NazaraCore"}, Packages = { "fmt" }, Custom = function () - if has_config("link_curl") then - add_defines("NAZARA_NETWORK_CURL_LINK") - add_packages("libcurl") + if not is_plat("wasm") then + if has_config("link_curl") then + add_defines("NAZARA_NETWORK_CURL_LINK") + add_packages("libcurl") + else + add_packages("libcurl", { links = {} }) + end else - add_packages("libcurl", { links = {} }) + add_ldflags("-sFETCH", { public = true }) + remove_headerfiles("include/Nazara/Network/CurlLibrary.hpp") + remove_files("src/Nazara/Network/CurlLibrary.cpp") end if is_plat("windows", "mingw") then @@ -268,10 +274,13 @@ if has_config("joltphysics") then end if has_config("network") then - if has_config("link_curl") then - add_requires("libcurl") - else - add_requires("libcurl", { configs = { shared = true }}) + -- emscripten fetch API is used for WebService on wasm + if not is_plat("wasm") then + if has_config("link_curl") then + add_requires("libcurl") + else + add_requires("libcurl", { configs = { shared = true }}) + end end end