29 class cBlockingHTTPCallbacks :
34 explicit cBlockingHTTPCallbacks(std::shared_ptr<cEvent> a_Event,
AString & a_ResponseBody) :
35 m_Event(std::move(a_Event)), m_ResponseBody(a_ResponseBody),
47 LOGERROR(
"%s %d: HTTP Error: %s", __FILE__, __LINE__, a_ErrorMsg.c_str());
52 void OnBodyData(
const void * a_Data,
size_t a_Size)
override
54 m_ResponseBody.append(
static_cast<const char *
>(a_Data), a_Size);
57 std::shared_ptr<cEvent> m_Event;
90 a_Method, a_URL, std::move(a_Callbacks), std::move(a_Headers), a_Body, a_Options
92 return ptr->DoRequest(ptr);
103 if (
auto link =
m_Link.lock())
124 if (!cert->Parse(itr->second.data(), itr->second.size()))
126 LOGD(
"OwnCert failed to parse");
140 auto passItr =
m_Options.find(
"OwnPrivKeyPassword");
142 if (!key->ParsePrivate(itr->second.data(), itr->second.size(), pass))
153 auto itr =
m_Options.find(
"TrustedRootCAs");
158 auto Cert = std::make_shared<cX509Cert>();
159 auto Res = Cert->Parse(itr->second.data(), itr->second.size());
162 throw std::runtime_error(fmt::format(
"Failed to parse the TrustedRootCAs certificate: {}", Res));
226 std::pair<bool, AString>
DoRequest(
const std::shared_ptr<cUrlClientRequest> & a_Self);
235 m_Callbacks->OnError(fmt::format(FMT_STRING(
"Network error {} ({})"), a_ErrorCode, a_ErrorMsg));
250 virtual void OnReceivedData(
const char * a_Data,
size_t a_Length)
override;
268 class cSchemeHandler
abstract
272 m_ParentRequest(a_ParentRequest)
277 virtual ~cSchemeHandler() {}
284 virtual void OnConnected(
cTCPLink & a_Link) = 0;
287 virtual void OnReceivedData(
const char * a_Data,
size_t a_Length) = 0;
290 virtual void OnTlsHandshakeCompleted(
void) = 0;
295 virtual void OnRemoteClosed(
void) = 0;
307 public cSchemeHandler,
315 Super(a_ParentRequest),
328 m_Link->
StartTLSClient(m_ParentRequest.GetOwnCert(), m_ParentRequest.GetOwnPrivKey(), m_ParentRequest.GetTrustedRootCAs());
342 auto requestLine = m_ParentRequest.m_UrlPath;
343 if (requestLine.empty())
347 if (!m_ParentRequest.m_UrlQuery.empty())
349 requestLine.push_back(
'?');
350 requestLine.append(m_ParentRequest.m_UrlQuery);
352 m_Link->
Send(fmt::format(FMT_STRING(
"{} {} HTTP/1.1\r\n"), m_ParentRequest.m_Method, requestLine));
355 m_Link->
Send(fmt::format(FMT_STRING(
"Host: {}\r\n"), m_ParentRequest.m_UrlHost));
356 m_Link->
Send(fmt::format(FMT_STRING(
"Content-Length: {}\r\n"), m_ParentRequest.m_Body.size()));
357 for (
const auto & hdr: m_ParentRequest.m_Headers)
359 m_Link->
Send(fmt::format(FMT_STRING(
"{}: {}\r\n"), hdr.first, hdr.second));
367 m_ParentRequest.GetCallbacks().OnRequestSent();
374 if (res == AString::npos)
376 m_ParentRequest.CallErrorCallback(
"Failed to parse HTTP response");
398 m_ParentRequest.CallErrorCallback(a_ErrorDescription);
405 auto idxFirstSpace = a_FirstLine.find(
' ');
406 if (idxFirstSpace == AString::npos)
408 m_ParentRequest.CallErrorCallback(fmt::format(FMT_STRING(
"Failed to parse HTTP status line \"{}\", no space delimiter."), a_FirstLine));
411 auto idxSecondSpace = a_FirstLine.find(
' ', idxFirstSpace + 1);
412 if (idxSecondSpace == AString::npos)
414 m_ParentRequest.CallErrorCallback(fmt::format(FMT_STRING(
"Failed to parse HTTP status line \"{}\", missing second space delimiter."), a_FirstLine));
418 auto resultCodeStr = a_FirstLine.substr(idxFirstSpace + 1, idxSecondSpace - idxFirstSpace - 1);
421 m_ParentRequest.CallErrorCallback(fmt::format(FMT_STRING(
"Failed to parse HTTP result code from response \"{}\""), resultCodeStr));
438 m_ParentRequest.GetCallbacks().OnStatusLine(a_FirstLine.substr(0, idxFirstSpace), resultCode, a_FirstLine.substr(idxSecondSpace + 1));
446 if (a_Key ==
"Location")
453 m_ParentRequest.GetCallbacks().OnHeader(a_Key, a_Value);
463 m_ParentRequest.GetCallbacks().OnHeadersFinished();
469 virtual void OnBodyData(
const void * a_Data,
size_t a_Size)
override
473 m_ParentRequest.GetCallbacks().OnBodyData(a_Data, a_Size);
485 m_ParentRequest.CallErrorCallback(
"Invalid redirect, there's no location to redirect to");
494 m_ParentRequest.GetCallbacks().OnBodyFinished();
531 if (lowerScheme ==
"http")
533 return std::make_shared<cHttpSchemeHandler>(a_ParentRequest,
false);
535 else if (lowerScheme ==
"https")
537 return std::make_shared<cHttpSchemeHandler>(a_ParentRequest,
true);
556 CallErrorCallback(fmt::format(FMT_STRING(
"Redirect to \"{}\" not allowed"), a_RedirectUrl));
561 auto Self =
m_Self.lock();
564 if (
auto Link =
m_Link.lock())
569 m_Url = a_RedirectUrl;
574 m_Callbacks->OnError(fmt::format(FMT_STRING(
"Redirection failed: {}"), res.second));
615 if (handler !=
nullptr)
617 handler->OnReceivedData(a_Data, a_Length);
629 if (handler !=
nullptr)
631 handler->OnRemoteClosed();
642 ASSERT(a_Self.get() ==
this);
657 return std::make_pair(
false, fmt::format(FMT_STRING(
"Unknown URL scheme: {}"),
m_UrlScheme));
663 return std::make_pair(
false,
"Network connection failed");
665 return std::make_pair(
true,
AString());
685 a_Method, a_URL, std::move(a_Callbacks), std::move(a_Headers), a_Body, a_Options
702 "GET", a_URL, std::move(a_Callbacks), std::move(a_Headers), a_Body, a_Options
719 "POST", a_URL, std::move(a_Callbacks), std::move(a_Headers), a_Body, a_Options
736 "PUT", a_URL, std::move(a_Callbacks), std::move(a_Headers), a_Body, a_Options
752 auto EvtFinished = std::make_shared<cEvent>();
754 auto Callbacks = std::make_shared<cBlockingHTTPCallbacks>(EvtFinished, Response);
755 auto [Success, ErrorMessage] =
cUrlClient::Request(a_Method, a_URL, Callbacks, std::move(a_Headers), a_Body, a_Options);
758 if (!EvtFinished->Wait(10000))
760 return std::make_pair(
false,
"Timeout");
762 if (Callbacks->m_IsError)
764 return std::make_pair(
false,
AString());
769 LOGWARNING(
"%s: HTTP error: %s", __FUNCTION__, ErrorMessage.c_str());
770 return std::make_pair(
false,
AString());
772 return std::make_pair(
true, Response);
786 return BlockingRequest(
"GET", a_URL, std::move(a_Headers), a_Body, a_Options);
800 return BlockingRequest(
"POST", a_URL, std::move(a_Headers), a_Body, a_Options);
814 return BlockingRequest(
"PUT", a_URL, std::move(a_Headers), a_Body, a_Options);
std::shared_ptr< cSchemeHandler > cSchemeHandlerPtr
void LOGERROR(std::string_view a_Format, const Args &... args)
void LOGWARNING(std::string_view a_Format, const Args &... args)
std::shared_ptr< cCryptoKey > cCryptoKeyPtr
std::shared_ptr< cX509Cert > cX509CertPtr
std::shared_ptr< cTCPLink > cTCPLinkPtr
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
T GetStringMapInteger(const AStringMap &a_Map, const AString &a_Key, T a_Default)
Returns a number (of any integer type T) from a key-value string map.
bool StringToInteger(const AString &a_str, T &a_Num)
Parses any integer type.
std::map< AString, AString > AStringMap
A string dictionary, used for key-value pairs.
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data and calls the appropriate callbacks.
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
Called when the cTCPLink for the connection is created.
bool ShouldAllowRedirects() const
virtual void OnConnected(cTCPLink &a_Link) override
Called when the Connect call succeeds.
cX509CertPtr GetOwnCert() const
cUrlClient::cCallbacksPtr m_Callbacks
Callbacks that report progress and results of the request.
virtual void OnReceivedData(const char *a_Data, size_t a_Length) override
Called when there's data incoming from the remote peer.
static std::pair< bool, AString > Request(const AString &a_Method, const AString &a_URL, cUrlClient::cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options)
virtual void OnError(int a_ErrorCode, const AString &a_ErrorMsg) override
Called when an error is detected on the connection.
virtual void OnTlsHandshakeCompleted(void) override
Called when the TLS handshake has been completed and communication can continue regularly.
AString m_UrlScheme
Individual components of the URL that will be requested.
void CallErrorCallback(const AString &a_ErrorMessage)
Calls the error callback with the specified message, if it exists, and terminates the request.
AString m_Body
Body to be sent with the request, if any.
cX509CertPtr GetTrustedRootCAs() const
Returns the parsed TrustedRootCAs from the options, or an empty pointer if the option is not set.
std::weak_ptr< cTCPLink > m_Link
The link handling the request.
std::weak_ptr< cUrlClientRequest > m_Self
weak_ptr to self, so that this object can keep itself alive as needed by calling lock(),...
AStringMap m_Options
Extra options to be used for the request.
AString m_Method
Method to be used for the request.
cUrlClientRequest(const AString &a_Method, const AString &a_Url, cUrlClient::cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options)
virtual void OnRemoteClosed(void) override
Called when the remote end closes the connection.
void RedirectTo(const AString &a_RedirectUrl)
std::pair< bool, AString > DoRequest(const std::shared_ptr< cUrlClientRequest > &a_Self)
cCryptoKeyPtr GetOwnPrivKey() const
AString m_Url
URL that will be requested.
int m_NumRemainingRedirects
The number of redirect attempts that will still be followed.
std::shared_ptr< cSchemeHandler > m_SchemeHandler
The handler that "talks" the protocol specified in m_UrlScheme, handles all the sending and receiving...
AStringMap m_Headers
Extra headers to be sent with the request (besides the normal ones).
cUrlClient::cCallbacks & GetCallbacks()
cSchemeHandler descendant that handles HTTP (and HTTPS) requests.
virtual void OnFirstLine(const AString &a_FirstLine) override
Called when the first line of the request or response is fully parsed.
virtual void OnReceivedData(const char *a_Data, size_t a_Length) override
virtual void OnHeadersFinished(void) override
Called when all the headers have been parsed.
cHTTPMessageParser m_Parser
Parser of the HTTP response message.
bool m_IsRedirect
Set to true if the first line contains a redirecting HTTP status code and the options specify to foll...
AString m_RedirectLocation
The Location where the request should be redirected.
virtual void OnBodyData(const void *a_Data, size_t a_Size) override
Called for each chunk of the incoming body data.
virtual void OnError(const AString &a_ErrorDescription) override
Called when an error has occured while parsing.
void SendRequest()
Sends the HTTP request over the link.
virtual void OnTlsHandshakeCompleted(void) override
bool m_IsTls
If true, the TLS should be started on the link before sending the request (used for https).
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a single header line is parsed.
virtual void OnRemoteClosed(void) override
virtual void OnBodyFinished(void) override
Called when the entire body has been reported by OnBodyData().
cTCPLink * m_Link
The network link.
virtual void OnConnected(cTCPLink &a_Link) override
cHttpSchemeHandler(cUrlClientRequest &a_ParentRequest, bool a_IsTls)
static std::pair< bool, AString > BlockingPut(const AString &a_URL, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options={})
Alias for BlockingRequest("PUT", ...)
static std::pair< bool, AString > BlockingRequest(const AString &a_Method, const AString &a_URL, AStringMap &&a_Headers={}, const AString &a_Body={}, const AStringMap &a_Options={})
Sends a generic request and block until a response is received or an error occurs.
std::shared_ptr< cCallbacks > cCallbacksPtr
static std::pair< bool, AString > Put(const AString &a_URL, cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options={})
Alias for Request("PUT", ...)
static std::pair< bool, AString > Request(const AString &a_Method, const AString &a_URL, cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options)
Makes a network request to the specified URL, using the specified method (if applicable).
@ HTTP_STATUS_MULTIPLE_CHOICES
@ HTTP_STATUS_MOVED_PERMANENTLY
@ HTTP_STATUS_TEMPORARY_REDIRECT
static std::pair< bool, AString > BlockingPost(const AString &a_URL, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options={})
Alias for BlockingRequest("POST", ...)
static std::pair< bool, AString > BlockingGet(const AString &a_URL, AStringMap a_Headers={}, const AString &a_Body={}, const AStringMap &a_Options={})
Alias for BlockingRequest("GET", ...)
static std::pair< bool, AString > Post(const AString &a_URL, cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers, const AString &a_Body, const AStringMap &a_Options={})
Alias for Request("POST", ...)
static std::pair< bool, AString > Get(const AString &a_URL, cCallbacksPtr &&a_Callbacks, AStringMap &&a_Headers={}, const AString &a_Body={}, const AStringMap &a_Options={})
Alias for Request("GET", ...)
Callbacks that are used for progress and result reporting.
virtual void OnBodyData(const void *a_Data, size_t a_Size)
Called when the next fragment of the response body is received, unless the response is an allowed red...
virtual void OnError(const AString &a_ErrorMsg)
Called when an asynchronous error is encountered.
virtual void OnBodyFinished()
Called after the response body has been fully reported by OnBody() calls, unless the response is an a...
static std::pair< bool, AString > Parse(const AString &a_Url, AString &a_Scheme, AString &a_Username, AString &a_Password, AString &a_Host, UInt16 &a_Port, AString &a_Path, AString &a_Query, AString &a_Fragment)
Parses the given URL into individual components.
Interface that provides the methods available on a single TCP connection.
virtual AString StartTLSClient(cX509CertPtr a_OwnCert, cCryptoKeyPtr a_OwnPrivKey, cX509CertPtr a_TrustedRootCAs)=0
Starts a TLS handshake as a client connection.
virtual bool Send(const void *a_Data, size_t a_Length)=0
Queues the specified data for sending to the remote peer.
virtual void Shutdown(void)=0
Closes the link gracefully.
static bool Connect(const AString &a_Host, UInt16 a_Port, cConnectCallbacksPtr a_ConnectCallbacks, cTCPLink::cCallbacksPtr a_LinkCallbacks)
Queues a TCP connection to be made to the specified host.
Callbacks used for connecting to other servers as a client.