#pragma once #include "http_client_options.h" #include #include #include #include #include #include #include #include #include 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; 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 THttpCode DoRequestReliable(const T& raw, IOutputStream* output, THttpHeaders* outHeaders); TVector 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 Connection; bool IsClosingRequired; TMaybe ClientCertificate; TMaybe HttpsVerification; private: THttpInput* GetHttpInput(); using TIfResponseRequired = std::function; 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: using TOptions = TSimpleHttpClientOptions; explicit TRedirectableHttpClient(const TOptions& options); 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; private: TOptions Opts; }; namespace NPrivate { class THttpConnection { public: THttpConnection(const TString& host, ui32 port, TDuration sockTimeout, TDuration connTimeout, bool isHttps, const TMaybe& clientCert, const TMaybe& verifyCert); bool IsOk() const { return IsNotSocketClosedByOtherSide(Socket); } template void Write(const TContainer& request) { HttpOut->Write(request.data(), request.size()); HttpIn = Ssl ? MakeHolder(Ssl.Get()) : MakeHolder(&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 Ssl; THolder HttpIn; THolder HttpOut; }; } template 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_ABORT(); // We should never be here. return 0; }