http_client.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. #include "http_client.h"
  2. #include <library/cpp/string_utils/url/url.h>
  3. #include <util/stream/output.h>
  4. #include <util/string/cast.h>
  5. #include <util/string/join.h>
  6. #include <util/string/split.h>
  7. TKeepAliveHttpClient::TKeepAliveHttpClient(const TString& host,
  8. ui32 port,
  9. TDuration socketTimeout,
  10. TDuration connectTimeout)
  11. : Host(CutHttpPrefix(host))
  12. , Port(port)
  13. , SocketTimeout(socketTimeout)
  14. , ConnectTimeout(connectTimeout)
  15. , IsHttps(host.StartsWith("https"))
  16. , IsClosingRequired(false)
  17. , HttpsVerification(TVerifyCert{Host})
  18. , IfResponseRequired([](const THttpInput&) { return true; })
  19. {
  20. }
  21. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoGet(const TStringBuf relativeUrl,
  22. IOutputStream* output,
  23. const THeaders& headers,
  24. THttpHeaders* outHeaders) {
  25. return DoRequest(TStringBuf("GET"),
  26. relativeUrl,
  27. {},
  28. output,
  29. headers,
  30. outHeaders);
  31. }
  32. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoPost(const TStringBuf relativeUrl,
  33. const TStringBuf body,
  34. IOutputStream* output,
  35. const THeaders& headers,
  36. THttpHeaders* outHeaders) {
  37. return DoRequest(TStringBuf("POST"),
  38. relativeUrl,
  39. body,
  40. output,
  41. headers,
  42. outHeaders);
  43. }
  44. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoRequest(const TStringBuf method,
  45. const TStringBuf relativeUrl,
  46. const TStringBuf body,
  47. IOutputStream* output,
  48. const THeaders& inHeaders,
  49. THttpHeaders* outHeaders) {
  50. const TString contentLength = IntToString<10, size_t>(body.size());
  51. return DoRequestReliable(FormRequest(method, relativeUrl, body, inHeaders, contentLength), output, outHeaders);
  52. }
  53. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::DoRequestRaw(const TStringBuf raw,
  54. IOutputStream* output,
  55. THttpHeaders* outHeaders) {
  56. return DoRequestReliable(raw, output, outHeaders);
  57. }
  58. void TKeepAliveHttpClient::DisableVerificationForHttps() {
  59. HttpsVerification.Clear();
  60. Connection.Reset();
  61. }
  62. void TKeepAliveHttpClient::SetClientCertificate(const TOpenSslClientIO::TOptions::TClientCert& options) {
  63. ClientCertificate = options;
  64. }
  65. void TKeepAliveHttpClient::ResetConnection() {
  66. Connection.Reset();
  67. }
  68. TVector<IOutputStream::TPart> TKeepAliveHttpClient::FormRequest(TStringBuf method,
  69. const TStringBuf relativeUrl,
  70. TStringBuf body,
  71. const TKeepAliveHttpClient::THeaders& headers,
  72. TStringBuf contentLength) const {
  73. TVector<IOutputStream::TPart> parts;
  74. parts.reserve(16 + 4 * headers.size());
  75. parts.push_back(method);
  76. parts.push_back(TStringBuf(" "));
  77. parts.push_back(relativeUrl);
  78. parts.push_back(TStringBuf(" HTTP/1.1"));
  79. parts.push_back(IOutputStream::TPart::CrLf());
  80. parts.push_back(TStringBuf("Host: "));
  81. parts.push_back(TStringBuf(Host));
  82. parts.push_back(IOutputStream::TPart::CrLf());
  83. parts.push_back(TStringBuf("Content-Length: "));
  84. parts.push_back(contentLength);
  85. parts.push_back(IOutputStream::TPart::CrLf());
  86. for (const auto& entry : headers) {
  87. parts.push_back(IOutputStream::TPart(entry.first));
  88. parts.push_back(IOutputStream::TPart(TStringBuf(": ")));
  89. parts.push_back(IOutputStream::TPart(entry.second));
  90. parts.push_back(IOutputStream::TPart::CrLf());
  91. }
  92. parts.push_back(IOutputStream::TPart::CrLf());
  93. if (body) {
  94. parts.push_back(IOutputStream::TPart(body));
  95. }
  96. return parts;
  97. }
  98. TKeepAliveHttpClient::THttpCode TKeepAliveHttpClient::ReadAndTransferHttp(THttpInput& input,
  99. IOutputStream* output,
  100. THttpHeaders* outHeaders) const {
  101. TKeepAliveHttpClient::THttpCode statusCode;
  102. try {
  103. statusCode = ParseHttpRetCode(input.FirstLine());
  104. } catch (TFromStringException& e) {
  105. TString rest = input.ReadAll();
  106. ythrow THttpRequestException() << "Failed parse status code in response of " << Host << ": " << e.what() << " (" << input.FirstLine() << ")"
  107. << "\nFull http response:\n"
  108. << rest;
  109. }
  110. auto canContainBody = [](auto statusCode) {
  111. return statusCode != HTTP_NOT_MODIFIED && statusCode != HTTP_NO_CONTENT;
  112. };
  113. if (output && canContainBody(statusCode) && IfResponseRequired(input)) {
  114. TransferData(&input, output);
  115. }
  116. if (outHeaders) {
  117. *outHeaders = input.Headers();
  118. }
  119. return statusCode;
  120. }
  121. THttpInput* TKeepAliveHttpClient::GetHttpInput() {
  122. return Connection ? Connection->GetHttpInput() : nullptr;
  123. }
  124. bool TKeepAliveHttpClient::CreateNewConnectionIfNeeded() {
  125. if (IsClosingRequired || (Connection && !Connection->IsOk())) {
  126. Connection.Reset();
  127. }
  128. if (!Connection) {
  129. Connection = MakeHolder<NPrivate::THttpConnection>(Host,
  130. Port,
  131. SocketTimeout,
  132. ConnectTimeout,
  133. IsHttps,
  134. ClientCertificate,
  135. HttpsVerification);
  136. IsClosingRequired = false;
  137. return true;
  138. }
  139. return false;
  140. }
  141. THttpRequestException::THttpRequestException(int statusCode)
  142. : StatusCode(statusCode)
  143. {
  144. }
  145. int THttpRequestException::GetStatusCode() const {
  146. return StatusCode;
  147. }
  148. TSimpleHttpClient::TSimpleHttpClient(const TOptions& options)
  149. : Host(options.Host())
  150. , Port(options.Port())
  151. , SocketTimeout(options.SocketTimeout())
  152. , ConnectTimeout(options.ConnectTimeout())
  153. {
  154. }
  155. TSimpleHttpClient::TSimpleHttpClient(const TString& host, ui32 port, TDuration socketTimeout, TDuration connectTimeout)
  156. : Host(host)
  157. , Port(port)
  158. , SocketTimeout(socketTimeout)
  159. , ConnectTimeout(connectTimeout)
  160. {
  161. }
  162. void TSimpleHttpClient::EnableVerificationForHttps() {
  163. HttpsVerification = true;
  164. }
  165. void TSimpleHttpClient::DoGet(const TStringBuf relativeUrl, IOutputStream* output, const THeaders& headers) const {
  166. TKeepAliveHttpClient cl = CreateClient();
  167. TKeepAliveHttpClient::THttpCode code = cl.DoGet(relativeUrl, output, headers);
  168. Y_ENSURE(cl.GetHttpInput());
  169. ProcessResponse(relativeUrl, *cl.GetHttpInput(), output, code);
  170. }
  171. void TSimpleHttpClient::DoPost(const TStringBuf relativeUrl, TStringBuf body, IOutputStream* output, const THashMap<TString, TString>& headers) const {
  172. TKeepAliveHttpClient cl = CreateClient();
  173. TKeepAliveHttpClient::THttpCode code = cl.DoPost(relativeUrl, body, output, headers);
  174. Y_ENSURE(cl.GetHttpInput());
  175. ProcessResponse(relativeUrl, *cl.GetHttpInput(), output, code);
  176. }
  177. void TSimpleHttpClient::DoPostRaw(const TStringBuf relativeUrl, const TStringBuf rawRequest, IOutputStream* output) const {
  178. TKeepAliveHttpClient cl = CreateClient();
  179. TKeepAliveHttpClient::THttpCode code = cl.DoRequestRaw(rawRequest, output);
  180. Y_ENSURE(cl.GetHttpInput());
  181. ProcessResponse(relativeUrl, *cl.GetHttpInput(), output, code);
  182. }
  183. namespace NPrivate {
  184. THttpConnection::THttpConnection(const TString& host,
  185. ui32 port,
  186. TDuration sockTimeout,
  187. TDuration connTimeout,
  188. bool isHttps,
  189. const TMaybe<TOpenSslClientIO::TOptions::TClientCert>& clientCert,
  190. const TMaybe<TOpenSslClientIO::TOptions::TVerifyCert>& verifyCert)
  191. : Addr(Resolve(host, port))
  192. , Socket(Connect(Addr, sockTimeout, connTimeout, host, port))
  193. , SocketIn(Socket)
  194. , SocketOut(Socket)
  195. {
  196. if (isHttps) {
  197. TOpenSslClientIO::TOptions opts;
  198. if (clientCert) {
  199. opts.ClientCert_ = clientCert;
  200. }
  201. if (verifyCert) {
  202. opts.VerifyCert_ = verifyCert;
  203. }
  204. Ssl = MakeHolder<TOpenSslClientIO>(&SocketIn, &SocketOut, opts);
  205. HttpOut = MakeHolder<THttpOutput>(Ssl.Get());
  206. } else {
  207. HttpOut = MakeHolder<THttpOutput>(&SocketOut);
  208. }
  209. HttpOut->EnableKeepAlive(true);
  210. }
  211. TNetworkAddress THttpConnection::Resolve(const TString& host, ui32 port) {
  212. try {
  213. return TNetworkAddress(host, port);
  214. } catch (const yexception& e) {
  215. ythrow THttpRequestException() << "Resolve of " << host << ": " << e.what();
  216. }
  217. }
  218. TSocket THttpConnection::Connect(TNetworkAddress& addr,
  219. TDuration sockTimeout,
  220. TDuration connTimeout,
  221. const TString& host,
  222. ui32 port) {
  223. try {
  224. TSocket socket(addr, connTimeout);
  225. TDuration socketTimeout = Max(sockTimeout, TDuration::MilliSeconds(1)); // timeout less than 1ms will be interpreted as 0 in SetSocketTimeout() call below and will result in infinite wait
  226. ui32 seconds = socketTimeout.Seconds();
  227. ui32 milliSeconds = (socketTimeout - TDuration::Seconds(seconds)).MilliSeconds();
  228. socket.SetSocketTimeout(seconds, milliSeconds);
  229. return socket;
  230. } catch (const yexception& e) {
  231. ythrow THttpRequestException() << "Connect to " << host << ':' << port << " failed: " << e.what();
  232. }
  233. }
  234. }
  235. void TSimpleHttpClient::ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream*, const unsigned statusCode) const {
  236. if (!(statusCode >= 200 && statusCode < 300)) {
  237. TString rest = input.ReadAll();
  238. ythrow THttpRequestException(statusCode) << "Got " << statusCode << " at " << Host << relativeUrl << "\nFull http response:\n"
  239. << rest;
  240. }
  241. }
  242. TSimpleHttpClient::~TSimpleHttpClient() {
  243. }
  244. TKeepAliveHttpClient TSimpleHttpClient::CreateClient() const {
  245. TKeepAliveHttpClient cl(Host, Port, SocketTimeout, ConnectTimeout);
  246. if (!HttpsVerification) {
  247. cl.DisableVerificationForHttps();
  248. }
  249. PrepareClient(cl);
  250. return cl;
  251. }
  252. void TSimpleHttpClient::PrepareClient(TKeepAliveHttpClient&) const {
  253. }
  254. TRedirectableHttpClient::TRedirectableHttpClient(const TOptions& options)
  255. : TSimpleHttpClient(options)
  256. , Opts(options)
  257. {
  258. }
  259. TRedirectableHttpClient::TRedirectableHttpClient(const TString& host, ui32 port, TDuration socketTimeout, TDuration connectTimeout)
  260. : TRedirectableHttpClient(TOptions().Host(host).Port(port).SocketTimeout(socketTimeout).ConnectTimeout(connectTimeout))
  261. {
  262. }
  263. void TRedirectableHttpClient::PrepareClient(TKeepAliveHttpClient& cl) const {
  264. cl.IfResponseRequired = [](const THttpInput& input) {
  265. return !input.Headers().HasHeader("Location");
  266. };
  267. }
  268. void TRedirectableHttpClient::ProcessResponse(const TStringBuf relativeUrl, THttpInput& input, IOutputStream* output, const unsigned statusCode) const {
  269. for (auto i = input.Headers().Begin(), e = input.Headers().End(); i != e; ++i) {
  270. if (0 == TString::compare(i->Name(), TStringBuf("Location"))) {
  271. if (Opts.MaxRedirectCount() == 0) {
  272. ythrow THttpRequestException(statusCode) << "Exceeds MaxRedirectCount limit, code " << statusCode << " at " << Host << relativeUrl;
  273. }
  274. TStringBuf schemeHostPort = GetSchemeHostAndPort(i->Value());
  275. TStringBuf scheme("http://");
  276. TStringBuf host("unknown");
  277. ui16 port = 0;
  278. GetSchemeHostAndPort(schemeHostPort, scheme, host, port);
  279. TStringBuf body = GetPathAndQuery(i->Value(), false);
  280. if (port == 0) {
  281. if (scheme.StartsWith("https")) {
  282. port = 443;
  283. } else if (scheme.StartsWith("http")) {
  284. port = 80;
  285. } else {
  286. port = 80;
  287. }
  288. }
  289. auto opts = Opts;
  290. opts.Host(TString(scheme) + TString(host));
  291. opts.Port(port);
  292. opts.MaxRedirectCount(opts.MaxRedirectCount() - 1);
  293. TRedirectableHttpClient cl(opts);
  294. if (HttpsVerification) {
  295. cl.EnableVerificationForHttps();
  296. }
  297. cl.DoGet(body, output);
  298. return;
  299. }
  300. }
  301. if (!(statusCode >= 200 && statusCode < 300)) {
  302. TString rest = input.ReadAll();
  303. ythrow THttpRequestException(statusCode) << "Got " << statusCode << " at " << Host << relativeUrl << "\nFull http response:\n"
  304. << rest;
  305. }
  306. TransferData(&input, output);
  307. }