http_ut.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. #include <library/cpp/http/simple/http_client.h>
  2. #include <library/cpp/http/server/response.h>
  3. #include <library/cpp/testing/mock_server/server.h>
  4. #include <library/cpp/testing/unittest/registar.h>
  5. #include <library/cpp/testing/unittest/tests_data.h>
  6. #include <util/system/event.h>
  7. #include <util/system/thread.h>
  8. #include <thread>
  9. Y_UNIT_TEST_SUITE(SimpleHttp) {
  10. static THttpServerOptions createOptions(ui16 port, bool keepAlive) {
  11. THttpServerOptions o;
  12. o.AddBindAddress("localhost", port);
  13. o.SetThreads(1);
  14. o.SetMaxConnections(1);
  15. o.SetMaxQueueSize(1);
  16. o.EnableKeepAlive(keepAlive);
  17. return o;
  18. }
  19. class TPong: public TRequestReplier {
  20. TDuration Sleep_;
  21. ui16 Port_;
  22. public:
  23. TPong(TDuration sleep = TDuration(), ui16 port = 80)
  24. : Sleep_(sleep)
  25. , Port_(port)
  26. {
  27. }
  28. bool DoReply(const TReplyParams& params) override {
  29. TStringBuf path = TParsedHttpFull(params.Input.FirstLine()).Path;
  30. params.Input.ReadAll();
  31. if (path == "/redirect") {
  32. params.Output << "HTTP/1.1 307 Internal Redirect\r\n"
  33. "Location: http://localhost:"
  34. << Port_
  35. << "/redirect2?some_param=qwe\r\n"
  36. "Non-Authoritative-Reason: HSTS\r\n\r\n"
  37. "must be missing";
  38. return true;
  39. }
  40. if (path == "/redirect2") {
  41. UNIT_ASSERT_VALUES_EQUAL("some_param=qwe", TParsedHttpFull(params.Input.FirstLine()).Cgi);
  42. params.Output << "HTTP/1.1 307 Internal Redirect\r\n"
  43. "Location: http://localhost:"
  44. << Port_
  45. << "/ping\r\n"
  46. "Non-Authoritative-Reason: HSTS\r\n\r\n"
  47. "must be missing too";
  48. return true;
  49. }
  50. if (path != "/ping") {
  51. UNIT_ASSERT_C(false, "path is incorrect: '" << path << "'");
  52. }
  53. Sleep(Sleep_);
  54. THttpResponse resp(HTTP_OK);
  55. resp.SetContent("pong");
  56. resp.OutTo(params.Output);
  57. return true;
  58. }
  59. };
  60. class TScenario {
  61. public:
  62. struct TElem {
  63. TString Url;
  64. int Status = HTTP_OK;
  65. TString Content{};
  66. };
  67. TScenario(const TVector<TElem>& seq, ui16 port = 80, TDuration sleep = TDuration())
  68. : Seq_(seq)
  69. , Sleep_(sleep)
  70. , Port_(port)
  71. {
  72. }
  73. bool DoReply(const TRequestReplier::TReplyParams& params, TRequestReplier* replier) {
  74. const auto parsed = TParsedHttpFull(params.Input.FirstLine());
  75. const auto url = parsed.Request;
  76. params.Input.ReadAll();
  77. UNIT_ASSERT(SeqIdx_ < Seq_.size());
  78. auto& elem = Seq_[SeqIdx_++];
  79. UNIT_ASSERT_VALUES_EQUAL(elem.Url, url);
  80. Sleep(Sleep_);
  81. if (elem.Status == -1) {
  82. replier->ResetConnection(); // RST / ECONNRESET
  83. return true;
  84. }
  85. THttpResponse resp((HttpCodes)elem.Status);
  86. if (elem.Status >= 300 && elem.Status < 400) {
  87. UNIT_ASSERT(SeqIdx_ < Seq_.size());
  88. resp.AddHeader("Location", TStringBuilder() << "http://localhost:" << Port_ << Seq_[SeqIdx_].Url);
  89. }
  90. resp.SetContent(elem.Content);
  91. resp.OutTo(params.Output);
  92. return true;
  93. }
  94. void VerifyInvariants() {
  95. UNIT_ASSERT_VALUES_EQUAL(SeqIdx_, Seq_.size());
  96. }
  97. private:
  98. TVector<TElem> Seq_;
  99. size_t SeqIdx_ = 0;
  100. TDuration Sleep_;
  101. ui16 Port_;
  102. };
  103. class TScenarioReplier: public TRequestReplier {
  104. TScenario* Scenario_ = nullptr;
  105. public:
  106. TScenarioReplier(TScenario* scenario)
  107. : Scenario_(scenario)
  108. {
  109. }
  110. bool DoReply(const TReplyParams& params) override {
  111. return Scenario_->DoReply(params, this);
  112. }
  113. };
  114. class TCodedPong: public TRequestReplier {
  115. HttpCodes Code_;
  116. public:
  117. TCodedPong(HttpCodes code)
  118. : Code_(code)
  119. {
  120. }
  121. bool DoReply(const TReplyParams& params) override {
  122. if (TParsedHttpFull(params.Input.FirstLine()).Path != "/ping") {
  123. UNIT_ASSERT(false);
  124. }
  125. THttpResponse resp(Code_);
  126. resp.SetContent("pong");
  127. resp.OutTo(params.Output);
  128. return true;
  129. }
  130. };
  131. class T500: public TRequestReplier {
  132. ui16 Port_;
  133. public:
  134. T500(ui16 port)
  135. : Port_(port)
  136. {
  137. }
  138. bool DoReply(const TReplyParams& params) override {
  139. TStringBuf path = TParsedHttpFull(params.Input.FirstLine()).Path;
  140. if (path == "/bad_redirect") {
  141. params.Output << "HTTP/1.1 500 Internal Redirect\r\n"
  142. "Location: http://localhost:1/qwerty\r\n"
  143. "Non-Authoritative-Reason: HSTS\r\n\r\n";
  144. return true;
  145. }
  146. if (path == "/redirect_to_500") {
  147. params.Output << "HTTP/1.1 307 Internal Redirect\r\n"
  148. "Location: http://localhost:"
  149. << Port_
  150. << "/500\r\n"
  151. "Non-Authoritative-Reason: HSTS\r\n\r\n";
  152. return true;
  153. }
  154. THttpResponse resp(HTTP_INTERNAL_SERVER_ERROR);
  155. resp.SetContent("bang");
  156. resp.OutTo(params.Output);
  157. return true;
  158. }
  159. };
  160. static void TestRedirectCountParam(int maxRedirectCount, int redirectCount) {
  161. TPortManager pm;
  162. ui16 port = pm.GetPort(80);
  163. TVector<TScenario::TElem> steps;
  164. for (int i = 0; i < redirectCount; ++i) {
  165. steps.push_back({"/any", 302});
  166. }
  167. steps.push_back({"/any", 200, "Hello"});
  168. TScenario scenario(steps, port);
  169. NMock::TMockServer server(createOptions(port, true), [&scenario]() { return new TScenarioReplier(&scenario); });
  170. TRedirectableHttpClient cl(TSimpleHttpClientOptions().Host("localhost").Port(port).MaxRedirectCount(maxRedirectCount));
  171. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  172. TStringStream s;
  173. if (maxRedirectCount >= redirectCount) {
  174. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/any", &s));
  175. UNIT_ASSERT_VALUES_EQUAL("Hello", s.Str());
  176. scenario.VerifyInvariants();
  177. } else {
  178. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/any", &s), THttpRequestException, "");
  179. }
  180. }
  181. Y_UNIT_TEST(simpleSuccessful) {
  182. TPortManager pm;
  183. ui16 port = pm.GetPort(80);
  184. NMock::TMockServer server(createOptions(port, false), []() { return new TPong; });
  185. TSimpleHttpClient cl("localhost", port);
  186. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  187. {
  188. TStringStream s;
  189. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", &s));
  190. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  191. Sleep(TDuration::MilliSeconds(500));
  192. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  193. }
  194. {
  195. TStringStream s;
  196. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", &s));
  197. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  198. Sleep(TDuration::MilliSeconds(500));
  199. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  200. }
  201. {
  202. TStringStream s;
  203. UNIT_ASSERT_NO_EXCEPTION(cl.DoPost("/ping", "", &s));
  204. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  205. Sleep(TDuration::MilliSeconds(500));
  206. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  207. }
  208. {
  209. TStringStream s;
  210. UNIT_ASSERT_NO_EXCEPTION(cl.DoPost("/ping", "", &s));
  211. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  212. Sleep(TDuration::MilliSeconds(500));
  213. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  214. }
  215. }
  216. Y_UNIT_TEST(simpleMessages) {
  217. TPortManager pm;
  218. ui16 port = pm.GetPort(80);
  219. NMock::TMockServer server(createOptions(port, false), []() { return new TPong; });
  220. TSimpleHttpClient cl("localhost", port);
  221. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  222. {
  223. TStringStream s;
  224. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", &s));
  225. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  226. Sleep(TDuration::MilliSeconds(500));
  227. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  228. }
  229. {
  230. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", nullptr));
  231. Sleep(TDuration::MilliSeconds(500));
  232. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  233. }
  234. server.SetGenerator([]() { return new TCodedPong(HTTP_CONTINUE); });
  235. {
  236. TStringStream s;
  237. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoPost("/ping", "", &s),
  238. THttpRequestException,
  239. "Got 100 at localhost/ping\n"
  240. "Full http response:\n");
  241. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  242. Sleep(TDuration::MilliSeconds(500));
  243. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  244. }
  245. {
  246. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoPost("/ping", "", nullptr),
  247. THttpRequestException,
  248. "Got 100 at localhost/ping\n"
  249. "Full http response:\n"
  250. "pong");
  251. Sleep(TDuration::MilliSeconds(500));
  252. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  253. }
  254. }
  255. Y_UNIT_TEST(simpleTimeout) {
  256. TPortManager pm;
  257. ui16 port = pm.GetPort(80);
  258. NMock::TMockServer server(createOptions(port, true), []() { return new TPong(TDuration::MilliSeconds(300)); });
  259. TSimpleHttpClient cl("localhost", port, TDuration::MilliSeconds(50), TDuration::MilliSeconds(50));
  260. TStringStream s;
  261. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/ping", &s),
  262. TSystemError,
  263. "Resource temporarily unavailable");
  264. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoPost("/ping", "", &s),
  265. TSystemError,
  266. "Resource temporarily unavailable");
  267. }
  268. Y_UNIT_TEST(simpleError) {
  269. TPortManager pm;
  270. ui16 port = pm.GetPort(80);
  271. NMock::TMockServer server(createOptions(port, true), []() { return new TPong; });
  272. TSimpleHttpClient cl("localhost", port);
  273. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  274. {
  275. TStringStream s;
  276. server.SetGenerator([]() { return new TCodedPong(HTTP_CONTINUE); });
  277. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/ping", &s),
  278. THttpRequestException,
  279. "Got 100 at localhost/ping\n"
  280. "Full http response:");
  281. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  282. Sleep(TDuration::MilliSeconds(500));
  283. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  284. }
  285. {
  286. TStringStream s;
  287. server.SetGenerator([]() { return new TCodedPong(HTTP_OK); });
  288. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", &s));
  289. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  290. Sleep(TDuration::MilliSeconds(500));
  291. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  292. server.SetGenerator([]() { return new TCodedPong(HTTP_PARTIAL_CONTENT); });
  293. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping", &s));
  294. Sleep(TDuration::MilliSeconds(500));
  295. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  296. }
  297. {
  298. TStringStream s;
  299. server.SetGenerator([]() { return new TCodedPong(HTTP_MULTIPLE_CHOICES); });
  300. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/ping", &s),
  301. THttpRequestException,
  302. "Got 300 at localhost/ping\n"
  303. "Full http response:");
  304. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  305. Sleep(TDuration::MilliSeconds(500));
  306. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  307. }
  308. }
  309. Y_UNIT_TEST(redirectCountDefault) {
  310. TPortManager pm;
  311. ui16 port = pm.GetPort(80);
  312. TScenario scenario({
  313. {"/any", 307},
  314. {"/any?param=1", 302},
  315. {"/any?param=1", 302},
  316. {"/any?param=1", 302},
  317. {"/any?param=1", 302},
  318. {"/any?param=1", 302},
  319. {"/any?param=1", 302},
  320. {"/any?param=1", 302},
  321. {"/any?param=1", 302},
  322. {"/any?param=1", 302},
  323. {"/any?param=2", 200, "Hello"}
  324. }, port);
  325. NMock::TMockServer server(createOptions(port, true), [&scenario]() { return new TScenarioReplier(&scenario); });
  326. TRedirectableHttpClient cl("localhost", port);
  327. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  328. TStringStream s;
  329. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/any", &s));
  330. UNIT_ASSERT_VALUES_EQUAL("Hello", s.Str());
  331. scenario.VerifyInvariants();
  332. }
  333. Y_UNIT_TEST(redirectCountN) {
  334. TestRedirectCountParam(0, 0);
  335. TestRedirectCountParam(0, 1);
  336. TestRedirectCountParam(1, 1);
  337. TestRedirectCountParam(3, 3);
  338. TestRedirectCountParam(20, 20);
  339. TestRedirectCountParam(20, 21);
  340. }
  341. Y_UNIT_TEST(redirectable) {
  342. TPortManager pm;
  343. ui16 port = pm.GetPort(80);
  344. NMock::TMockServer server(createOptions(port, true), [port]() { return new TPong(TDuration(), port); });
  345. TRedirectableHttpClient cl("localhost", port);
  346. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  347. {
  348. TStringStream s;
  349. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/redirect", &s));
  350. UNIT_ASSERT_VALUES_EQUAL("pong", s.Str());
  351. Sleep(TDuration::MilliSeconds(500));
  352. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  353. }
  354. server.SetGenerator([port]() { return new T500(port); });
  355. TStringStream s;
  356. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/bad_redirect", &s),
  357. THttpRequestException,
  358. "can not connect to ");
  359. Sleep(TDuration::MilliSeconds(500));
  360. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  361. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/redirect_to_500", &s),
  362. THttpRequestException,
  363. "Got 500 at http://localhost/500\n"
  364. "Full http response:\n");
  365. UNIT_ASSERT_VALUES_EQUAL("bang", s.Str());
  366. Sleep(TDuration::MilliSeconds(500));
  367. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  368. }
  369. Y_UNIT_TEST(keepaliveSuccessful) {
  370. auto test = [](bool keepalive, i64 clientCount) {
  371. TPortManager pm;
  372. ui16 port = pm.GetPort(80);
  373. NMock::TMockServer server(createOptions(port, keepalive), []() { return new TPong; });
  374. TKeepAliveHttpClient cl("localhost", port);
  375. UNIT_ASSERT_VALUES_EQUAL(0, server.GetClientCount());
  376. {
  377. TStringStream s;
  378. int code = -1;
  379. UNIT_ASSERT_NO_EXCEPTION_C(code = cl.DoGet("/ping", &s), keepalive);
  380. UNIT_ASSERT_VALUES_EQUAL_C(200, code, keepalive);
  381. UNIT_ASSERT_VALUES_EQUAL_C("pong", s.Str(), keepalive);
  382. Sleep(TDuration::MilliSeconds(500));
  383. UNIT_ASSERT_VALUES_EQUAL(clientCount, server.GetClientCount());
  384. }
  385. {
  386. TStringStream s;
  387. int code = -1;
  388. UNIT_ASSERT_NO_EXCEPTION_C(code = cl.DoGet("/ping", &s), keepalive);
  389. UNIT_ASSERT_VALUES_EQUAL_C(200, code, keepalive);
  390. UNIT_ASSERT_VALUES_EQUAL_C("pong", s.Str(), keepalive);
  391. Sleep(TDuration::MilliSeconds(500));
  392. UNIT_ASSERT_VALUES_EQUAL(clientCount, server.GetClientCount());
  393. }
  394. {
  395. TStringStream s;
  396. int code = -1;
  397. UNIT_ASSERT_NO_EXCEPTION_C(code = cl.DoPost("/ping", "", &s), keepalive);
  398. UNIT_ASSERT_VALUES_EQUAL_C(200, code, keepalive);
  399. UNIT_ASSERT_VALUES_EQUAL_C("pong", s.Str(), keepalive);
  400. Sleep(TDuration::MilliSeconds(500));
  401. UNIT_ASSERT_VALUES_EQUAL(clientCount, server.GetClientCount());
  402. }
  403. {
  404. TStringStream s;
  405. int code = -1;
  406. UNIT_ASSERT_NO_EXCEPTION_C(code = cl.DoPost("/ping", "", &s), keepalive);
  407. UNIT_ASSERT_VALUES_EQUAL_C(200, code, keepalive);
  408. UNIT_ASSERT_VALUES_EQUAL_C("pong", s.Str(), keepalive);
  409. Sleep(TDuration::MilliSeconds(500));
  410. UNIT_ASSERT_VALUES_EQUAL(clientCount, server.GetClientCount());
  411. }
  412. };
  413. test(true, 1);
  414. test(false, 0);
  415. }
  416. Y_UNIT_TEST(keepaliveTimeout) {
  417. TPortManager pm;
  418. ui16 port = pm.GetPort(80);
  419. NMock::TMockServer server(createOptions(port, true), []() { return new TPong(TDuration::MilliSeconds(300)); });
  420. TKeepAliveHttpClient cl("localhost", port, TDuration::MilliSeconds(50), TDuration::MilliSeconds(50));
  421. TStringStream s;
  422. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoGet("/ping", &s),
  423. TSystemError,
  424. "Resource temporarily unavailable");
  425. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoPost("/ping", "", &s),
  426. TSystemError,
  427. "Resource temporarily unavailable");
  428. }
  429. Y_UNIT_TEST(keepaliveHeaders) {
  430. TPortManager pm;
  431. ui16 port = pm.GetPort(80);
  432. NMock::TMockServer server(createOptions(port, true), []() { return new TPong; });
  433. TKeepAliveHttpClient cl("localhost", port);
  434. TStringStream s;
  435. THttpHeaders h;
  436. UNIT_ASSERT_VALUES_EQUAL(200, cl.DoGet("/ping", &s, {}, &h));
  437. TStringStream hs;
  438. h.OutTo(&hs);
  439. UNIT_ASSERT_VALUES_EQUAL("Content-Length: 4\r\nConnection: Keep-Alive\r\n", hs.Str());
  440. }
  441. Y_UNIT_TEST(keepaliveRaw) {
  442. TPortManager pm;
  443. ui16 port = pm.GetPort(80);
  444. NMock::TMockServer server(createOptions(port, true), []() { return new TPong; });
  445. TKeepAliveHttpClient cl("localhost", port);
  446. TStringStream s;
  447. THttpHeaders h;
  448. TString raw = "POST /ping HTTP/1.1\r\n"
  449. "Connection: Keep-Alive\r\n"
  450. "Accept-Encoding: gzip, deflate\r\n"
  451. "Content-Length: 9\r\n"
  452. "Content-Type: application/x-www-form-urlencoded\r\n"
  453. "User-Agent: Arcadia-library/cpp/http\r\n"
  454. "\r\n"
  455. "some body";
  456. UNIT_ASSERT_VALUES_EQUAL(200, cl.DoRequestRaw(raw, &s, &h));
  457. TStringStream hs;
  458. h.OutTo(&hs);
  459. UNIT_ASSERT_VALUES_EQUAL("Content-Length: 4\r\nConnection: Keep-Alive\r\n", hs.Str());
  460. raw = "GET /ping HT TP/1.1\r\n";
  461. UNIT_ASSERT_EXCEPTION_CONTAINS(cl.DoRequestRaw(raw, &s, &h), TSystemError, "can not read from socket input stream");
  462. }
  463. Y_UNIT_TEST(keepaliveWithClosedByPeer) {
  464. TPortManager pm;
  465. ui16 port = pm.GetPort(80);
  466. NMock::TMockServer::TGenerator gen = []() { return new TPong; };
  467. THolder<NMock::TMockServer> server = MakeHolder<NMock::TMockServer>(createOptions(port, true), gen);
  468. TKeepAliveHttpClient cl("localhost", port);
  469. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping"));
  470. server.Reset();
  471. server = MakeHolder<NMock::TMockServer>(createOptions(port, true), gen);
  472. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping"));
  473. TKeepAliveHttpClient cl2("localhost", port);
  474. UNIT_ASSERT_NO_EXCEPTION(cl2.DoGet("/ping"));
  475. Sleep(TDuration::MilliSeconds(500));
  476. UNIT_ASSERT_NO_EXCEPTION(cl.DoGet("/ping"));
  477. }
  478. }