Network: Add WebService

This commit is contained in:
SirLynix
2023-06-09 09:20:25 +02:00
committed by Jérôme Leclercq
parent b5206ebdb7
commit c65daba072
20 changed files with 899 additions and 4 deletions

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Network module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Network/CurlLibrary.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <NazaraUtils/CallOnExit.hpp>
#include <Nazara/Network/Debug.hpp>
namespace Nz
{
bool CurlLibrary::Load()
{
Unload();
CallOnExit unloadOnFailure([this] { Unload(); });
auto PostLoad = [&]
{
if (global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
{
NazaraError("failed to initialize libcurl");
return false;
}
m_isInitialized = true;
unloadOnFailure.Reset();
return true;
};
#ifndef NAZARA_NETWORK_LIBCURL_LINK
for (const char* libname : { "libcurl" NAZARA_DYNLIB_EXTENSION, "libcurl-d" NAZARA_DYNLIB_EXTENSION })
{
ErrorFlags errFlags(Nz::ErrorMode::Silent);
if (m_library.Load(libname))
break;
}
if (!m_library.IsLoaded())
{
NazaraError("failed to load libcurl: " + m_library.GetLastError());
return false;
}
auto LoadSymbol = [this](const char* name, bool optional)
{
DynLibFunc funcPtr = m_library.GetSymbol(name);
if (!funcPtr && !optional)
throw std::runtime_error(std::string("failed to load ") + name);
return funcPtr;
};
try
{
#define NAZARA_CURL_FUNCTION(name) name = reinterpret_cast<decltype(&::curl_##name)>(LoadSymbol("curl_" #name, false));
#include <Nazara/Network/CurlFunctions.hpp>
}
catch (const std::exception& e)
{
NazaraError(e.what());
return false;
}
return PostLoad();
#else
// libcurl is linked to the executable
#define NAZARA_CURL_FUNCTION(name) name = &::curl_##name;
#include <Nazara/Network/CurlFunctions.hpp>
return PostLoad();
#endif
}
void CurlLibrary::Unload()
{
if (!m_library.IsLoaded())
return;
if (m_isInitialized)
global_cleanup();
#define NAZARA_CURL_FUNCTION(name) name = nullptr;
#include <Nazara/Network/CurlFunctions.hpp>
m_library.Unload();
}
}

View File

@@ -7,7 +7,9 @@
#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>
#if defined(NAZARA_PLATFORM_WINDOWS)
@@ -30,7 +32,7 @@ namespace Nz
* \brief Network class that represents the module initializer of Network
*/
Network::Network(Config /*config*/) :
Network::Network(Config config) :
ModuleBase("Network", this)
{
// Initialize module here
@@ -39,14 +41,37 @@ namespace Nz
if (!NetPacket::Initialize())
throw std::runtime_error("failed to initialize packets");
if (config.webServices)
{
m_curlLibrary = std::make_unique<CurlLibrary>();
if (!m_curlLibrary->Load())
throw std::runtime_error("failed to initialize curl");
}
}
Network::~Network()
{
// Uninitialize module here
m_curlLibrary.reset();
NetPacket::Uninitialize();
SocketImpl::Uninitialize();
}
std::unique_ptr<WebService> Network::InstantiateWebService()
{
if (!m_curlLibrary)
{
std::unique_ptr<CurlLibrary> curlLibrary = std::make_unique<CurlLibrary>();
if (!curlLibrary->Load())
throw std::runtime_error("failed to initialize curl");
m_curlLibrary = std::move(curlLibrary);
}
return std::make_unique<WebService>(*m_curlLibrary);
}
Network* Network::s_instance = nullptr;
}

View File

