http_client.h 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #pragma once
  2. #include "http_client_options.h"
  3. #include <util/datetime/base.h>
  4. #include <util/generic/hash.h>
  5. #include <util/generic/ptr.h>
  6. #include <util/generic/strbuf.h>
  7. #include <util/generic/yexception.h>
  8. #include <util/network/socket.h>
  9. #include <library/cpp/http/io/stream.h>
  10. #include <library/cpp/http/misc/httpcodes.h>
  11. #include <library/cpp/openssl/io/stream.h>
  12. class TNetworkAddress;
  13. class IOutputStream;
  14. class TSocket;
  15. namespace NPrivate {
  16. class THttpConnection;
  17. }
  18. /*!
  19. * HTTPS is supported in two modes.
  20. * HTTPS verification enabled by default in TKeepAliveHttpClient and disabled by default in TSimpleHttpClient.
  21. * HTTPS verification requires valid private certificate on server side and valid public certificate on client side.
  22. *
  23. * For client:
  24. * Uses builtin certs.
  25. * Also uses default CA path /etc/ssl/certs/ - can be provided with debian package: ca-certificates.deb.
  26. * It can be expanded with ENV: SSL_CERT_DIR.
  27. */
  28. /*!
  29. * TKeepAliveHttpClient can keep connection alive with HTTP and HTTPS only if you use the same instance of class.
  30. * It closes connection on every socket/network error and throws error.
  31. * For example, HTTP code == 500 is NOT error - connection will be still open.
  32. * It is THREAD UNSAFE because it stores connection state in attributes.
  33. * If you need thread safe client, look at TSimpleHttpClient
  34. */
  35. class TKeepAliveHttpClient {
  36. public:
  37. using THeaders = THashMap<TString, TString>;
  38. using THttpCode = unsigned;
  39. public:
  40. TKeepAliveHttpClient(const TString& host,
  41. ui32 port,
  42. TDuration socketTimeout = TDuration::Seconds(5),
  43. TDuration connectTimeout = TDuration::Seconds(30));
  44. THttpCode DoGet(const TStringBuf relativeUrl,
  45. IOutputStream* output = nullptr,
  46. const THeaders& headers = THeaders(),
  47. THttpHeaders* outHeaders = nullptr);
  48. // builds post request from headers and body
  49. THttpCode DoPost(const TStringBuf relativeUrl,
  50. const TStringBuf body,
  51. IOutputStream* output = nullptr,
  52. const THeaders& headers = THeaders(),
  53. THttpHeaders* outHeaders = nullptr);
  54. // builds request with any HTTP method from headers and body
  55. THttpCode DoRequest(const TStringBuf method,
  56. const TStringBuf relativeUrl,
  57. const TStringBuf body,
  58. IOutputStream* output = nullptr,
  59. const THeaders& inHeaders = THeaders(),
  60. THttpHeaders* outHeaders = nullptr);
  61. // requires already well-formed request
  62. THttpCode DoRequestRaw(const TStringBuf raw,
  63. IOutputStream* output = nullptr,
  64. THttpHeaders* outHeaders = nullptr);
  65. void DisableVerificationForHttps();
  66. void SetClientCertificate(const TOpenSslClientIO::TOptions::TClientCert& options);
  67. void ResetConnection();
  68. const TString& GetHost() const {
  69. return Host;
  70. }
  71. ui32 GetPort() const {
  72. return Port;
  73. }
  74. private:
  75. template <class T>
  76. THttpCode DoRequestReliable(const T& raw,
  77. IOutputStream* output,
  78. THttpHeaders* outHeaders);
  79. TVector<IOutputStream::TPart> FormRequest(TStringBuf method, const TStringBuf relativeUrl,
  80. TStringBuf body,
  81. const THeaders& headers, TStringBuf contentLength) const;
  82. THttpCode ReadAndTransferHttp(THttpInput& input, IOutputStream* output, THttpHeaders* outHeaders) const;
  83. bool CreateNewConnectionIfNeeded(); // Returns true if now we have a new connection.
  84. private:
  85. using TVerifyCert = TOpenSslClientIO::TOptions::TVerifyCert;
  86. using TClientCert = TOpenSslClientIO::TOptions::TClientCert;
  87. const TString Host;
  88. const ui32 Port;
  89. const TDuration SocketTimeout;
  90. const TDuration ConnectTimeout;
  91. const bool IsHttps;
  92. THolder<NPrivate::THttpConnection> Connection;
  93. bool IsClosingRequired;
  94. TMaybe<TClientCert> ClientCertificate;
  95. TMaybe<TVerifyCert> HttpsVerification;
  96. private:
  97. THttpInput* GetHttpInput();
  98. using TIfResponseRequired = std::function<bool(const THttpInput&)>;
  99. TIfResponseRequired IfResponseRequired;
  100. friend class TSimpleHttpClient;
  101. friend class TRedirectableHttpClient;
  102. };
  103. class THttpRequestException: public yexception {
  104. private:
  105. int StatusCode;
  106. public:
  107. THttpRequestException(int statusCode = 0);
  108. int GetStatusCode() const;
  109. };
  110. /*!
  111. * TSimpleHttpClient can NOT keep connection alive.
  112. * It closes connection after each request.
  113. * HTTP code < 200 || code >= 300 is error - exception will be thrown.
  114. * It is THREAD SAFE because it stores only consts.
  115. */
  116. class TSimpleHttpClient {
  117. protected:
  118. using TVerifyCert = TKeepAliveHttpClient::TVerifyCert;
  119. const TString Host;
  120. const ui32 Port;
  121. const TDuration SocketTimeout;
  122. const TDuration ConnectTimeout;
  123. bool HttpsVerification = false;
  124. public:
  125. using THeaders = TKeepAliveHttpClient::THeaders;
  126. using TOptions = TSimpleHttpClientOptions;
  127. public:
  128. explicit TSimpleHttpClient(const TOptions& options);
  129. TSimpleHttpClient(const TString& host, ui32 port,
  130. TDuration socketTimeout = TDuration::Seconds(5), TDuration connectTimeout = TDuration::Seconds(30));
  131. void EnableVerificationForHttps();
  132. void DoGet(const TStringBuf relativeUrl, IOutputStream* output, const THeaders& headers = THeaders()) const;
  133. // builds post request from headers and body
  134. void DoPost(const TStringBuf relativeUrl, TStringBuf body, IOutputStream* output, const THeaders& headers = THeaders()) const;
  135. // requires already well-formed post request
  136. void DoPostRaw(const TStringBuf relativeUrl, TStringBuf rawRequest, IOutputStream* output) const;
  137. virtual ~TSimpleHttpClient();
  138. private:
  139. TKeepAliveHttpClient CreateClient() const;
  140. virtual void PrepareClient(TKeepAliveHttpClient& cl) const;
  141. virtual void ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream* output, const unsigned statusCode) const;
  142. };
  143. class TRedirectableHttpClient: public TSimpleHttpClient {
  144. public:
  145. TRedirectableHttpClient(const TString& host, ui32 port, TDuration socketTimeout = TDuration::Seconds(5),
  146. TDuration connectTimeout = TDuration::Seconds(30));
  147. private:
  148. void PrepareClient(TKeepAliveHttpClient& cl) const override;
  149. void ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream* output, const unsigned statusCode) const override;
  150. };
  151. namespace NPrivate {
  152. class THttpConnection {
  153. public:
  154. THttpConnection(const TString& host,
  155. ui32 port,
  156. TDuration sockTimeout,
  157. TDuration connTimeout,
  158. bool isHttps,
  159. const TMaybe<TOpenSslClientIO::TOptions::TClientCert>& clientCert,
  160. const TMaybe<TOpenSslClientIO::TOptions::TVerifyCert>& verifyCert);
  161. bool IsOk() const {
  162. return IsNotSocketClosedByOtherSide(Socket);
  163. }
  164. template <typename TContainer>
  165. void Write(const TContainer& request) {
  166. HttpOut->Write(request.data(), request.size());
  167. HttpIn = Ssl ? MakeHolder<THttpInput>(Ssl.Get())
  168. : MakeHolder<THttpInput>(&SocketIn);
  169. HttpOut->Flush();
  170. }
  171. THttpInput* GetHttpInput() {
  172. return HttpIn.Get();
  173. }
  174. private:
  175. static TNetworkAddress Resolve(const TString& host, ui32 port);
  176. static TSocket Connect(TNetworkAddress& addr,
  177. TDuration sockTimeout,
  178. TDuration connTimeout,
  179. const TString& host,
  180. ui32 port);
  181. private:
  182. TNetworkAddress Addr;
  183. TSocket Socket;
  184. TSocketInput SocketIn;
  185. TSocketOutput SocketOut;
  186. THolder<TOpenSslClientIO> Ssl;
  187. THolder<THttpInput> HttpIn;
  188. THolder<THttpOutput> HttpOut;
  189. };
  190. }
  191. template <class T>
  192. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoRequestReliable(const T& raw,
  193. IOutputStream* output,
  194. THttpHeaders* outHeaders) {
  195. for (int i = 0; i < 2; ++i) {
  196. const bool haveNewConnection = CreateNewConnectionIfNeeded();
  197. const bool couldRetry = !haveNewConnection && i == 0; // Actually old connection could be already closed by server,
  198. // so we should try one more time in this case.
  199. try {
  200. Connection->Write(raw);
  201. THttpCode code = ReadAndTransferHttp(*Connection->GetHttpInput(), output, outHeaders);
  202. if (!Connection->GetHttpInput()->IsKeepAlive()) {
  203. IsClosingRequired = true;
  204. }
  205. return code;
  206. } catch (const TSystemError& e) {
  207. Connection.Reset();
  208. if (!couldRetry || e.Status() != EPIPE) {
  209. throw;
  210. }
  211. } catch (const THttpReadException&) { // Actually old connection is already closed by server
  212. Connection.Reset();
  213. if (!couldRetry) {
  214. throw;
  215. }
  216. } catch (const std::exception&) {
  217. Connection.Reset();
  218. throw;
  219. }
  220. }
  221. Y_ABORT(); // We should never be here.
  222. return 0;
  223. }