http_client.cpp 14 KB

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