stream_ut.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. #include "stream.h"
  2. #include "chunk.h"
  3. #include <library/cpp/http/server/http_ex.h>
  4. #include <library/cpp/testing/unittest/registar.h>
  5. #include <library/cpp/testing/unittest/tests_data.h>
  6. #include <util/string/printf.h>
  7. #include <util/network/socket.h>
  8. #include <util/stream/file.h>
  9. #include <util/stream/output.h>
  10. #include <util/stream/tee.h>
  11. #include <util/stream/zlib.h>
  12. #include <util/stream/null.h>
  13. Y_UNIT_TEST_SUITE(THttpStreamTest) {
  14. class TTestHttpServer: public THttpServer::ICallBack {
  15. class TRequest: public THttpClientRequestEx {
  16. public:
  17. inline TRequest(TTestHttpServer* parent)
  18. : Parent_(parent)
  19. {
  20. }
  21. bool Reply(void* /*tsr*/) override {
  22. if (!ProcessHeaders()) {
  23. return true;
  24. }
  25. // Check that function will not hang on
  26. Input().ReadAll();
  27. // "lo" is for "local"
  28. if (RD.ServerName() == "yandex.lo") {
  29. // do redirect
  30. Output() << "HTTP/1.1 301 Moved permanently\r\n"
  31. "Location: http://www.yandex.lo\r\n"
  32. "\r\n";
  33. } else if (RD.ServerName() == "www.yandex.lo") {
  34. Output() << "HTTP/1.1 200 Ok\r\n"
  35. "\r\n";
  36. } else {
  37. Output() << "HTTP/1.1 200 Ok\r\n\r\n";
  38. if (Buf.Size()) {
  39. Output().Write(Buf.AsCharPtr(), Buf.Size());
  40. } else {
  41. Output() << Parent_->Res_;
  42. }
  43. }
  44. Output().Finish();
  45. Parent_->LastRequestSentSize_ = Output().SentSize();
  46. return true;
  47. }
  48. private:
  49. TTestHttpServer* Parent_ = nullptr;
  50. };
  51. public:
  52. inline TTestHttpServer(const TString& res)
  53. : Res_(res)
  54. {
  55. }
  56. TClientRequest* CreateClient() override {
  57. return new TRequest(this);
  58. }
  59. size_t LastRequestSentSize() const {
  60. return LastRequestSentSize_;
  61. }
  62. private:
  63. TString Res_;
  64. size_t LastRequestSentSize_ = 0;
  65. };
  66. Y_UNIT_TEST(TestCodings1) {
  67. UNIT_ASSERT(SupportedCodings().size() > 0);
  68. }
  69. Y_UNIT_TEST(TestHttpInput) {
  70. TString res = "I'm a teapot";
  71. TPortManager pm;
  72. const ui16 port = pm.GetPort();
  73. TTestHttpServer serverImpl(res);
  74. THttpServer server(&serverImpl, THttpServer::TOptions(port).EnableKeepAlive(true).EnableCompression(true));
  75. UNIT_ASSERT(server.Start());
  76. TNetworkAddress addr("localhost", port);
  77. TSocket s(addr);
  78. //TDebugOutput dbg;
  79. TNullOutput dbg;
  80. {
  81. TSocketOutput so(s);
  82. TTeeOutput out(&so, &dbg);
  83. THttpOutput output(&out);
  84. output.EnableKeepAlive(true);
  85. output.EnableCompression(true);
  86. TString r;
  87. r += "GET / HTTP/1.1";
  88. r += "\r\n";
  89. r += "Host: yandex.lo";
  90. r += "\r\n";
  91. r += "\r\n";
  92. output.Write(r.data(), r.size());
  93. output.Finish();
  94. }
  95. {
  96. TSocketInput si(s);
  97. THttpInput input(&si);
  98. unsigned httpCode = ParseHttpRetCode(input.FirstLine());
  99. UNIT_ASSERT_VALUES_EQUAL(httpCode / 10, 30u);
  100. TransferData(&input, &dbg);
  101. }
  102. server.Stop();
  103. }
  104. Y_UNIT_TEST(TestHttpInputDelete) {
  105. TString res = "I'm a teapot";
  106. TPortManager pm;
  107. const ui16 port = pm.GetPort();
  108. TTestHttpServer serverImpl(res);
  109. THttpServer server(&serverImpl, THttpServer::TOptions(port).EnableKeepAlive(true).EnableCompression(true));
  110. UNIT_ASSERT(server.Start());
  111. TNetworkAddress addr("localhost", port);
  112. TSocket s(addr);
  113. //TDebugOutput dbg;
  114. TNullOutput dbg;
  115. {
  116. TSocketOutput so(s);
  117. TTeeOutput out(&so, &dbg);
  118. THttpOutput output(&out);
  119. output.EnableKeepAlive(true);
  120. output.EnableCompression(true);
  121. TString r;
  122. r += "DELETE / HTTP/1.1";
  123. r += "\r\n";
  124. r += "Host: yandex.lo";
  125. r += "\r\n";
  126. r += "\r\n";
  127. output.Write(r.data(), r.size());
  128. output.Finish();
  129. }
  130. {
  131. TSocketInput si(s);
  132. THttpInput input(&si);
  133. unsigned httpCode = ParseHttpRetCode(input.FirstLine());
  134. UNIT_ASSERT_VALUES_EQUAL(httpCode / 10, 30u);
  135. TransferData(&input, &dbg);
  136. }
  137. server.Stop();
  138. }
  139. Y_UNIT_TEST(TestParseHttpRetCode) {
  140. UNIT_ASSERT_VALUES_EQUAL(ParseHttpRetCode("HTTP/1.1 301"), 301u);
  141. }
  142. Y_UNIT_TEST(TestKeepAlive) {
  143. {
  144. TString s = "GET / HTTP/1.0\r\n\r\n";
  145. TStringInput si(s);
  146. THttpInput in(&si);
  147. UNIT_ASSERT(!in.IsKeepAlive());
  148. }
  149. {
  150. TString s = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
  151. TStringInput si(s);
  152. THttpInput in(&si);
  153. UNIT_ASSERT(in.IsKeepAlive());
  154. }
  155. {
  156. TString s = "GET / HTTP/1.1\r\n\r\n";
  157. TStringInput si(s);
  158. THttpInput in(&si);
  159. UNIT_ASSERT(in.IsKeepAlive());
  160. }
  161. {
  162. TString s = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
  163. TStringInput si(s);
  164. THttpInput in(&si);
  165. UNIT_ASSERT(!in.IsKeepAlive());
  166. }
  167. {
  168. TString s = "HTTP/1.0 200 Ok\r\n\r\n";
  169. TStringInput si(s);
  170. THttpInput in(&si);
  171. UNIT_ASSERT(!in.IsKeepAlive());
  172. }
  173. {
  174. TString s = "HTTP/1.0 200 Ok\r\nConnection: keep-alive\r\n\r\n";
  175. TStringInput si(s);
  176. THttpInput in(&si);
  177. UNIT_ASSERT(in.IsKeepAlive());
  178. }
  179. {
  180. TString s = "HTTP/1.1 200 Ok\r\n\r\n";
  181. TStringInput si(s);
  182. THttpInput in(&si);
  183. UNIT_ASSERT(in.IsKeepAlive());
  184. }
  185. {
  186. TString s = "HTTP/1.1 200 Ok\r\nConnection: close\r\n\r\n";
  187. TStringInput si(s);
  188. THttpInput in(&si);
  189. UNIT_ASSERT(!in.IsKeepAlive());
  190. }
  191. }
  192. Y_UNIT_TEST(TestMinRequest) {
  193. TString res = "qqqqqq";
  194. TPortManager pm;
  195. const ui16 port = pm.GetPort();
  196. TTestHttpServer serverImpl(res);
  197. THttpServer server(&serverImpl, THttpServer::TOptions(port).EnableKeepAlive(true).EnableCompression(true));
  198. UNIT_ASSERT(server.Start());
  199. TNetworkAddress addr("localhost", port);
  200. TSocket s(addr);
  201. TNullOutput dbg;
  202. SendMinimalHttpRequest(s, "www.yandex.lo", "/");
  203. TSocketInput si(s);
  204. THttpInput input(&si);
  205. unsigned httpCode = ParseHttpRetCode(input.FirstLine());
  206. UNIT_ASSERT_VALUES_EQUAL(httpCode, 200u);
  207. TransferData(&input, &dbg);
  208. server.Stop();
  209. }
  210. Y_UNIT_TEST(TestResponseWithBlanks) {
  211. TString res = "qqqqqq\r\n\r\nsdasdsad\r\n";
  212. TPortManager pm;
  213. const ui16 port = pm.GetPort();
  214. TTestHttpServer serverImpl(res);
  215. THttpServer server(&serverImpl, THttpServer::TOptions(port).EnableKeepAlive(true).EnableCompression(true));
  216. UNIT_ASSERT(server.Start());
  217. TNetworkAddress addr("localhost", port);
  218. TSocket s(addr);
  219. SendMinimalHttpRequest(s, "www.yandex.ru", "/");
  220. TSocketInput si(s);
  221. THttpInput input(&si);
  222. unsigned httpCode = ParseHttpRetCode(input.FirstLine());
  223. UNIT_ASSERT_VALUES_EQUAL(httpCode, 200u);
  224. TString reply = input.ReadAll();
  225. UNIT_ASSERT_VALUES_EQUAL(reply, res);
  226. server.Stop();
  227. }
  228. Y_UNIT_TEST(TestOutputFlush) {
  229. TString str;
  230. TStringOutput strOut(str);
  231. TBufferedOutput bufOut(&strOut, 8192);
  232. THttpOutput httpOut(&bufOut);
  233. httpOut.EnableKeepAlive(true);
  234. httpOut.EnableCompression(true);
  235. const char* header = "GET / HTTP/1.1\r\nHost: yandex.ru\r\n\r\n";
  236. httpOut << header;
  237. unsigned curLen = str.size();
  238. const char* body = "<html>Hello</html>";
  239. httpOut << body;
  240. UNIT_ASSERT_VALUES_EQUAL(curLen, str.size());
  241. httpOut.Flush();
  242. UNIT_ASSERT_VALUES_EQUAL(curLen + strlen(body), str.size());
  243. }
  244. Y_UNIT_TEST(TestOutputPostFlush) {
  245. TString str;
  246. TString checkStr;
  247. TStringOutput strOut(str);
  248. TStringOutput checkOut(checkStr);
  249. TBufferedOutput bufOut(&strOut, 8192);
  250. TTeeOutput teeOut(&bufOut, &checkOut);
  251. THttpOutput httpOut(&teeOut);
  252. httpOut.EnableKeepAlive(true);
  253. httpOut.EnableCompression(true);
  254. const char* header = "POST / HTTP/1.1\r\nHost: yandex.ru\r\n\r\n";
  255. httpOut << header;
  256. UNIT_ASSERT_VALUES_EQUAL(str.size(), 0u);
  257. const char* body = "<html>Hello</html>";
  258. httpOut << body;
  259. UNIT_ASSERT_VALUES_EQUAL(str.size(), 0u);
  260. httpOut.Flush();
  261. UNIT_ASSERT_VALUES_EQUAL(checkStr.size(), str.size());
  262. }
  263. TString MakeHttpOutputBody(const char* body, bool encodingEnabled) {
  264. TString str;
  265. TStringOutput strOut(str);
  266. {
  267. TBufferedOutput bufOut(&strOut, 8192);
  268. THttpOutput httpOut(&bufOut);
  269. httpOut.EnableKeepAlive(true);
  270. httpOut.EnableCompression(true);
  271. httpOut.EnableBodyEncoding(encodingEnabled);
  272. httpOut << "POST / HTTP/1.1\r\n";
  273. httpOut << "Host: yandex.ru\r\n";
  274. httpOut << "Content-Encoding: gzip\r\n";
  275. httpOut << "\r\n";
  276. UNIT_ASSERT_VALUES_EQUAL(str.size(), 0u);
  277. httpOut << body;
  278. }
  279. const char* bodyDelimiter = "\r\n\r\n";
  280. size_t bodyPos = str.find(bodyDelimiter);
  281. UNIT_ASSERT(bodyPos != TString::npos);
  282. return str.substr(bodyPos + strlen(bodyDelimiter));
  283. }
  284. TString SimulateBodyEncoding(const char* body) {
  285. TString bodyStr;
  286. TStringOutput bodyOut(bodyStr);
  287. TChunkedOutput chunkOut(&bodyOut);
  288. TZLibCompress comprOut(&chunkOut, ZLib::GZip);
  289. comprOut << body;
  290. return bodyStr;
  291. }
  292. Y_UNIT_TEST(TestRebuildStreamOnPost) {
  293. const char* body = "<html>Hello</html>";
  294. UNIT_ASSERT(MakeHttpOutputBody(body, false) == body);
  295. UNIT_ASSERT(MakeHttpOutputBody(body, true) == SimulateBodyEncoding(body));
  296. }
  297. Y_UNIT_TEST(TestOutputFinish) {
  298. TString str;
  299. TStringOutput strOut(str);
  300. TBufferedOutput bufOut(&strOut, 8192);
  301. THttpOutput httpOut(&bufOut);
  302. httpOut.EnableKeepAlive(true);
  303. httpOut.EnableCompression(true);
  304. const char* header = "GET / HTTP/1.1\r\nHost: yandex.ru\r\n\r\n";
  305. httpOut << header;
  306. unsigned curLen = str.size();
  307. const char* body = "<html>Hello</html>";
  308. httpOut << body;
  309. UNIT_ASSERT_VALUES_EQUAL(curLen, str.size());
  310. httpOut.Finish();
  311. UNIT_ASSERT_VALUES_EQUAL(curLen + strlen(body), str.size());
  312. }
  313. Y_UNIT_TEST(TestMultilineHeaders) {
  314. const char* headerLine0 = "HTTP/1.1 200 OK";
  315. const char* headerLine1 = "Content-Language: en";
  316. const char* headerLine2 = "Vary: Accept-Encoding, ";
  317. const char* headerLine3 = "\tAccept-Language";
  318. const char* headerLine4 = "Content-Length: 18";
  319. TString endLine("\r\n");
  320. TString r;
  321. r += headerLine0 + endLine;
  322. r += headerLine1 + endLine;
  323. r += headerLine2 + endLine;
  324. r += headerLine3 + endLine;
  325. r += headerLine4 + endLine + endLine;
  326. r += "<html>Hello</html>";
  327. TStringInput stringInput(r);
  328. THttpInput input(&stringInput);
  329. const THttpHeaders& httpHeaders = input.Headers();
  330. UNIT_ASSERT_VALUES_EQUAL(httpHeaders.Count(), 3u);
  331. THttpHeaders::TConstIterator it = httpHeaders.Begin();
  332. UNIT_ASSERT_VALUES_EQUAL(it->ToString(), TString(headerLine1));
  333. UNIT_ASSERT_VALUES_EQUAL((++it)->ToString(), TString::Join(headerLine2, headerLine3));
  334. UNIT_ASSERT_VALUES_EQUAL((++it)->ToString(), TString(headerLine4));
  335. }
  336. Y_UNIT_TEST(ContentLengthRemoval) {
  337. TMemoryInput request("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n\r\n");
  338. THttpInput i(&request);
  339. TString result;
  340. TStringOutput out(result);
  341. THttpOutput httpOut(&out, &i);
  342. httpOut.EnableKeepAlive(true);
  343. httpOut.EnableCompression(true);
  344. httpOut << "HTTP/1.1 200 OK\r\n";
  345. char answer[] = "Mary had a little lamb.";
  346. httpOut << "Content-Length: " << strlen(answer) << "\r\n"
  347. "\r\n";
  348. httpOut << answer;
  349. httpOut.Finish();
  350. Cdbg << result;
  351. result.to_lower();
  352. UNIT_ASSERT(result.Contains("content-encoding: gzip"));
  353. UNIT_ASSERT(!result.Contains("content-length"));
  354. }
  355. Y_UNIT_TEST(CodecsPriority) {
  356. TMemoryInput request("GET / HTTP/1.1\r\nAccept-Encoding: gzip, br\r\n\r\n");
  357. TVector<TStringBuf> codecs = {"br", "gzip"};
  358. THttpInput i(&request);
  359. TString result;
  360. TStringOutput out(result);
  361. THttpOutput httpOut(&out, &i);
  362. httpOut.EnableKeepAlive(true);
  363. httpOut.EnableCompression(codecs);
  364. httpOut << "HTTP/1.1 200 OK\r\n";
  365. char answer[] = "Mary had a little lamb.";
  366. httpOut << "Content-Length: " << strlen(answer) << "\r\n"
  367. "\r\n";
  368. httpOut << answer;
  369. httpOut.Finish();
  370. Cdbg << result;
  371. result.to_lower();
  372. UNIT_ASSERT(result.Contains("content-encoding: br"));
  373. }
  374. Y_UNIT_TEST(CodecsPriority2) {
  375. TMemoryInput request("GET / HTTP/1.1\r\nAccept-Encoding: gzip, br\r\n\r\n");
  376. TVector<TStringBuf> codecs = {"gzip", "br"};
  377. THttpInput i(&request);
  378. TString result;
  379. TStringOutput out(result);
  380. THttpOutput httpOut(&out, &i);
  381. httpOut.EnableKeepAlive(true);
  382. httpOut.EnableCompression(codecs);
  383. httpOut << "HTTP/1.1 200 OK\r\n";
  384. char answer[] = "Mary had a little lamb.";
  385. httpOut << "Content-Length: " << strlen(answer) << "\r\n"
  386. "\r\n";
  387. httpOut << answer;
  388. httpOut.Finish();
  389. Cdbg << result;
  390. result.to_lower();
  391. UNIT_ASSERT(result.Contains("content-encoding: gzip"));
  392. }
  393. Y_UNIT_TEST(HasTrailers) {
  394. TMemoryInput response(
  395. "HTTP/1.1 200 OK\r\n"
  396. "Transfer-Encoding: chunked\r\n"
  397. "\r\n"
  398. "3\r\n"
  399. "foo"
  400. "0\r\n"
  401. "Bar: baz\r\n"
  402. "\r\n");
  403. THttpInput i(&response);
  404. TMaybe<THttpHeaders> trailers = i.Trailers();
  405. UNIT_ASSERT(!trailers.Defined());
  406. i.ReadAll();
  407. trailers = i.Trailers();
  408. UNIT_ASSERT_VALUES_EQUAL(trailers.GetRef().Count(), 1);
  409. UNIT_ASSERT_VALUES_EQUAL(trailers.GetRef().Begin()->ToString(), "Bar: baz");
  410. }
  411. Y_UNIT_TEST(NoTrailersWithChunks) {
  412. TMemoryInput response(
  413. "HTTP/1.1 200 OK\r\n"
  414. "Transfer-Encoding: chunked\r\n"
  415. "\r\n"
  416. "3\r\n"
  417. "foo"
  418. "0\r\n"
  419. "\r\n");
  420. THttpInput i(&response);
  421. TMaybe<THttpHeaders> trailers = i.Trailers();
  422. UNIT_ASSERT(!trailers.Defined());
  423. i.ReadAll();
  424. trailers = i.Trailers();
  425. UNIT_ASSERT_VALUES_EQUAL(trailers.GetRef().Count(), 0);
  426. }
  427. Y_UNIT_TEST(NoTrailersNoChunks) {
  428. TMemoryInput response(
  429. "HTTP/1.1 200 OK\r\n"
  430. "Content-Length: 3\r\n"
  431. "\r\n"
  432. "bar");
  433. THttpInput i(&response);
  434. TMaybe<THttpHeaders> trailers = i.Trailers();
  435. UNIT_ASSERT(!trailers.Defined());
  436. i.ReadAll();
  437. trailers = i.Trailers();
  438. UNIT_ASSERT_VALUES_EQUAL(trailers.GetRef().Count(), 0);
  439. }
  440. Y_UNIT_TEST(RequestWithoutContentLength) {
  441. TStringStream request;
  442. {
  443. THttpOutput httpOutput(&request);
  444. httpOutput << "POST / HTTP/1.1\r\n"
  445. "Host: yandex.ru\r\n"
  446. "\r\n";
  447. httpOutput << "GGLOL";
  448. }
  449. {
  450. TStringInput input(request.Str());
  451. THttpInput httpInput(&input);
  452. bool chunkedOrHasContentLength = false;
  453. for (const auto& header : httpInput.Headers()) {
  454. if (header.Name() == "Transfer-Encoding" && header.Value() == "chunked" || header.Name() == "Content-Length") {
  455. chunkedOrHasContentLength = true;
  456. }
  457. }
  458. // If request doesn't contain neither Content-Length header nor Transfer-Encoding header
  459. // then server considers message body length to be zero.
  460. // (See https://tools.ietf.org/html/rfc7230#section-3.3.3)
  461. UNIT_ASSERT(chunkedOrHasContentLength);
  462. UNIT_ASSERT_VALUES_EQUAL(httpInput.ReadAll(), "GGLOL");
  463. }
  464. }
  465. Y_UNIT_TEST(TestInputHasContent) {
  466. {
  467. TStringStream request;
  468. request << "POST / HTTP/1.1\r\n"
  469. "Host: yandex.ru\r\n"
  470. "\r\n";
  471. request << "HTTPDATA";
  472. TStringInput input(request.Str());
  473. THttpInput httpInput(&input);
  474. UNIT_ASSERT(!httpInput.HasContent());
  475. UNIT_ASSERT_VALUES_EQUAL(httpInput.ReadAll(), "");
  476. }
  477. {
  478. TStringStream request;
  479. request << "POST / HTTP/1.1\r\n"
  480. "Host: yandex.ru\r\n"
  481. "Content-Length: 8"
  482. "\r\n\r\n";
  483. request << "HTTPDATA";
  484. TStringInput input(request.Str());
  485. THttpInput httpInput(&input);
  486. UNIT_ASSERT(httpInput.HasContent());
  487. UNIT_ASSERT_VALUES_EQUAL(httpInput.ReadAll(), "HTTPDATA");
  488. }
  489. {
  490. TStringStream request;
  491. request << "POST / HTTP/1.1\r\n"
  492. "Host: yandex.ru\r\n"
  493. "Transfer-Encoding: chunked"
  494. "\r\n\r\n";
  495. request << "8\r\nHTTPDATA\r\n0\r\n";
  496. TStringInput input(request.Str());
  497. THttpInput httpInput(&input);
  498. UNIT_ASSERT(httpInput.HasContent());
  499. UNIT_ASSERT_VALUES_EQUAL(httpInput.ReadAll(), "HTTPDATA");
  500. }
  501. }
  502. Y_UNIT_TEST(TestHttpInputHeadRequest) {
  503. class THeadOnlyInput: public IInputStream {
  504. public:
  505. THeadOnlyInput() = default;
  506. private:
  507. size_t DoRead(void* buf, size_t len) override {
  508. if (Eof_) {
  509. ythrow yexception() << "should not read after EOF";
  510. }
  511. const size_t toWrite = Min(len, Data_.size() - Pos_);
  512. if (toWrite == 0) {
  513. Eof_ = true;
  514. return 0;
  515. }
  516. memcpy(buf, Data_.data() + Pos_, toWrite);
  517. Pos_ += toWrite;
  518. return toWrite;
  519. }
  520. private:
  521. TString Data_{TStringBuf("HEAD / HTTP/1.1\r\nHost: yandex.ru\r\n\r\n")};
  522. size_t Pos_{0};
  523. bool Eof_{false};
  524. };
  525. THeadOnlyInput input;
  526. THttpInput httpInput(&input);
  527. UNIT_ASSERT(!httpInput.HasContent());
  528. UNIT_ASSERT_VALUES_EQUAL(httpInput.ReadAll(), "");
  529. }
  530. Y_UNIT_TEST(TestHttpOutputResponseToHeadRequestNoZeroChunk) {
  531. TStringStream request;
  532. request << "HEAD / HTTP/1.1\r\n"
  533. "Host: yandex.ru\r\n"
  534. "Connection: Keep-Alive\r\n"
  535. "\r\n";
  536. TStringInput input(request.Str());
  537. THttpInput httpInput(&input);
  538. TStringStream outBuf;
  539. THttpOutput out(&outBuf, &httpInput);
  540. out.EnableKeepAlive(true);
  541. out << "HTTP/1.1 200 OK\r\nConnection: Keep-Alive\r\n\r\n";
  542. out << "";
  543. out.Finish();
  544. TString result = outBuf.Str();
  545. UNIT_ASSERT(!result.Contains(TStringBuf("0\r\n")));
  546. }
  547. Y_UNIT_TEST(TestHttpOutputDisableCompressionHeader) {
  548. TMemoryInput request("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n\r\n");
  549. const TString data = "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";
  550. THttpInput httpInput(&request);
  551. TString result;
  552. {
  553. TStringOutput output(result);
  554. THttpOutput httpOutput(&output, &httpInput);
  555. httpOutput.EnableCompressionHeader(false);
  556. httpOutput << "HTTP/1.1 200 OK\r\n"
  557. "content-encoding: gzip\r\n"
  558. "\r\n" + data;
  559. httpOutput.Finish();
  560. }
  561. UNIT_ASSERT(result.Contains("content-encoding: gzip"));
  562. UNIT_ASSERT(result.Contains(data));
  563. }
  564. size_t DoTestHttpOutputSize(const TString& res, bool enableCompession) {
  565. TTestHttpServer serverImpl(res);
  566. TPortManager pm;
  567. const ui16 port = pm.GetPort();
  568. THttpServer server(&serverImpl,
  569. THttpServer::TOptions(port)
  570. .EnableKeepAlive(true)
  571. .EnableCompression(enableCompession));
  572. UNIT_ASSERT(server.Start());
  573. TNetworkAddress addr("localhost", port);
  574. TSocket s(addr);
  575. {
  576. TSocketOutput so(s);
  577. THttpOutput out(&so);
  578. out << "GET / HTTP/1.1\r\n"
  579. "Host: www.yandex.ru\r\n"
  580. "Connection: Keep-Alive\r\n"
  581. "Accept-Encoding: gzip\r\n"
  582. "\r\n";
  583. out.Finish();
  584. }
  585. TSocketInput si(s);
  586. THttpInput input(&si);
  587. unsigned httpCode = ParseHttpRetCode(input.FirstLine());
  588. UNIT_ASSERT_VALUES_EQUAL(httpCode, 200u);
  589. UNIT_ASSERT_VALUES_EQUAL(res, input.ReadAll());
  590. server.Stop();
  591. return serverImpl.LastRequestSentSize();
  592. }
  593. Y_UNIT_TEST(TestHttpOutputSize) {
  594. TString res = "qqqqqq";
  595. UNIT_ASSERT_VALUES_EQUAL(res.size(), DoTestHttpOutputSize(res, false));
  596. UNIT_ASSERT_VALUES_UNEQUAL(res.size(), DoTestHttpOutputSize(res, true));
  597. }
  598. } // THttpStreamTest suite