mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-28 08:13:54 +01:00
Adds lib-network-manager
This commit is contained in:
349
libraries/lib-network-manager/curl/CurlHandleManager.cpp
Normal file
349
libraries/lib-network-manager/curl/CurlHandleManager.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlHandleManager.cpp
|
||||
@brief Define a class responsible for reuse of CURL hanldes.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#include "CurlHandleManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <wx/platinfo.h>
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
void GetOSString (std::ostringstream& output, const wxPlatformInfo& platformInfo)
|
||||
{
|
||||
const wxOperatingSystemId osID = platformInfo.GetOperatingSystemId ();
|
||||
|
||||
if (osID & wxOS_WINDOWS)
|
||||
output << "Windows ";
|
||||
else if (osID & wxOS_MAC)
|
||||
output << "MacOS ";
|
||||
else if (osID & wxOS_UNIX_LINUX)
|
||||
output << "Linux ";
|
||||
else if (osID & wxOS_UNIX_FREEBSD)
|
||||
output << "FreeBSD ";
|
||||
else if (osID & wxOS_UNIX_OPENBSD)
|
||||
output << "OpenBSD ";
|
||||
else
|
||||
output << "Other ";
|
||||
|
||||
output <<
|
||||
platformInfo.GetOSMajorVersion () <<
|
||||
"_" <<
|
||||
platformInfo.GetOSMinorVersion () <<
|
||||
"_" <<
|
||||
platformInfo.GetOSMicroVersion() <<
|
||||
"; ";
|
||||
|
||||
#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
|
||||
output << "x64";
|
||||
#elif defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(_X86_) || defined(__THW_INTEL)
|
||||
output << "x86";
|
||||
#elif
|
||||
output << "arm64";
|
||||
#elif defined(arm) || defined(__arm__) || defined(ARM) || defined(_ARM_)
|
||||
output << "arm";
|
||||
#else
|
||||
output << "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_IDLE;
|
||||
constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_PROBE;
|
||||
|
||||
CurlHandleManager::Handle::Handle(CurlHandleManager* owner, CURL* handle, RequestVerb verb, std::string url) noexcept
|
||||
: mHandle (handle),
|
||||
mOwner (owner),
|
||||
mVerb (verb),
|
||||
mUrl (std::move (url)),
|
||||
mHandleFromCache (handle != nullptr)
|
||||
{
|
||||
if (mHandle == nullptr)
|
||||
mHandle = curl_easy_init ();
|
||||
|
||||
setOption (CURLOPT_URL, mUrl);
|
||||
|
||||
switch (verb)
|
||||
{
|
||||
case RequestVerb::Head:
|
||||
setOption (CURLOPT_NOBODY, 1);
|
||||
break;
|
||||
case RequestVerb::Get:
|
||||
// This is a default, no additional setup is needed
|
||||
// We cache handles by the verb, so there is no need to
|
||||
// reset the handle state
|
||||
break;
|
||||
case RequestVerb::Post:
|
||||
setOption (CURLOPT_POST, 1);
|
||||
break;
|
||||
case RequestVerb::Put:
|
||||
setOption (CURLOPT_UPLOAD, 1);
|
||||
break;
|
||||
case RequestVerb::Delete:
|
||||
setOption (CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
break;
|
||||
}
|
||||
|
||||
setOption (CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
setOption (CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
setOption (CURLOPT_SSL_VERIFYHOST, 2L);
|
||||
|
||||
setOption (CURLOPT_ACCEPT_ENCODING, "");
|
||||
}
|
||||
|
||||
CurlHandleManager::Handle::Handle (Handle&& rhs) noexcept
|
||||
{
|
||||
*this = std::move (rhs);
|
||||
}
|
||||
|
||||
CurlHandleManager::Handle::~Handle () noexcept
|
||||
{
|
||||
if (mReuse)
|
||||
mOwner->cacheHandle (*this);
|
||||
else
|
||||
curl_easy_cleanup (mHandle);
|
||||
}
|
||||
|
||||
CurlHandleManager::Handle& CurlHandleManager::Handle::operator=(Handle&& rhs) noexcept
|
||||
{
|
||||
std::swap (mHandle, rhs.mHandle);
|
||||
std::swap (mOwner, rhs.mOwner);
|
||||
|
||||
std::swap (mVerb, rhs.mVerb);
|
||||
|
||||
mUrl = std::move (rhs.mUrl);
|
||||
mHeaders = std::move (rhs.mHeaders);
|
||||
|
||||
mReuse = rhs.mReuse;
|
||||
rhs.mReuse = false;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CURLcode CurlHandleManager::Handle::setOption (CURLoption option, const std::string& value) noexcept
|
||||
{
|
||||
return setOption (option, value.c_str ());
|
||||
}
|
||||
|
||||
CURLcode CurlHandleManager::Handle::appendCookie (const Cookie& cookie) noexcept
|
||||
{
|
||||
return setOption(CURLOPT_COOKIE, "Set-Cookie: " + cookie.Name + "=" + cookie.Value);
|
||||
}
|
||||
|
||||
CURLcode CurlHandleManager::Handle::appendCookies (const CookiesList& cookies)noexcept
|
||||
{
|
||||
for (const Cookie& cookie : cookies)
|
||||
{
|
||||
const CURLcode result = appendCookie (cookie);
|
||||
|
||||
if (result != CURLE_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
void CurlHandleManager::Handle::appendHeader (const Header& header)
|
||||
{
|
||||
if (header.hasSameName ("User-Agent"))
|
||||
mUserAgentSet = true;
|
||||
|
||||
mHeaders.append(header.Name + ": " + header.Value);
|
||||
}
|
||||
|
||||
void CurlHandleManager::Handle::appendHeaders (const HeadersList& headers)
|
||||
{
|
||||
for (const Header& header : headers)
|
||||
appendHeader (header);
|
||||
}
|
||||
|
||||
CurlHandleManager::Handle::Result CurlHandleManager::Handle::perform ()
|
||||
{
|
||||
if (!mUserAgentSet)
|
||||
mHeaders.append ("User-Agent: " + mOwner->getUserAgent ());
|
||||
|
||||
CURLcode result = setOption (CURLOPT_HTTPHEADER, mHeaders.getCurlList ());
|
||||
|
||||
if (result != CURLE_OK)
|
||||
return { result, std::string () };
|
||||
|
||||
char currentError[CURL_ERROR_SIZE] = {};
|
||||
setOption(CURLOPT_ERRORBUFFER, currentError);
|
||||
|
||||
result = curl_easy_perform (mHandle);
|
||||
|
||||
mReuse = mReuse && result == CURLE_OK;
|
||||
|
||||
return { result, std::string (currentError) };
|
||||
}
|
||||
|
||||
void CurlHandleManager::Handle::markKeepAlive ()
|
||||
{
|
||||
mReuse = true;
|
||||
}
|
||||
|
||||
bool CurlHandleManager::Handle::isHandleFromCache () const noexcept
|
||||
{
|
||||
return mHandleFromCache;
|
||||
}
|
||||
|
||||
unsigned CurlHandleManager::Handle::getHTTPCode () const noexcept
|
||||
{
|
||||
long code = 0;
|
||||
|
||||
if (CURLE_OK != curl_easy_getinfo(mHandle, CURLINFO_RESPONSE_CODE, &code))
|
||||
return 0;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
void CurlHandleManager::Handle::reset () noexcept
|
||||
{
|
||||
setOption (CURLOPT_COOKIELIST, nullptr);
|
||||
setOption (CURLOPT_PROXY, nullptr);
|
||||
setOption (CURLOPT_SSL_OPTIONS, 0);
|
||||
|
||||
mUserAgentSet = false;
|
||||
}
|
||||
|
||||
CurlHandleManager::CurlHandleManager ()
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << "Audacity/" <<
|
||||
AUDACITY_VERSION << "." <<
|
||||
AUDACITY_RELEASE << "." <<
|
||||
AUDACITY_REVISION <<
|
||||
" (";
|
||||
|
||||
GetOSString (ss, wxPlatformInfo::Get ());
|
||||
|
||||
ss << ")";
|
||||
|
||||
mUserAgent = ss.str ();
|
||||
}
|
||||
|
||||
CurlHandleManager::~CurlHandleManager ()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
||||
|
||||
for (auto& cachedHandle : mHandleCache)
|
||||
curl_easy_cleanup (cachedHandle.Handle);
|
||||
}
|
||||
|
||||
void CurlHandleManager::setProxy (std::string proxy)
|
||||
{
|
||||
mProxy = std::move (proxy);
|
||||
}
|
||||
|
||||
CurlHandleManager::Handle CurlHandleManager::getHandle (RequestVerb verb, const std::string& url)
|
||||
{
|
||||
Handle handle (this, getCurlHandleFromCache (verb, url), verb, url);
|
||||
|
||||
if (!mProxy.empty ())
|
||||
{
|
||||
handle.setOption (CURLOPT_PROXY, mProxy);
|
||||
// If we use proxy, checking the CRL will likely break the SSL proxying
|
||||
handle.setOption (CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
|
||||
}
|
||||
|
||||
handle.setOption (CURLOPT_TCP_KEEPALIVE, 1L);
|
||||
|
||||
handle.setOption (CURLOPT_TCP_KEEPIDLE,
|
||||
std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_IDLE).count ()
|
||||
);
|
||||
|
||||
handle.setOption (CURLOPT_TCP_KEEPINTVL,
|
||||
std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_PROBE).count ()
|
||||
);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
std::string CurlHandleManager::getUserAgent () const
|
||||
{
|
||||
return mUserAgent;
|
||||
}
|
||||
|
||||
CURL* CurlHandleManager::getCurlHandleFromCache (RequestVerb verb, const std::string& url)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
||||
|
||||
cleanupHandlesCache ();
|
||||
|
||||
const std::string schemeAndDomain = GetSchemeAndDomain (url);
|
||||
|
||||
auto it = std::find_if (mHandleCache.begin (), mHandleCache.end (), [verb, schemeAndDomain](const CachedHandle& handle) {
|
||||
return handle.Verb == verb && handle.SchemeAndDomain == schemeAndDomain;
|
||||
});
|
||||
|
||||
if (it == mHandleCache.end ())
|
||||
return nullptr;
|
||||
|
||||
CURL* handle = it->Handle;
|
||||
|
||||
mHandleCache.erase (it);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void CurlHandleManager::cacheHandle (Handle& handle)
|
||||
{
|
||||
// Reset the state to the safe defaults
|
||||
handle.reset ();
|
||||
|
||||
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
||||
|
||||
cleanupHandlesCache ();
|
||||
|
||||
mHandleCache.push_back ({
|
||||
handle.mVerb,
|
||||
GetSchemeAndDomain (handle.mUrl),
|
||||
handle.mHandle,
|
||||
RequestClock::now ()
|
||||
});
|
||||
}
|
||||
|
||||
void CurlHandleManager::cleanupHandlesCache ()
|
||||
{
|
||||
const RequestTimePoint timePoint = RequestClock::now ();
|
||||
|
||||
mHandleCache.erase (std::remove_if (mHandleCache.begin (), mHandleCache.end (), [timePoint](const CachedHandle& cachedHandle) {
|
||||
return (timePoint - cachedHandle.RequestTime) >= KEEP_ALIVE_IDLE;
|
||||
}), mHandleCache.end ());
|
||||
}
|
||||
|
||||
std::string CurlHandleManager::GetSchemeAndDomain (const std::string& url)
|
||||
{
|
||||
const size_t schemeEndPosition = url.find ("://");
|
||||
|
||||
if (schemeEndPosition == std::string::npos) // Is url even valid?
|
||||
return url;
|
||||
|
||||
const size_t domainStartPosition = schemeEndPosition + 3;
|
||||
|
||||
const size_t slashLocation = url.find ('/', domainStartPosition);
|
||||
|
||||
if (slashLocation == std::string::npos)
|
||||
return url;
|
||||
|
||||
return url.substr (domainStartPosition, slashLocation - domainStartPosition);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
127
libraries/lib-network-manager/curl/CurlHandleManager.h
Normal file
127
libraries/lib-network-manager/curl/CurlHandleManager.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlHandleManager.h
|
||||
@brief Declare a class responsible for reuse of CURL hanldes.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "CurlStringList.h"
|
||||
#include "../IResponseFactory.h"
|
||||
|
||||
#include "../CookiesList.h"
|
||||
#include "../HeadersList.h"
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
class CurlHandleManager final
|
||||
{
|
||||
public:
|
||||
class Handle final
|
||||
{
|
||||
friend class CurlHandleManager;
|
||||
Handle (CurlHandleManager* owner, CURL* handle, RequestVerb verb, std::string url) noexcept;
|
||||
public:
|
||||
Handle (Handle&& rhs) noexcept;
|
||||
~Handle () noexcept;
|
||||
|
||||
Handle& operator = (Handle&& rhs) noexcept;
|
||||
|
||||
template<typename... Args>
|
||||
CURLcode setOption (CURLoption option, Args... value) noexcept
|
||||
{
|
||||
return curl_easy_setopt (mHandle, option, value...);
|
||||
}
|
||||
|
||||
CURLcode setOption (CURLoption option, const std::string& value) noexcept;
|
||||
|
||||
CURLcode appendCookie (const Cookie& cookie) noexcept;
|
||||
CURLcode appendCookies (const CookiesList& cookie) noexcept;
|
||||
|
||||
void appendHeader (const Header& header);
|
||||
void appendHeaders (const HeadersList& headers);
|
||||
|
||||
struct Result final
|
||||
{
|
||||
CURLcode Code;
|
||||
std::string Message;
|
||||
};
|
||||
|
||||
Result perform ();
|
||||
|
||||
void markKeepAlive ();
|
||||
|
||||
bool isHandleFromCache () const noexcept;
|
||||
|
||||
unsigned getHTTPCode () const noexcept;
|
||||
|
||||
void reset () noexcept;
|
||||
private:
|
||||
CURL* mHandle { nullptr };
|
||||
CurlHandleManager* mOwner { nullptr };
|
||||
|
||||
RequestVerb mVerb;
|
||||
std::string mUrl;
|
||||
|
||||
CurlStringList mHeaders;
|
||||
|
||||
bool mUserAgentSet { false };
|
||||
bool mReuse { false };
|
||||
bool mHandleFromCache { false };
|
||||
};
|
||||
|
||||
CurlHandleManager ();
|
||||
~CurlHandleManager ();
|
||||
|
||||
void setProxy (std::string proxy);
|
||||
|
||||
Handle getHandle (RequestVerb verb, const std::string& url);
|
||||
private:
|
||||
using RequestClock = std::chrono::steady_clock;
|
||||
using RequestTimePoint = RequestClock::time_point;
|
||||
|
||||
static constexpr std::chrono::milliseconds KEEP_ALIVE_IDLE { std::chrono::seconds (120) };
|
||||
static constexpr std::chrono::milliseconds KEEP_ALIVE_PROBE { std::chrono::seconds (60) };
|
||||
|
||||
struct CachedHandle final
|
||||
{
|
||||
RequestVerb Verb;
|
||||
std::string SchemeAndDomain;
|
||||
|
||||
CURL* Handle { nullptr };
|
||||
|
||||
RequestTimePoint RequestTime;
|
||||
};
|
||||
|
||||
std::string getUserAgent () const;
|
||||
|
||||
CURL* getCurlHandleFromCache (RequestVerb verb, const std::string& url);
|
||||
void cacheHandle (Handle& handle);
|
||||
|
||||
void cleanupHandlesCache ();
|
||||
|
||||
static std::string GetSchemeAndDomain (const std::string& url);
|
||||
|
||||
std::string mProxy;
|
||||
std::string mUserAgent;
|
||||
|
||||
std::mutex mHandleCacheLock;
|
||||
std::vector<CachedHandle> mHandleCache;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
381
libraries/lib-network-manager/curl/CurlResponse.cpp
Normal file
381
libraries/lib-network-manager/curl/CurlResponse.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlResponse.cpp
|
||||
@brief Define an implementation of IResponse using libcurl.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#include "CurlResponse.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
static const std::map<CURLcode, NetworkError> errorsMap = {
|
||||
{ CURLE_OK, NetworkError::NoError },
|
||||
{ CURLE_URL_MALFORMAT, NetworkError::BadURL },
|
||||
{ CURLE_COULDNT_RESOLVE_PROXY, NetworkError::ProxyNotFound },
|
||||
{ CURLE_COULDNT_RESOLVE_HOST, NetworkError::HostNotFound },
|
||||
{ CURLE_COULDNT_CONNECT, NetworkError::ConnectionRefused },
|
||||
{ CURLE_HTTP_RETURNED_ERROR, NetworkError::HTTPError },
|
||||
{ CURLE_WRITE_ERROR, NetworkError::OperationCancelled },
|
||||
{ CURLE_READ_ERROR, NetworkError::OperationCancelled },
|
||||
{ CURLE_OPERATION_TIMEDOUT, NetworkError::Timeout },
|
||||
{ CURLE_RANGE_ERROR, NetworkError::HTTPError },
|
||||
{ CURLE_HTTP_POST_ERROR, NetworkError::HTTPError },
|
||||
{ CURLE_SSL_CONNECT_ERROR, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_ABORTED_BY_CALLBACK, NetworkError::OperationCancelled },
|
||||
{ CURLE_TOO_MANY_REDIRECTS, NetworkError::OperationCancelled },
|
||||
{ CURLE_PEER_FAILED_VERIFICATION, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_GOT_NOTHING, NetworkError::RemoteHostClosed },
|
||||
{ CURLE_SSL_ENGINE_NOTFOUND, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_ENGINE_SETFAILED, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SEND_ERROR, NetworkError::RemoteHostClosed },
|
||||
{ CURLE_RECV_ERROR, NetworkError::RemoteHostClosed },
|
||||
{ CURLE_SSL_CERTPROBLEM, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_CIPHER, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_CACERT, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_USE_SSL_FAILED, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_ENGINE_INITFAILED, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_CACERT_BADFILE, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_SHUTDOWN_FAILED, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_CRL_BADFILE, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_ISSUER_ERROR, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_CHUNK_FAILED, NetworkError::HTTPError },
|
||||
{ CURLE_NO_CONNECTION_AVAILABLE, NetworkError::ConnectionFailed },
|
||||
{ CURLE_SSL_PINNEDPUBKEYNOTMATCH, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_SSL_INVALIDCERTSTATUS, NetworkError::SSLHandshakeFailed },
|
||||
{ CURLE_PARTIAL_FILE, NetworkError::RemoteHostClosed }
|
||||
};
|
||||
|
||||
struct DataStream final
|
||||
{
|
||||
const char* Buffer;
|
||||
size_t Size;
|
||||
|
||||
size_t Offset { 0 };
|
||||
};
|
||||
|
||||
size_t DataStreamRead (char* ptr, size_t size, size_t nmemb, DataStream* stream) noexcept
|
||||
{
|
||||
size = std::min (size * nmemb, stream->Size - stream->Offset);
|
||||
|
||||
const char* start = stream->Buffer + stream->Offset;
|
||||
const char* end = start + size;
|
||||
|
||||
std::copy (start, end, ptr);
|
||||
|
||||
stream->Offset += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int DataStreamSeek (DataStream* stream, curl_off_t offs, int origin) noexcept
|
||||
{
|
||||
int64_t offset = offs;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case SEEK_CUR:
|
||||
offset += stream->Offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
offset += stream->Size;
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset < 0 || offset >= stream->Size)
|
||||
return CURL_SEEKFUNC_FAIL;
|
||||
|
||||
stream->Offset = offset;
|
||||
|
||||
return CURL_SEEKFUNC_OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CurlResponse::CurlResponse (RequestVerb verb, const Request& request, CurlHandleManager* handleManager) noexcept
|
||||
: mVerb(verb),
|
||||
mRequest(request),
|
||||
mHandleManager (handleManager)
|
||||
{
|
||||
}
|
||||
|
||||
bool CurlResponse::isFinished () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
return mRequestFinished;
|
||||
}
|
||||
|
||||
unsigned CurlResponse::getHTTPCode () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
return mHttpCode;
|
||||
}
|
||||
|
||||
NetworkError CurlResponse::getError () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
return mNetworkError;
|
||||
}
|
||||
|
||||
std::string CurlResponse::getErrorString () const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
return mErrorString;
|
||||
}
|
||||
|
||||
bool CurlResponse::headersReceived () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
return mHeadersReceived;
|
||||
}
|
||||
|
||||
bool CurlResponse::hasHeader (const std::string& headerName) const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHeadersMutex);
|
||||
return mResponseHeaders.hasHeader (headerName);
|
||||
}
|
||||
|
||||
std::string CurlResponse::getHeader (const std::string& headerName) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHeadersMutex);
|
||||
return mResponseHeaders.getHeaderValue (headerName);
|
||||
}
|
||||
|
||||
const HeadersList& CurlResponse::getHeaders () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHeadersMutex);
|
||||
return mResponseHeaders;
|
||||
}
|
||||
|
||||
const CookiesList& CurlResponse::getCookies () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mHeadersMutex);
|
||||
return mResponseCookies;
|
||||
}
|
||||
|
||||
const Request& CurlResponse::getRequest () const noexcept
|
||||
{
|
||||
return mRequest;
|
||||
}
|
||||
|
||||
std::string CurlResponse::getURL () const
|
||||
{
|
||||
return mRequest.getURL ();
|
||||
}
|
||||
|
||||
void CurlResponse::abort () noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
mAbortRequested = true;
|
||||
}
|
||||
|
||||
void CurlResponse::setOnDataReceivedCallback (RequestCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mCallbackMutex);
|
||||
|
||||
mOnDataReceivedCallback = std::move (callback);
|
||||
|
||||
if (mOnDataReceivedCallback && getBytesAvailable () > 0)
|
||||
mOnDataReceivedCallback (this);
|
||||
}
|
||||
|
||||
void CurlResponse::setRequestFinishedCallback (RequestCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> callbackLock (mCallbackMutex);
|
||||
|
||||
mRequestFinishedCallback = std::move (callback);
|
||||
|
||||
std::lock_guard<std::mutex> statusLock (mStatusMutex);
|
||||
|
||||
if (mRequestFinishedCallback && mRequestFinished)
|
||||
mRequestFinishedCallback (this);
|
||||
}
|
||||
|
||||
uint64_t CurlResponse::getBytesAvailable () const noexcept
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mDataBufferMutex);
|
||||
return mDataBuffer.size ();
|
||||
}
|
||||
|
||||
uint64_t CurlResponse::readData (void* buffer, uint64_t maxBytesCount)
|
||||
{
|
||||
if (buffer == nullptr || maxBytesCount == 0)
|
||||
return 0;
|
||||
|
||||
std::lock_guard<std::mutex> lock (mDataBufferMutex);
|
||||
|
||||
if (mDataBuffer.empty ())
|
||||
return 0;
|
||||
|
||||
maxBytesCount = std::min<uint64_t> (maxBytesCount, mDataBuffer.size ());
|
||||
|
||||
const auto begin = mDataBuffer.begin ();
|
||||
const auto end = begin + maxBytesCount;
|
||||
|
||||
std::copy (begin, end, static_cast<uint8_t*> (buffer));
|
||||
|
||||
mDataBuffer.erase (begin, end);
|
||||
|
||||
return maxBytesCount;
|
||||
}
|
||||
|
||||
void CurlResponse::perform (const void* ptr, size_t size)
|
||||
{
|
||||
CurlHandleManager::Handle handle = mHandleManager->getHandle (mVerb, mRequest.getURL ());
|
||||
|
||||
handle.setOption (CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
handle.setOption (CURLOPT_WRITEDATA, this);
|
||||
|
||||
handle.setOption (CURLOPT_HEADERFUNCTION, HeaderCallback);
|
||||
handle.setOption (CURLOPT_HEADERDATA, this);
|
||||
|
||||
handle.setOption (CURLOPT_FOLLOWLOCATION, mRequest.getMaxRedirects () == 0 ? 0 : 1);
|
||||
handle.setOption (CURLOPT_MAXREDIRS, mRequest.getMaxRedirects ());
|
||||
|
||||
handle.setOption (CURLOPT_CONNECTTIMEOUT_MS,
|
||||
std::chrono::duration_cast<std::chrono::milliseconds> (mRequest.getTimeout()).count ()
|
||||
);
|
||||
|
||||
handle.appendCookies (mRequest.getCookies ());
|
||||
|
||||
DataStream ds { reinterpret_cast<const char*>(ptr), size };
|
||||
|
||||
if (ptr != nullptr && size != 0)
|
||||
{
|
||||
handle.appendHeader ({ "Transfer-Encoding", std::string () });
|
||||
handle.appendHeader ({ "Content-Length", std::to_string (size) });
|
||||
|
||||
if (mVerb == RequestVerb::Post)
|
||||
handle.setOption (CURLOPT_POSTFIELDSIZE_LARGE, size);
|
||||
else
|
||||
handle.setOption (CURLOPT_INFILESIZE_LARGE, size);
|
||||
|
||||
handle.setOption (CURLOPT_READFUNCTION, DataStreamRead);
|
||||
handle.setOption (CURLOPT_READDATA, &ds);
|
||||
|
||||
handle.setOption (CURLOPT_SEEKFUNCTION, DataStreamSeek);
|
||||
handle.setOption (CURLOPT_SEEKDATA, &ds);
|
||||
}
|
||||
|
||||
handle.appendHeaders (mRequest.getHeaders ());
|
||||
|
||||
mCurrentHandle = &handle;
|
||||
const auto result = handle.perform ();
|
||||
mCurrentHandle = nullptr;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (mStatusMutex);
|
||||
|
||||
if (result.Code != CURLE_OK)
|
||||
{
|
||||
const auto it = errorsMap.find (result.Code);
|
||||
|
||||
mNetworkError = it != errorsMap.end () ? it->second : NetworkError::UnknownError;
|
||||
mErrorString = result.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mHttpCode == 0)
|
||||
mHttpCode = handle.getHTTPCode ();
|
||||
}
|
||||
|
||||
mRequestFinished = true;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock (mCallbackMutex);
|
||||
|
||||
if (mRequestFinishedCallback)
|
||||
mRequestFinishedCallback (this);
|
||||
|
||||
mRequestFinishedCallback = {};
|
||||
mOnDataReceivedCallback = {};
|
||||
}
|
||||
|
||||
|
||||
size_t CurlResponse::WriteCallback (const uint8_t* ptr, size_t size, size_t nmemb, CurlResponse* request) noexcept
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (request->mStatusMutex);
|
||||
|
||||
if (request->mAbortRequested)
|
||||
return 0;
|
||||
|
||||
if (!request->mHeadersReceived)
|
||||
{
|
||||
request->mHeadersReceived = true;
|
||||
|
||||
// WriteCallback is called by the handle
|
||||
assert (request->mCurrentHandle != nullptr);
|
||||
|
||||
if (request->mCurrentHandle != nullptr && request->mHttpCode == 0)
|
||||
request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
|
||||
}
|
||||
}
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (request->mDataBufferMutex);
|
||||
request->mDataBuffer.insert (request->mDataBuffer.end (), ptr, ptr + size);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock (request->mCallbackMutex);
|
||||
|
||||
if (request->mOnDataReceivedCallback)
|
||||
request->mOnDataReceivedCallback (request);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t CurlResponse::HeaderCallback (const char* buffer, size_t size, size_t nitems, CurlResponse* request) noexcept
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock (request->mStatusMutex);
|
||||
|
||||
if (request->mAbortRequested)
|
||||
return 0;
|
||||
|
||||
// HeaderCallback is called by the handle
|
||||
assert (request->mCurrentHandle != nullptr);
|
||||
|
||||
if (request->mCurrentHandle != nullptr && request->mHttpCode == 0)
|
||||
request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
|
||||
}
|
||||
|
||||
size = size * nitems;
|
||||
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
const Header header = Header::Parse (std::string (buffer, size - 2));
|
||||
|
||||
std::lock_guard<std::mutex> lock (request->mHeadersMutex);
|
||||
|
||||
if (header.hasSameName ("Set-Cookie"))
|
||||
{
|
||||
request->mResponseCookies.addCookie (Cookie::Parse (header.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (header.hasSameName ("Keep-Alive"))
|
||||
request->mCurrentHandle->markKeepAlive ();
|
||||
|
||||
request->mResponseHeaders.addHeader (header);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
98
libraries/lib-network-manager/curl/CurlResponse.h
Normal file
98
libraries/lib-network-manager/curl/CurlResponse.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlResponse.h
|
||||
@brief Declare an implementation of IResponse using libcurl.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../IResponse.h"
|
||||
|
||||
#include "../HeadersList.h"
|
||||
#include "../CookiesList.h"
|
||||
#include "../Request.h"
|
||||
|
||||
#include "CurlHandleManager.h"
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
class CurlResponse final : public IResponse
|
||||
{
|
||||
public:
|
||||
CurlResponse (RequestVerb verb, const Request& request, CurlHandleManager* handleManager) noexcept;
|
||||
|
||||
bool isFinished () const noexcept override;
|
||||
unsigned getHTTPCode () const noexcept override;
|
||||
|
||||
NetworkError getError () const noexcept override;
|
||||
std::string getErrorString () const override;
|
||||
|
||||
bool headersReceived () const noexcept override;
|
||||
|
||||
bool hasHeader (const std::string& headerName) const noexcept override;
|
||||
std::string getHeader (const std::string& headerName) const override;
|
||||
|
||||
const HeadersList& getHeaders () const noexcept override;
|
||||
const CookiesList& getCookies () const noexcept override;
|
||||
|
||||
const Request& getRequest () const noexcept override;
|
||||
|
||||
std::string getURL () const override;
|
||||
|
||||
void abort () noexcept override;
|
||||
|
||||
void setOnDataReceivedCallback (RequestCallback callback) override;
|
||||
|
||||
void setRequestFinishedCallback (RequestCallback callback) override;
|
||||
|
||||
uint64_t getBytesAvailable () const noexcept override;
|
||||
uint64_t readData (void* buffer, uint64_t maxBytesCount) override;
|
||||
|
||||
void perform (const void* ptr, size_t size);
|
||||
private:
|
||||
RequestVerb mVerb;
|
||||
|
||||
Request mRequest;
|
||||
CurlHandleManager* mHandleManager;
|
||||
|
||||
CurlHandleManager::Handle* mCurrentHandle { nullptr };
|
||||
|
||||
mutable std::mutex mCallbackMutex;
|
||||
RequestCallback mOnDataReceivedCallback;
|
||||
RequestCallback mRequestFinishedCallback;
|
||||
|
||||
mutable std::mutex mHeadersMutex;
|
||||
HeadersList mResponseHeaders;
|
||||
CookiesList mResponseCookies;
|
||||
|
||||
mutable std::mutex mDataBufferMutex;
|
||||
std::deque<uint8_t> mDataBuffer;
|
||||
|
||||
mutable std::mutex mStatusMutex;
|
||||
|
||||
NetworkError mNetworkError { NetworkError::NoError };
|
||||
std::string mErrorString;
|
||||
|
||||
unsigned mHttpCode { 0 };
|
||||
|
||||
bool mHeadersReceived { false };
|
||||
bool mRequestFinished { false };
|
||||
bool mAbortRequested { false };
|
||||
|
||||
static size_t WriteCallback (const uint8_t* ptr, size_t size, size_t nmemb, CurlResponse* userdata) noexcept;
|
||||
static size_t HeaderCallback (const char* buffer, size_t size, size_t nitems, CurlResponse* userdata) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
80
libraries/lib-network-manager/curl/CurlResponseFactory.cpp
Normal file
80
libraries/lib-network-manager/curl/CurlResponseFactory.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlResponseFactory.cpp
|
||||
@brief Define an implementation of IResponseFactory using libcurl.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#include "CurlResponseFactory.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CurlResponse.h"
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
constexpr decltype(std::thread::hardware_concurrency ()) MIN_CURL_THREADS = 6;
|
||||
|
||||
CurlResponseFactory::CurlResponseFactory ()
|
||||
: mThreadPool (std::make_unique<ThreadPool>(
|
||||
std::max (
|
||||
MIN_CURL_THREADS,
|
||||
std::thread::hardware_concurrency ()
|
||||
)))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CurlResponseFactory::setProxy (const std::string& proxy)
|
||||
{
|
||||
mHandleManager->setProxy (proxy);
|
||||
}
|
||||
|
||||
ResponsePtr CurlResponseFactory::performRequest (RequestVerb verb, const Request& request)
|
||||
{
|
||||
return performRequest (verb, request, nullptr, 0);
|
||||
}
|
||||
|
||||
ResponsePtr CurlResponseFactory::performRequest (RequestVerb verb, const Request& request, const void* data, size_t size)
|
||||
{
|
||||
if (!mThreadPool)
|
||||
return {};
|
||||
|
||||
std::shared_ptr<CurlResponse> response = std::make_shared<CurlResponse> (
|
||||
verb, request, mHandleManager.get ()
|
||||
);
|
||||
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
if (data != nullptr && size != 0)
|
||||
{
|
||||
const uint8_t* start = static_cast<const uint8_t*>(data);
|
||||
const uint8_t* end = static_cast<const uint8_t*>(data) + size;
|
||||
|
||||
buffer.insert (buffer.begin (), start, end);
|
||||
}
|
||||
|
||||
mThreadPool->enqueue ([response, dataBuffer = std::move (buffer)]() {
|
||||
if (!dataBuffer.empty())
|
||||
response->perform (dataBuffer.data (), dataBuffer.size ());
|
||||
else
|
||||
response->perform (nullptr, 0);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
void CurlResponseFactory::terminate ()
|
||||
{
|
||||
mThreadPool.reset ();
|
||||
mHandleManager.reset ();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
46
libraries/lib-network-manager/curl/CurlResponseFactory.h
Normal file
46
libraries/lib-network-manager/curl/CurlResponseFactory.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlResponseFactory.h
|
||||
@brief Declare an implementation of IResponseFactory using libcurl.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "../IResponseFactory.h"
|
||||
|
||||
#include "CurlHandleManager.h"
|
||||
#include "ThreadPool/ThreadPool.h"
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
class CurlResponseFactory final : public IResponseFactory
|
||||
{
|
||||
public:
|
||||
CurlResponseFactory ();
|
||||
|
||||
void setProxy (const std::string& proxy) override;
|
||||
|
||||
ResponsePtr performRequest (RequestVerb verb, const Request& request) override;
|
||||
ResponsePtr performRequest (RequestVerb verb, const Request& request, const void* data, size_t size) override;
|
||||
|
||||
void terminate () override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<CurlHandleManager> mHandleManager { std::make_unique<CurlHandleManager> () };
|
||||
std::unique_ptr<ThreadPool> mThreadPool;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
54
libraries/lib-network-manager/curl/CurlStringList.cpp
Normal file
54
libraries/lib-network-manager/curl/CurlStringList.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlStringList.cpp
|
||||
@brief Define a RAII wrapper for the curl_slist.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#include "CurlStringList.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
CurlStringList::CurlStringList (CurlStringList&& rhs) noexcept
|
||||
: mList (rhs.mList)
|
||||
{
|
||||
rhs.mList = nullptr;
|
||||
}
|
||||
|
||||
CurlStringList::~CurlStringList () noexcept
|
||||
{
|
||||
curl_slist_free_all (mList);
|
||||
}
|
||||
|
||||
CurlStringList& CurlStringList::operator= (CurlStringList&& rhs) noexcept
|
||||
{
|
||||
std::swap (mList, rhs.mList);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CurlStringList::append (const std::string& string) noexcept
|
||||
{
|
||||
mList = curl_slist_append (mList, string.c_str ());
|
||||
}
|
||||
|
||||
void CurlStringList::append (const char* string) noexcept
|
||||
{
|
||||
mList = curl_slist_append (mList, string);
|
||||
}
|
||||
|
||||
curl_slist* CurlStringList::getCurlList () const noexcept
|
||||
{
|
||||
return mList;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
42
libraries/lib-network-manager/curl/CurlStringList.h
Normal file
42
libraries/lib-network-manager/curl/CurlStringList.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file CurlStringList.h
|
||||
@brief Declare a RAII wrapper for the curl_slist.
|
||||
|
||||
Dmitry Vedenko
|
||||
**********************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct curl_slist;
|
||||
|
||||
namespace audacity
|
||||
{
|
||||
namespace network_manager
|
||||
{
|
||||
|
||||
class CurlStringList final
|
||||
{
|
||||
public:
|
||||
CurlStringList () = default;
|
||||
CurlStringList (CurlStringList&& rhs) noexcept;
|
||||
|
||||
~CurlStringList () noexcept;
|
||||
|
||||
CurlStringList& operator = (CurlStringList&& rhs) noexcept;
|
||||
|
||||
void append (const std::string& string) noexcept;
|
||||
void append (const char* string) noexcept;
|
||||
|
||||
curl_slist* getCurlList () const noexcept;
|
||||
|
||||
private:
|
||||
curl_slist* mList { nullptr };
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user