Network: Implement WebService with emscripten fetch API on wasm
This commit is contained in:
committed by
Jérôme Leclercq
parent
b28d97b1fa
commit
6bf91e10e5
@@ -7,11 +7,15 @@
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Core/Log.hpp>
|
||||
#include <Nazara/Network/Config.hpp>
|
||||
#include <Nazara/Network/CurlLibrary.hpp>
|
||||
#include <Nazara/Network/NetPacket.hpp>
|
||||
#include <Nazara/Network/WebService.hpp>
|
||||
#include <NazaraUtils/CallOnExit.hpp>
|
||||
|
||||
|
||||
#ifndef NAZARA_PLATFORM_WEB
|
||||
#include <Nazara/Network/CurlLibrary.hpp>
|
||||
#endif
|
||||
|
||||
#if defined(NAZARA_PLATFORM_WINDOWS)
|
||||
#include <Nazara/Network/Win32/SocketImpl.hpp>
|
||||
#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<CurlLibrary>();
|
||||
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<WebService> Network::InstantiateWebService()
|
||||
{
|
||||
#ifndef NAZARA_PLATFORM_WEB
|
||||
if (!m_curlLibrary)
|
||||
{
|
||||
std::unique_ptr<CurlLibrary> curlLibrary = std::make_unique<CurlLibrary>();
|
||||
@@ -71,6 +79,9 @@ namespace Nz
|
||||
}
|
||||
|
||||
return std::make_unique<WebService>(*m_curlLibrary);
|
||||
#else
|
||||
return std::make_unique<WebService>();
|
||||
#endif
|
||||
}
|
||||
|
||||
Network* Network::s_instance = nullptr;
|
||||
|
||||
@@ -3,12 +3,20 @@
|
||||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Network/WebRequest.hpp>
|
||||
#include <Nazara/Network/CurlLibrary.hpp> //< include last because of curl/curl.h
|
||||
#include <Nazara/Network/WebService.hpp>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef NAZARA_PLATFORM_WEB
|
||||
#include <Nazara/Network/CurlLibrary.hpp>
|
||||
#else
|
||||
#include <emscripten/fetch.h>
|
||||
#endif
|
||||
|
||||
#include <Nazara/Network/Debug.hpp>
|
||||
|
||||
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<curl_off_t>(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<curl_off_t>(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
|
||||
}
|
||||
|
||||
@@ -3,38 +3,55 @@
|
||||
// For conditions of distribution and use, see copyright notice in Config.hpp
|
||||
|
||||
#include <Nazara/Network/WebRequestResult.hpp>
|
||||
#include <Nazara/Network/CurlLibrary.hpp> //< include last because of curl/curl.h
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Network/WebService.hpp>
|
||||
#include <NazaraUtils/Algorithm.hpp>
|
||||
|
||||
#ifndef NAZARA_PLATFORM_WEB
|
||||
#include <Nazara/Network/CurlLibrary.hpp>
|
||||
#else
|
||||
#include <emscripten/fetch.h>
|
||||
#endif
|
||||
|
||||
#include <Nazara/Network/Debug.hpp>
|
||||
|
||||
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<UInt64>(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<UInt64>(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<UInt32>(responseCode);
|
||||
#else
|
||||
return m_fetchHandle->status;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
#include <Nazara/Network/WebService.hpp>
|
||||
#include <Nazara/Core/Error.hpp>
|
||||
#include <Nazara/Core/ErrorFlags.hpp>
|
||||
#include <Nazara/Network/CurlLibrary.hpp> //< include last because of curl/curl.h
|
||||
#ifndef NAZARA_PLATFORM_WEB
|
||||
#include <Nazara/Network/CurlLibrary.hpp>
|
||||
#else
|
||||
#include <emscripten/fetch.h>
|
||||
#endif
|
||||
#include <fmt/format.h>
|
||||
#include <Nazara/Network/Debug.hpp>
|
||||
|
||||
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<WebRequest>&& 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<WebService*>(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<WebRequest>& request = it->second;
|
||||
request->StopClock();
|
||||
request->OnBodyResponse(fetch->data, SafeCast<std::size_t>(fetch->numBytes));
|
||||
|
||||
service->m_finishedRequests.push_back({
|
||||
std::move(request),
|
||||
true
|
||||
});
|
||||
};
|
||||
|
||||
attr.onerror = [](emscripten_fetch_t* fetch)
|
||||
{
|
||||
WebService* service = static_cast<WebService*>(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<WebRequest>& 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user