@@ -0,0 +1,133 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Network module"
// 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 <Nazara/Network/Debug.hpp>
namespace Nz
{
WebRequest::WebRequest(WebService& webService) :
m_webService(webService),
m_isUserAgentSet(false)
{
auto& libcurl = m_webService.GetCurlLibrary();
m_curlHandle = libcurl.easy_init();
}
WebRequest::~WebRequest()
{
auto& libcurl = m_webService.GetCurlLibrary();
if (m_curlHandle)
libcurl.easy_cleanup(m_curlHandle);
if (m_headerList)
libcurl.slist_free_all(m_headerList);
}
void WebRequest::ForceProtocol(Nz::NetProtocol protocol)
{
assert(protocol != Nz::NetProtocol::Unknown);
auto& libcurl = m_webService.GetCurlLibrary();
switch (protocol)
{
case Nz::NetProtocol::Any:
libcurl.easy_setopt(m_curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
break;
case Nz::NetProtocol::IPv4:
libcurl.easy_setopt(m_curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
break;
case Nz::NetProtocol::IPv6:
libcurl.easy_setopt(m_curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
break;
case Nz::NetProtocol::Unknown:
break;
}
}
void WebRequest::SetJSonContent(std::string_view encodedJSon)
{
auto& libcurl = m_webService.GetCurlLibrary();
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());
}
void WebRequest::SetMaximumFileSize(Nz::UInt64 maxFileSize)
{
auto& libcurl = m_webService.GetCurlLibrary();
curl_off_t maxSize = maxFileSize;
libcurl.easy_setopt(m_curlHandle, CURLOPT_MAXFILESIZE_LARGE, maxSize);
}
void WebRequest::SetServiceName(const std::string_view& serviceName)
{
auto& libcurl = m_webService.GetCurlLibrary();
if (!serviceName.empty())
{
//TODO Nz::StackString?
std::string userAgent = m_webService.GetUserAgent();
userAgent.reserve(userAgent.size() + 3 + serviceName.size());
userAgent.append(" - ");
userAgent.append(serviceName.data(), serviceName.size());
libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, userAgent.data());
}
else
libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, m_webService.GetUserAgent().c_str());
m_isUserAgentSet = true;
}
void WebRequest::SetURL(const std::string& url)
{
auto& libcurl = m_webService.GetCurlLibrary();
libcurl.easy_setopt(m_curlHandle, CURLOPT_URL, url.data());
}
void WebRequest::SetupGet()
{
auto& libcurl = m_webService.GetCurlLibrary();
libcurl.easy_setopt(m_curlHandle, CURLOPT_HTTPGET, long(1));
}
void WebRequest::SetupPost()
{
auto& libcurl = m_webService.GetCurlLibrary();
libcurl.easy_setopt(m_curlHandle, CURLOPT_POST, long(1));
}
CURL* WebRequest::Prepare()
{
auto& libcurl = m_webService.GetCurlLibrary();
if (!m_headers.empty())
{
for (auto&& [header, value] : m_headers)
{
std::string headerValue = (!value.empty()) ? header + ": " + value : header + ";";
m_headerList = libcurl.slist_append(m_headerList, headerValue.c_str());
}
libcurl.easy_setopt(m_curlHandle, CURLOPT_HTTPHEADER, m_headerList.Get());
}
if (!m_isUserAgentSet)
libcurl.easy_setopt(m_curlHandle, CURLOPT_USERAGENT, m_webService.GetUserAgent().c_str());
return m_curlHandle;
}
}

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Network module"
// 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/Network/WebService.hpp>
#include <Nazara/Network/Debug.hpp>
namespace Nz
{
Nz::UInt64 WebRequestResult::GetDownloadedSize() const
{
assert(HasSucceeded());
auto& libcurl = m_webService.GetCurlLibrary();
curl_off_t downloadedSize = 0;
libcurl.easy_getinfo(m_curlHandle, CURLINFO_SIZE_DOWNLOAD_T, &downloadedSize);
return downloadedSize;
}
std::size_t WebRequestResult::GetDownloadSpeed() const
{
assert(HasSucceeded());
auto& libcurl = m_webService.GetCurlLibrary();
curl_off_t downloadSpeed = 0;
libcurl.easy_getinfo(m_curlHandle, CURLINFO_SPEED_DOWNLOAD_T, &downloadSpeed);
return downloadSpeed;
}
long WebRequestResult::GetReponseCode() const
{
assert(HasSucceeded());
auto& libcurl = m_webService.GetCurlLibrary();
long responseCode;
libcurl.easy_getinfo(m_curlHandle, CURLINFO_RESPONSE_CODE, &responseCode);
return responseCode;
}
}

View File

@@ -0,0 +1,99 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// This file is part of the "Nazara Engine - Network module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#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
#include <fmt/format.h>
#include <Nazara/Network/Debug.hpp>
namespace Nz
{
WebService::WebService(const CurlLibrary& curl) :
m_curl(curl)
{
curl_version_info_data* curlVersionData = m_curl.version_info(CURLVERSION_NOW);
m_userAgent = fmt::format("Nazara WebService - curl/{}", curlVersionData->version);
m_curlMulti = m_curl.multi_init();
}
WebService::~WebService()
{
if (m_curlMulti)
{
for (auto&& [handle, request] : m_activeRequests)
m_curl.multi_remove_handle(m_curlMulti, handle);
m_curl.multi_cleanup(m_curlMulti);
}
}
void WebService::Poll()
{
assert(m_curlMulti);
int reportedActiveRequest;
CURLMcode err = m_curl.multi_perform(m_curlMulti, &reportedActiveRequest);
if (err != CURLM_OK)
{
NazaraError(fmt::format("[WebService] curl_multi_perform failed with {0}: {1}", UnderlyingCast(err), m_curl.multi_strerror(err)));
return;
}
CURLMsg* m;
do
{
int msgq;
m = m_curl.multi_info_read(m_curlMulti, &msgq);
if (m && (m->msg == CURLMSG_DONE))
{
CURL* handle = m->easy_handle;
auto it = m_activeRequests.find(handle);
assert(it != m_activeRequests.end());
WebRequest& request = *it->second;
if (m->data.result == CURLE_OK)
request.TriggerCallback();
else
request.TriggerCallback(m_curl.easy_strerror(m->data.result));
m_curl.multi_remove_handle(m_curlMulti, handle);
m_activeRequests.erase(handle);
}
}
while (m);
}
void WebService::QueueRequest(std::unique_ptr<WebRequest>&& request)
{
assert(m_curlMulti);
assert(request);
CURL* handle = request->Prepare();
curl_write_callback writeCallback = [](char* ptr, std::size_t size, std::size_t nmemb, void* userdata) -> std::size_t
{
WebRequest* request = static_cast<WebRequest*>(userdata);
std::size_t totalSize = size * nmemb;
if (!request->OnBodyResponse(ptr, totalSize))
return 0;
return totalSize;
};
m_curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback);
m_curl.easy_setopt(handle, CURLOPT_WRITEDATA, request.get());
m_activeRequests.emplace(handle, std::move(request));
m_curl.multi_add_handle(m_curlMulti, handle);
}
}