diff options
author | Daniil Cherednik <[email protected]> | 2022-11-24 13:14:34 +0300 |
---|---|---|
committer | Daniil Cherednik <[email protected]> | 2022-11-24 14:46:00 +0300 |
commit | 87f7fceed34bcafb8aaff351dd493a35c916986f (patch) | |
tree | 26809ec8f550aba8eb019e59adc3d48e51913eb2 /library/cpp/http/simple/http_client.h | |
parent | 11bc4015b8010ae201bf3eb33db7dba425aca35e (diff) |
Ydb stable 22-4-4322.4.43
x-stable-origin-commit: 8d49d46cc834835bf3e50870516acd7376a63bcf
Diffstat (limited to 'library/cpp/http/simple/http_client.h')
-rw-r--r-- | library/cpp/http/simple/http_client.h | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/library/cpp/http/simple/http_client.h b/library/cpp/http/simple/http_client.h new file mode 100644 index 00000000000..94ee487202b --- /dev/null +++ b/library/cpp/http/simple/http_client.h @@ -0,0 +1,276 @@ +#pragma once + +#include "http_client_options.h" + +#include <util/datetime/base.h> +#include <util/generic/hash.h> +#include <util/generic/ptr.h> +#include <util/generic/strbuf.h> +#include <util/generic/yexception.h> +#include <util/network/socket.h> + +#include <library/cpp/http/io/stream.h> +#include <library/cpp/http/misc/httpcodes.h> +#include <library/cpp/openssl/io/stream.h> + +class TNetworkAddress; +class IOutputStream; +class TSocket; + +namespace NPrivate { + class THttpConnection; +} + +/*! + * HTTPS is supported in two modes. + * HTTPS verification enabled by default in TKeepAliveHttpClient and disabled by default in TSimpleHttpClient. + * HTTPS verification requires valid private certificate on server side and valid public certificate on client side. + * + * For client: + * Uses builtin certs. + * Also uses default CA path /etc/ssl/certs/ - can be provided with debian package: ca-certificates.deb. + * It can be expanded with ENV: SSL_CERT_DIR. + */ + +/*! + * TKeepAliveHttpClient can keep connection alive with HTTP and HTTPS only if you use the same instance of class. + * It closes connection on every socket/network error and throws error. + * For example, HTTP code == 500 is NOT error - connection will be still open. + * It is THREAD UNSAFE because it stores connection state in attributes. + * If you need thread safe client, look at TSimpleHttpClient + */ + +class TKeepAliveHttpClient { +public: + using THeaders = THashMap<TString, TString>; + using THttpCode = unsigned; + +public: + TKeepAliveHttpClient(const TString& host, + ui32 port, + TDuration socketTimeout = TDuration::Seconds(5), + TDuration connectTimeout = TDuration::Seconds(30)); + + THttpCode DoGet(const TStringBuf relativeUrl, + IOutputStream* output = nullptr, + const THeaders& headers = THeaders(), + THttpHeaders* outHeaders = nullptr); + + // builds post request from headers and body + THttpCode DoPost(const TStringBuf relativeUrl, + const TStringBuf body, + IOutputStream* output = nullptr, + const THeaders& headers = THeaders(), + THttpHeaders* outHeaders = nullptr); + + // builds request with any HTTP method from headers and body + THttpCode DoRequest(const TStringBuf method, + const TStringBuf relativeUrl, + const TStringBuf body, + IOutputStream* output = nullptr, + const THeaders& inHeaders = THeaders(), + THttpHeaders* outHeaders = nullptr); + + // requires already well-formed request + THttpCode DoRequestRaw(const TStringBuf raw, + IOutputStream* output = nullptr, + THttpHeaders* outHeaders = nullptr); + + void DisableVerificationForHttps(); + void SetClientCertificate(const TOpenSslClientIO::TOptions::TClientCert& options); + + void ResetConnection(); + + const TString& GetHost() const { + return Host; + } + + ui32 GetPort() const { + return Port; + } + +private: + template <class T> + THttpCode DoRequestReliable(const T& raw, + IOutputStream* output, + THttpHeaders* outHeaders); + + TVector<IOutputStream::TPart> FormRequest(TStringBuf method, const TStringBuf relativeUrl, + TStringBuf body, + const THeaders& headers, TStringBuf contentLength) const; + + THttpCode ReadAndTransferHttp(THttpInput& input, IOutputStream* output, THttpHeaders* outHeaders) const; + + bool CreateNewConnectionIfNeeded(); // Returns true if now we have a new connection. + +private: + using TVerifyCert = TOpenSslClientIO::TOptions::TVerifyCert; + using TClientCert = TOpenSslClientIO::TOptions::TClientCert; + + const TString Host; + const ui32 Port; + const TDuration SocketTimeout; + const TDuration ConnectTimeout; + const bool IsHttps; + + THolder<NPrivate::THttpConnection> Connection; + bool IsClosingRequired; + TMaybe<TClientCert> ClientCertificate; + TMaybe<TVerifyCert> HttpsVerification; + +private: + THttpInput* GetHttpInput(); + + using TIfResponseRequired = std::function<bool(const THttpInput&)>; + TIfResponseRequired IfResponseRequired; + + friend class TSimpleHttpClient; + friend class TRedirectableHttpClient; +}; + +class THttpRequestException: public yexception { +private: + int StatusCode; + +public: + THttpRequestException(int statusCode = 0); + int GetStatusCode() const; +}; + +/*! + * TSimpleHttpClient can NOT keep connection alive. + * It closes connection after each request. + * HTTP code < 200 || code >= 300 is error - exception will be thrown. + * It is THREAD SAFE because it stores only consts. + */ + +class TSimpleHttpClient { +protected: + using TVerifyCert = TKeepAliveHttpClient::TVerifyCert; + + const TString Host; + const ui32 Port; + const TDuration SocketTimeout; + const TDuration ConnectTimeout; + bool HttpsVerification = false; + +public: + using THeaders = TKeepAliveHttpClient::THeaders; + using TOptions = TSimpleHttpClientOptions; + +public: + explicit TSimpleHttpClient(const TOptions& options); + + TSimpleHttpClient(const TString& host, ui32 port, + TDuration socketTimeout = TDuration::Seconds(5), TDuration connectTimeout = TDuration::Seconds(30)); + + void EnableVerificationForHttps(); + + void DoGet(const TStringBuf relativeUrl, IOutputStream* output, const THeaders& headers = THeaders()) const; + + // builds post request from headers and body + void DoPost(const TStringBuf relativeUrl, TStringBuf body, IOutputStream* output, const THeaders& headers = THeaders()) const; + + // requires already well-formed post request + void DoPostRaw(const TStringBuf relativeUrl, TStringBuf rawRequest, IOutputStream* output) const; + + virtual ~TSimpleHttpClient(); + +private: + TKeepAliveHttpClient CreateClient() const; + + virtual void PrepareClient(TKeepAliveHttpClient& cl) const; + virtual void ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream* output, const unsigned statusCode) const; +}; + +class TRedirectableHttpClient: public TSimpleHttpClient { +public: + TRedirectableHttpClient(const TString& host, ui32 port, TDuration socketTimeout = TDuration::Seconds(5), + TDuration connectTimeout = TDuration::Seconds(30)); + +private: + void PrepareClient(TKeepAliveHttpClient& cl) const override; + void ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream* output, const unsigned statusCode) const override; +}; + +namespace NPrivate { + class THttpConnection { + public: + THttpConnection(const TString& host, + ui32 port, + TDuration sockTimeout, + TDuration connTimeout, + bool isHttps, + const TMaybe<TOpenSslClientIO::TOptions::TClientCert>& clientCert, + const TMaybe<TOpenSslClientIO::TOptions::TVerifyCert>& verifyCert); + + bool IsOk() const { + return IsNotSocketClosedByOtherSide(Socket); + } + + template <typename TContainer> + void Write(const TContainer& request) { + HttpOut->Write(request.data(), request.size()); + HttpIn = Ssl ? MakeHolder<THttpInput>(Ssl.Get()) + : MakeHolder<THttpInput>(&SocketIn); + HttpOut->Flush(); + } + + THttpInput* GetHttpInput() { + return HttpIn.Get(); + } + + private: + static TNetworkAddress Resolve(const TString& host, ui32 port); + + static TSocket Connect(TNetworkAddress& addr, + TDuration sockTimeout, + TDuration connTimeout, + const TString& host, + ui32 port); + + private: + TNetworkAddress Addr; + TSocket Socket; + TSocketInput SocketIn; + TSocketOutput SocketOut; + THolder<TOpenSslClientIO> Ssl; + THolder<THttpInput> HttpIn; + THolder<THttpOutput> HttpOut; + }; +} + +template <class T> +TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoRequestReliable(const T& raw, + IOutputStream* output, + THttpHeaders* outHeaders) { + for (int i = 0; i < 2; ++i) { + const bool haveNewConnection = CreateNewConnectionIfNeeded(); + const bool couldRetry = !haveNewConnection && i == 0; // Actually old connection could be already closed by server, + // so we should try one more time in this case. + try { + Connection->Write(raw); + + THttpCode code = ReadAndTransferHttp(*Connection->GetHttpInput(), output, outHeaders); + if (!Connection->GetHttpInput()->IsKeepAlive()) { + IsClosingRequired = true; + } + return code; + } catch (const TSystemError& e) { + Connection.Reset(); + if (!couldRetry || e.Status() != EPIPE) { + throw; + } + } catch (const THttpReadException&) { // Actually old connection is already closed by server + Connection.Reset(); + if (!couldRetry) { + throw; + } + } catch (const std::exception&) { + Connection.Reset(); + throw; + } + } + Y_FAIL(); // We should never be here. + return 0; +} |