PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/mordor/tests/http_server.cpp

http://github.com/mozy/mordor
C++ | 1027 lines | 867 code | 125 blank | 35 comment | 92 complexity | 44706c6aaf1e8af07664f0aff98dfb95 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Copyright (c) 2009 - Mozy, Inc.
  2. #include <boost/bind.hpp>
  3. #include "mordor/fiber.h"
  4. #include "mordor/http/broker.h"
  5. #include "mordor/http/client.h"
  6. #include "mordor/http/multipart.h"
  7. #include "mordor/http/parser.h"
  8. #include "mordor/http/server.h"
  9. #include "mordor/streams/duplex.h"
  10. #include "mordor/streams/limited.h"
  11. #include "mordor/streams/null.h"
  12. #include "mordor/streams/memory.h"
  13. #include "mordor/streams/pipe.h"
  14. #include "mordor/streams/random.h"
  15. #include "mordor/streams/test.h"
  16. #include "mordor/streams/transfer.h"
  17. #include "mordor/test/test.h"
  18. #include "mordor/timer.h"
  19. #include "mordor/util.h"
  20. #include "mordor/workerpool.h"
  21. using namespace Mordor;
  22. using namespace Mordor::HTTP;
  23. using namespace Mordor::Test;
  24. namespace {
  25. struct DummyException {};
  26. }
  27. static void
  28. httpRequest(ServerRequest::ptr request)
  29. {
  30. const std::string &method = request->request().requestLine.method;
  31. if (method == HTTP::GET || method == HTTP::HEAD || method == HTTP::PUT ||
  32. method == HTTP::POST) {
  33. if (request->request().requestLine.uri == "/exceptionReadingRequest") {
  34. MORDOR_ASSERT(request->hasRequestBody());
  35. MORDOR_THROW_EXCEPTION(DummyException());
  36. }
  37. request->response().entity.contentLength = request->request().entity.contentLength;
  38. request->response().entity.contentType = request->request().entity.contentType;
  39. request->response().general.transferEncoding = request->request().general.transferEncoding;
  40. request->response().status.status = OK;
  41. request->response().entity.extension = request->request().entity.extension;
  42. if (request->hasRequestBody()) {
  43. if (request->request().requestLine.method != HEAD) {
  44. if (request->request().entity.contentType.type == "multipart") {
  45. Multipart::ptr requestMultipart = request->requestMultipart();
  46. Multipart::ptr responseMultipart = request->responseMultipart();
  47. for (BodyPart::ptr requestPart = requestMultipart->nextPart();
  48. requestPart;
  49. requestPart = requestMultipart->nextPart()) {
  50. BodyPart::ptr responsePart = responseMultipart->nextPart();
  51. responsePart->headers() = requestPart->headers();
  52. transferStream(requestPart->stream(), responsePart->stream());
  53. responsePart->stream()->close();
  54. }
  55. responseMultipart->finish();
  56. } else {
  57. respondStream(request, request->requestStream());
  58. return;
  59. }
  60. } else {
  61. request->finish();
  62. }
  63. } else {
  64. request->response().entity.contentLength = 0;
  65. request->finish();
  66. }
  67. } else {
  68. respondError(request, METHOD_NOT_ALLOWED);
  69. }
  70. }
  71. static void
  72. doSingleRequest(const char *request, Response &response)
  73. {
  74. Stream::ptr input(new MemoryStream(Buffer(request)));
  75. MemoryStream::ptr output(new MemoryStream());
  76. Stream::ptr stream(new DuplexStream(input, output));
  77. ServerConnection::ptr conn(new ServerConnection(stream, &httpRequest));
  78. WorkerPool pool;
  79. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  80. pool.dispatch();
  81. ResponseParser parser(response);
  82. parser.run(output->buffer());
  83. MORDOR_TEST_ASSERT(parser.complete());
  84. MORDOR_TEST_ASSERT(!parser.error());
  85. }
  86. static void catchExceptionInRespond(ServerRequest::ptr request, bool expect)
  87. {
  88. try {
  89. respondError(request, OK);
  90. if (expect) {
  91. MORDOR_TEST_ASSERT(!"expect exception thrown");
  92. }
  93. }
  94. catch (DummyException &) {
  95. if (!expect) {
  96. MORDOR_TEST_ASSERT(!"unexpected exception thrown");
  97. }
  98. }
  99. catch (...) {
  100. MORDOR_TEST_ASSERT(!"unknown exception thrown");
  101. }
  102. }
  103. static void throwDummyException(Stream::CloseType type)
  104. {
  105. if (type == Stream::BOTH) {
  106. MORDOR_THROW_EXCEPTION(DummyException());
  107. }
  108. }
  109. static void processClientRequest(bool closeClient)
  110. {
  111. std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream();
  112. pipe.first->write("GET / HTTP/1.0\r\n\r\n");
  113. TestStream::ptr testStream(new TestStream(pipe.second));
  114. // always throw DummyException when server stream close
  115. testStream->onClose(throwDummyException);
  116. // if client closed, server should not throw exception
  117. // otherwise, server should throw exception
  118. bool expectExceptionInServer = true;
  119. if (closeClient) {
  120. expectExceptionInServer = false;
  121. }
  122. ServerConnection::ptr conn(
  123. new ServerConnection(
  124. testStream,
  125. boost::bind(&catchExceptionInRespond, _1, expectExceptionInServer)
  126. )
  127. );
  128. WorkerPool pool;
  129. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  130. // yield so that onRemoteClose is called in ServerConnection::processRequests
  131. Scheduler::yield();
  132. if (closeClient) {
  133. pipe.first->close();
  134. }
  135. }
  136. MORDOR_UNITTEST(HTTPServer, closeClientBeforeServer)
  137. {
  138. processClientRequest(true);
  139. processClientRequest(false);
  140. }
  141. MORDOR_UNITTEST(HTTPServer, ServerConnectionDestroyedBeforeRemoteClose)
  142. {
  143. std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream();
  144. pipe.first->write("GET / HTTP/1.0\r\n\r\n");
  145. {
  146. ServerConnection::ptr conn(
  147. new ServerConnection(
  148. pipe.second,
  149. boost::bind(&catchExceptionInRespond, _1, false)
  150. )
  151. );
  152. WorkerPool pool;
  153. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  154. pool.dispatch();
  155. }
  156. try {
  157. pipe.first->close();
  158. }
  159. catch (...) {
  160. MORDOR_TEST_ASSERT(!"Crashed caused by remote close!");
  161. }
  162. }
  163. MORDOR_UNITTEST(HTTPServer, badRequest)
  164. {
  165. Response response;
  166. doSingleRequest("garbage", response);
  167. MORDOR_TEST_ASSERT_EQUAL(response.status.status, BAD_REQUEST);
  168. }
  169. MORDOR_UNITTEST(HTTPServer, parseError)
  170. {
  171. Response response;
  172. doSingleRequest("CONNECT / HTTP/1.0\r\n\r\n", response);
  173. MORDOR_TEST_ASSERT_EQUAL(response.status.status, BAD_REQUEST);
  174. }
  175. MORDOR_UNITTEST(HTTPServer, close10)
  176. {
  177. Response response;
  178. doSingleRequest(
  179. "GET / HTTP/1.0\r\n"
  180. "\r\n",
  181. response);
  182. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 0));
  183. MORDOR_TEST_ASSERT_EQUAL(response.status.status, OK);
  184. MORDOR_TEST_ASSERT(response.general.connection.find("close") != response.general.connection.end());
  185. }
  186. MORDOR_UNITTEST(HTTPServer, notSupportHttpVersion)
  187. {
  188. Response response;
  189. doSingleRequest(
  190. "GET / HTTP/2.0\r\n"
  191. "\r\n",
  192. response);
  193. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 1));
  194. MORDOR_TEST_ASSERT_EQUAL(response.status.status, HTTP_VERSION_NOT_SUPPORTED);
  195. MORDOR_TEST_ASSERT(response.general.connection.find("close") != response.general.connection.end());
  196. }
  197. MORDOR_UNITTEST(HTTPServer, keepAlive10)
  198. {
  199. Response response;
  200. doSingleRequest(
  201. "GET / HTTP/1.0\r\n"
  202. "Connection: Keep-Alive\r\n"
  203. "\r\n",
  204. response);
  205. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 0));
  206. MORDOR_TEST_ASSERT_EQUAL(response.status.status, OK);
  207. MORDOR_TEST_ASSERT(response.general.connection.find("Keep-Alive") != response.general.connection.end());
  208. MORDOR_TEST_ASSERT(response.general.connection.find("close") == response.general.connection.end());
  209. }
  210. MORDOR_UNITTEST(HTTPServer, noHost11)
  211. {
  212. Response response;
  213. doSingleRequest(
  214. "GET / HTTP/1.1\r\n"
  215. "\r\n",
  216. response);
  217. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 1));
  218. MORDOR_TEST_ASSERT_EQUAL(response.status.status, BAD_REQUEST);
  219. }
  220. MORDOR_UNITTEST(HTTPServer, close11)
  221. {
  222. Response response;
  223. doSingleRequest(
  224. "GET / HTTP/1.1\r\n"
  225. "Host: garbage\r\n"
  226. "Connection: close\r\n"
  227. "\r\n",
  228. response);
  229. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 1));
  230. MORDOR_TEST_ASSERT_EQUAL(response.status.status, OK);
  231. MORDOR_TEST_ASSERT(response.general.connection.find("close") != response.general.connection.end());
  232. }
  233. MORDOR_UNITTEST(HTTPServer, keepAlive11)
  234. {
  235. Response response;
  236. doSingleRequest(
  237. "GET / HTTP/1.1\r\n"
  238. "Host: garbage\r\n"
  239. "\r\n",
  240. response);
  241. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 1));
  242. MORDOR_TEST_ASSERT_EQUAL(response.status.status, OK);
  243. MORDOR_TEST_ASSERT(response.general.connection.find("close") == response.general.connection.end());
  244. }
  245. MORDOR_UNITTEST(HTTPServer, exceptionReadingRequest)
  246. {
  247. Response response;
  248. doSingleRequest(
  249. "GET /exceptionReadingRequest HTTP/1.1\r\n"
  250. "Host: garbage\r\n"
  251. "Content-Length: 5\r\n"
  252. "\r\n"
  253. "hello",
  254. response);
  255. MORDOR_TEST_ASSERT_EQUAL(response.status.ver, Version(1, 1));
  256. MORDOR_TEST_ASSERT_EQUAL(response.status.status, INTERNAL_SERVER_ERROR);
  257. }
  258. static void
  259. disconnectDuringResponseServer(const URI &uri, ServerRequest::ptr request)
  260. {
  261. Stream::ptr random(new RandomStream());
  262. random.reset(new LimitedStream(random, 4096));
  263. request->response().status.status = OK;
  264. request->response().general.transferEncoding.push_back("chunked");
  265. Stream::ptr response = request->responseStream();
  266. response->flush();
  267. // Yield so that the request can be cancelled while the response is in progress
  268. Scheduler::yield();
  269. transferStream(random, response);
  270. MORDOR_TEST_ASSERT_EXCEPTION(response->flush(), BrokenPipeException);
  271. }
  272. MORDOR_UNITTEST(HTTPServer, disconnectDuringResponse)
  273. {
  274. WorkerPool pool;
  275. MockConnectionBroker server(&disconnectDuringResponseServer);
  276. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server, &nop<ConnectionBroker *>));
  277. Request requestHeaders;
  278. requestHeaders.requestLine.uri = "http://localhost/";
  279. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  280. request->doRequest();
  281. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  282. // Don't read the response body
  283. request->cancel(true);
  284. }
  285. static void quickResponseServer(const URI &uri, ServerRequest::ptr request)
  286. {
  287. respondError(request, OK, "hello");
  288. }
  289. MORDOR_UNITTEST(HTTPServer, responseCompletesBeforeRequest)
  290. {
  291. WorkerPool pool;
  292. MockConnectionBroker server(&quickResponseServer);
  293. ClientConnection::ptr connection =
  294. server.getConnection("http://localhost/").first;
  295. Request requestHeaders;
  296. requestHeaders.requestLine.uri = "/responseCompletesBeforeRequest";
  297. requestHeaders.entity.contentLength = 5;
  298. ClientRequest::ptr request = connection->request(requestHeaders);
  299. request->doRequest();
  300. Stream::ptr requestStream = request->requestStream();
  301. requestStream->flush();
  302. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  303. transferStream(request->responseStream(), NullStream::get());
  304. requestStream->write("hello", 5);
  305. requestStream->close();
  306. }
  307. static void pipelineWait(const URI &uri, ServerRequest::ptr request)
  308. {
  309. request->processNextRequest();
  310. if (request->request().requestLine.uri == "/one") {
  311. Scheduler::yield();
  312. Scheduler::yield();
  313. Scheduler::yield();
  314. }
  315. request->response().entity.extension["X-Timestamp"] =
  316. boost::lexical_cast<std::string>(TimerManager::now());
  317. respondError(request, OK);
  318. }
  319. MORDOR_UNITTEST(HTTPServer, pipelineResponseWaits)
  320. {
  321. WorkerPool pool;
  322. MockConnectionBroker server(&pipelineWait);
  323. ClientConnection::ptr connection =
  324. server.getConnection("http://localhost/").first;
  325. Request requestHeaders;
  326. requestHeaders.requestLine.uri = "/one";
  327. ClientRequest::ptr request1 = connection->request(requestHeaders);
  328. request1->doRequest();
  329. requestHeaders.requestLine.uri = "/two";
  330. ClientRequest::ptr request2 = connection->request(requestHeaders);
  331. request2->doRequest();
  332. unsigned long long timeStamp1 = boost::lexical_cast<unsigned long long>(
  333. request1->response().entity.extension.find("X-Timestamp")->second);
  334. unsigned long long timeStamp2 = boost::lexical_cast<unsigned long long>(
  335. request2->response().entity.extension.find("X-Timestamp")->second);
  336. MORDOR_TEST_ASSERT_LESS_THAN(timeStamp2, timeStamp1);
  337. }
  338. static void pipelineExceptions(const URI &uri, ServerRequest::ptr request)
  339. {
  340. static bool answeredTwo = false;
  341. request->processNextRequest();
  342. if (request->request().requestLine.uri == "/one") {
  343. while (!answeredTwo) {
  344. Scheduler::yield();
  345. }
  346. MORDOR_THROW_EXCEPTION(DummyException());
  347. }
  348. if (request->request().requestLine.uri == "/two") {
  349. answeredTwo = true;
  350. }
  351. respondError(request, OK);
  352. }
  353. MORDOR_UNITTEST(HTTPServer, pipelineResponseException)
  354. {
  355. WorkerPool pool;
  356. MockConnectionBroker server(&pipelineExceptions);
  357. ClientConnection::ptr connection =
  358. server.getConnection("http://localhost/").first;
  359. Request requestHeaders;
  360. requestHeaders.requestLine.uri = "/one";
  361. ClientRequest::ptr request1 = connection->request(requestHeaders);
  362. request1->doRequest();
  363. requestHeaders.requestLine.uri = "/two";
  364. ClientRequest::ptr request2 = connection->request(requestHeaders);
  365. request2->doRequest();
  366. }
  367. static void respondToTwo(TestStream::ptr testOutput, Fiber::ptr &fiber,
  368. int &sequence)
  369. {
  370. testOutput->onWrite(NULL);
  371. while (!fiber)
  372. Scheduler::yield();
  373. MORDOR_TEST_ASSERT_EQUAL(++sequence, 3);
  374. Scheduler::getThis()->schedule(fiber);
  375. Scheduler::yield();
  376. MORDOR_TEST_ASSERT_EQUAL(++sequence, 5);
  377. }
  378. static void yieldTwoServer(ServerRequest::ptr request, Fiber::ptr &fiber2,
  379. int &sequence)
  380. {
  381. request->processNextRequest();
  382. if (request->request().requestLine.uri == "/two") {
  383. MORDOR_TEST_ASSERT_EQUAL(++sequence, 2);
  384. fiber2 = Fiber::getThis();
  385. Scheduler::yieldTo();
  386. MORDOR_TEST_ASSERT_EQUAL(++sequence, 4);
  387. respondError(request, OK);
  388. MORDOR_TEST_ASSERT_EQUAL(++sequence, 7);
  389. return;
  390. }
  391. MORDOR_TEST_ASSERT_EQUAL(++sequence, 1);
  392. respondError(request, OK);
  393. MORDOR_TEST_ASSERT_EQUAL(++sequence, 6);
  394. }
  395. MORDOR_UNITTEST(HTTPServer, pipelineResponseWhilePriorResponseFlushing)
  396. {
  397. Fiber::ptr fiber2;
  398. int sequence = 0;
  399. Stream::ptr input(new MemoryStream(Buffer(
  400. "GET /one HTTP/1.1\r\n"
  401. "Host: localhost\r\n"
  402. "\r\n"
  403. "GET /two HTTP/1.1\r\n"
  404. "Host: localhost\r\n"
  405. "\r\n")));
  406. MemoryStream::ptr output(new MemoryStream());
  407. TestStream::ptr testOutput(new TestStream(output));
  408. testOutput->onWrite(boost::bind(&respondToTwo, testOutput,
  409. boost::ref(fiber2), boost::ref(sequence)), 0);
  410. Stream::ptr stream(new DuplexStream(input, testOutput));
  411. ServerConnection::ptr conn(new ServerConnection(stream,
  412. boost::bind(&yieldTwoServer, _1, boost::ref(fiber2),
  413. boost::ref(sequence))));
  414. WorkerPool pool;
  415. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  416. pool.dispatch();
  417. }
  418. enum ReadFinishPos
  419. {
  420. BEFORE,
  421. RESPOND1,
  422. RESPOND2,
  423. AFTER1,
  424. AFTER2
  425. };
  426. static void readFinishServer(ServerRequest::ptr request, Stream::ptr client, ReadFinishPos pos)
  427. {
  428. if (pos == BEFORE) {
  429. client->close(Stream::WRITE);
  430. }
  431. request->processNextRequest();
  432. if (request->request().requestLine.uri == "/1") {
  433. if (pos == RESPOND1) {
  434. client->close(Stream::WRITE);
  435. }
  436. respondError(request, ACCEPTED);
  437. if (pos == AFTER1) {
  438. client->close(Stream::WRITE);
  439. }
  440. return;
  441. }
  442. if (pos == RESPOND2) {
  443. client->close(Stream::WRITE);
  444. }
  445. respondError(request, OK);
  446. if (pos == AFTER2) {
  447. client->close(Stream::WRITE);
  448. }
  449. }
  450. static void testPipelineReadFinish(ReadFinishPos pos)
  451. {
  452. std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream();
  453. pipe.first->write("GET /1 HTTP/1.1\r\nHost: localhost\r\n\r\n");
  454. pipe.first->write("GET /2 HTTP/1.1\r\nHost: localhost\r\n\r\n");
  455. ServerConnection::ptr conn(new ServerConnection(pipe.second,
  456. boost::bind(&readFinishServer, _1, pipe.first, pos)));
  457. WorkerPool pool;
  458. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  459. Buffer firstResponse;
  460. pipe.first->read(firstResponse, 100);
  461. MORDOR_TEST_ASSERT_EQUAL(firstResponse.toString(),
  462. "HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n");
  463. Buffer secondResponse;
  464. pipe.first->read(secondResponse, 100);
  465. MORDOR_TEST_ASSERT_EQUAL(secondResponse.toString(),
  466. "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
  467. }
  468. MORDOR_UNITTEST(HTTPServer, pipelineReadFinish)
  469. {
  470. testPipelineReadFinish(BEFORE);
  471. testPipelineReadFinish(RESPOND1);
  472. testPipelineReadFinish(RESPOND2);
  473. testPipelineReadFinish(AFTER1);
  474. testPipelineReadFinish(AFTER2);
  475. }
  476. static void testNonePipelineReadFinish(ReadFinishPos pos)
  477. {
  478. std::pair<Stream::ptr, Stream::ptr> pipe = pipeStream();
  479. pipe.first->write("GET /1 HTTP/1.0\r\nHost: localhost\r\n\r\n");
  480. ServerConnection::ptr conn(new ServerConnection(pipe.second,
  481. boost::bind(&readFinishServer, _1, pipe.first, pos)));
  482. WorkerPool pool;
  483. pool.schedule(boost::bind(&ServerConnection::processRequests, conn));
  484. Buffer firstResponse;
  485. pipe.first->read(firstResponse, 100);
  486. MORDOR_TEST_ASSERT_EQUAL(firstResponse.toString(),
  487. "HTTP/1.0 202 Accepted\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
  488. }
  489. MORDOR_UNITTEST(HTTPServer, nonePipelineReadFinish)
  490. {
  491. testNonePipelineReadFinish(BEFORE);
  492. testNonePipelineReadFinish(RESPOND1);
  493. testNonePipelineReadFinish(AFTER1);
  494. }
  495. namespace {
  496. class UnseekableStream : public FilterStream
  497. {
  498. public:
  499. UnseekableStream(Stream::ptr parent, bool seekable, bool sizeable)
  500. : FilterStream(parent),
  501. m_seekable(seekable),
  502. m_sizeable(sizeable)
  503. {}
  504. bool supportsSeek() { return m_seekable; }
  505. bool supportsSize() { return m_sizeable; }
  506. using FilterStream::read;
  507. size_t read(Buffer &buffer, size_t length)
  508. { return parent()->read(buffer, length); }
  509. private:
  510. bool m_seekable, m_sizeable;
  511. };
  512. }
  513. static void streamServer(const URI &uri, ServerRequest::ptr request)
  514. {
  515. Stream::ptr memoryStream(new MemoryStream(Buffer("hello world!")));
  516. request->response().response.eTag = ETag("hello");
  517. if (request->request().requestLine.uri == "/unseekable")
  518. memoryStream.reset(new UnseekableStream(memoryStream, false, true));
  519. if (request->request().requestLine.uri == "/unsizeable")
  520. memoryStream.reset(new UnseekableStream(memoryStream, true, false));
  521. if (request->request().requestLine.uri == "/unseekable/unsizeable")
  522. memoryStream.reset(new UnseekableStream(memoryStream, false, false));
  523. respondStream(request, memoryStream);
  524. }
  525. static void verifyResponse(ClientRequest::ptr &request,
  526. bool head, bool seekable, bool sizeable, bool trailers,
  527. ContentRange expectedRange,
  528. bool partialContentEvenIfFullFileOnUnsizeableWithTrailers = false)
  529. {
  530. if (!sizeable && !trailers && expectedRange.instance != ~0ull) {
  531. expectedRange.first = 0;
  532. expectedRange.last = expectedRange.instance - 1;
  533. expectedRange.instance = ~0ull;
  534. }
  535. if (sizeable || !trailers)
  536. partialContentEvenIfFullFileOnUnsizeableWithTrailers = false;
  537. if (expectedRange.instance == ~0ull &&
  538. !partialContentEvenIfFullFileOnUnsizeableWithTrailers) {
  539. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  540. if (!sizeable) {
  541. MORDOR_TEST_ASSERT(!request->response().general.transferEncoding.empty());
  542. } else {
  543. MORDOR_TEST_ASSERT_EQUAL(request->response().entity.contentLength,
  544. expectedRange.last - expectedRange.first + 1);
  545. }
  546. } else {
  547. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  548. PARTIAL_CONTENT);
  549. if (!trailers) {
  550. MORDOR_TEST_ASSERT_EQUAL(expectedRange,
  551. request->response().entity.contentRange);
  552. MORDOR_TEST_ASSERT_EQUAL(request->response().entity.contentLength,
  553. expectedRange.last - expectedRange.first + 1);
  554. } else {
  555. MORDOR_TEST_ASSERT(!request->response().general.transferEncoding.empty());
  556. }
  557. }
  558. if (trailers || sizeable) {
  559. MORDOR_TEST_ASSERT_EQUAL(request->response().response.acceptRanges.size(),
  560. 1u);
  561. MORDOR_TEST_ASSERT_EQUAL(*request->response().response.acceptRanges.begin(),
  562. "bytes");
  563. }
  564. if (!head) {
  565. MemoryStream response;
  566. transferStream(request->responseStream(), response);
  567. MemoryStream expected("hello world!");
  568. expected.truncate(expectedRange.last + 1);
  569. expected.seek(expectedRange.first);
  570. MORDOR_TEST_ASSERT(response.buffer() == expected.readBuffer());
  571. if (trailers && expectedRange.instance != ~0ull)
  572. MORDOR_TEST_ASSERT_EQUAL(expectedRange,
  573. request->responseTrailer().contentRange);
  574. }
  575. }
  576. static void doRespondStream(RequestBroker &requestBroker, bool head,
  577. bool seekable, bool sizeable, bool trailers)
  578. {
  579. Request requestHeaders;
  580. if (head)
  581. requestHeaders.requestLine.method = HEAD;
  582. requestHeaders.requestLine.uri = "http://localhost/";
  583. if (!seekable)
  584. requestHeaders.requestLine.uri.path.append("unseekable");
  585. if (!sizeable)
  586. requestHeaders.requestLine.uri.path.append("unsizeable");
  587. if (trailers)
  588. requestHeaders.request.te.push_back("trailers");
  589. // Request the whole stream
  590. MemoryStream response;
  591. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  592. verifyResponse(request, head, seekable, sizeable, trailers,
  593. ContentRange(0, 11, ~0ull));
  594. // Request just the first byte
  595. requestHeaders.request.range.push_back(std::make_pair(0ull, 0ull));
  596. request = requestBroker.request(requestHeaders);
  597. verifyResponse(request, head, seekable, sizeable, trailers,
  598. ContentRange(0, 0, 12));
  599. // Request second byte until the end
  600. requestHeaders.request.range.clear();
  601. requestHeaders.request.range.push_back(std::make_pair(1ull, ~0ull));
  602. request = requestBroker.request(requestHeaders);
  603. verifyResponse(request, head, seekable, sizeable, trailers,
  604. ContentRange(1, 11, 12));
  605. // Request last byte
  606. requestHeaders.request.range.clear();
  607. requestHeaders.request.range.push_back(std::make_pair(~0ull, 1ull));
  608. request = requestBroker.request(requestHeaders);
  609. verifyResponse(request, head, seekable, sizeable, false,
  610. ContentRange(11, 11, 12), true);
  611. // Request entire file
  612. requestHeaders.request.range.clear();
  613. requestHeaders.request.range.push_back(std::make_pair(0ull, 11ull));
  614. request = requestBroker.request(requestHeaders);
  615. verifyResponse(request, head, seekable, sizeable, trailers,
  616. ContentRange(0, 11, ~0ull), true);
  617. // Request entire file, with a suffix range
  618. requestHeaders.request.range.clear();
  619. requestHeaders.request.range.push_back(std::make_pair(~0ull, 12ull));
  620. request = requestBroker.request(requestHeaders);
  621. verifyResponse(request, head, seekable, sizeable, false,
  622. ContentRange(0, 11, ~0ull));
  623. // Invalid range gets full file
  624. requestHeaders.request.range.clear();
  625. requestHeaders.request.range.push_back(std::make_pair(1ull, 0ull));
  626. request = requestBroker.request(requestHeaders);
  627. verifyResponse(request, head, seekable, sizeable, trailers,
  628. ContentRange(0, 11, ~0ull));
  629. if (sizeable || trailers) {
  630. // Beyond end of file
  631. requestHeaders.request.range.clear();
  632. requestHeaders.request.range.push_back(std::make_pair(13ull, 13ull));
  633. request = requestBroker.request(requestHeaders);
  634. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  635. REQUESTED_RANGE_NOT_SATISFIABLE);
  636. request->finish();
  637. // Exactly at end of file
  638. requestHeaders.request.range.clear();
  639. requestHeaders.request.range.push_back(std::make_pair(12ull, 12ull));
  640. request = requestBroker.request(requestHeaders);
  641. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  642. REQUESTED_RANGE_NOT_SATISFIABLE);
  643. request->finish();
  644. }
  645. // Suffix range beyond end of file
  646. requestHeaders.request.range.clear();
  647. requestHeaders.request.range.push_back(std::make_pair(~0ull, 13ull));
  648. request = requestBroker.request(requestHeaders);
  649. verifyResponse(request, head, seekable, sizeable, trailers,
  650. ContentRange(0, 11, ~0ull));
  651. // Non-contiguous range
  652. if (sizeable) {
  653. requestHeaders.request.range.clear();
  654. requestHeaders.request.range.push_back(std::make_pair(0ull, 0ull));
  655. requestHeaders.request.range.push_back(std::make_pair(2ull, 3ull));
  656. request = requestBroker.request(requestHeaders);
  657. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, PARTIAL_CONTENT);
  658. MORDOR_TEST_ASSERT_EQUAL(request->response().entity.contentType.type,
  659. "multipart");
  660. MORDOR_TEST_ASSERT_EQUAL(request->response().entity.contentType.subtype,
  661. "byteranges");
  662. if (!head) {
  663. Multipart::ptr multipart = request->responseMultipart();
  664. BodyPart::ptr part = multipart->nextPart();
  665. MORDOR_TEST_ASSERT_EQUAL(part->headers().contentRange,
  666. ContentRange(0ull, 0ull, sizeable ? 12ull : ~0ull));
  667. transferStream(part->stream(), response);
  668. MORDOR_TEST_ASSERT(response.buffer() == "h");
  669. response.truncate(0ull);
  670. response.seek(0ull);
  671. part = multipart->nextPart();
  672. MORDOR_TEST_ASSERT_EQUAL(part->headers().contentRange,
  673. ContentRange(2ull, 3ull, sizeable ? 12ull : ~0ull));
  674. transferStream(part->stream(), response);
  675. MORDOR_TEST_ASSERT(response.buffer() == "ll");
  676. response.truncate(0ull);
  677. response.seek(0ull);
  678. }
  679. }
  680. }
  681. MORDOR_UNITTEST(HTTPServer, respondStreamRegular)
  682. {
  683. WorkerPool pool;
  684. MockConnectionBroker server(&streamServer);
  685. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  686. &nop<ConnectionBroker *>));
  687. doRespondStream(requestBroker, false, true, true, false);
  688. doRespondStream(requestBroker, true, true, true, false);
  689. }
  690. MORDOR_UNITTEST(HTTPServer, respondStreamUnseekable)
  691. {
  692. WorkerPool pool;
  693. MockConnectionBroker server(&streamServer);
  694. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  695. &nop<ConnectionBroker *>));
  696. doRespondStream(requestBroker, false, false, true, false);
  697. doRespondStream(requestBroker, true, false, true, false);
  698. }
  699. MORDOR_UNITTEST(HTTPServer, respondStreamUnsizeable)
  700. {
  701. WorkerPool pool;
  702. MockConnectionBroker server(&streamServer);
  703. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  704. &nop<ConnectionBroker *>));
  705. doRespondStream(requestBroker, false, true, false, false);
  706. doRespondStream(requestBroker, true, true, false, false);
  707. doRespondStream(requestBroker, false, true, false, true);
  708. doRespondStream(requestBroker, true, true, false, true);
  709. }
  710. MORDOR_UNITTEST(HTTPServer, respondStreamUnseekableUnsizeable)
  711. {
  712. WorkerPool pool;
  713. MockConnectionBroker server(&streamServer);
  714. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  715. &nop<ConnectionBroker *>));
  716. doRespondStream(requestBroker, false, false, false, false);
  717. doRespondStream(requestBroker, true, false, false, false);
  718. doRespondStream(requestBroker, false, false, false, true);
  719. doRespondStream(requestBroker, true, false, false, true);
  720. }
  721. static void exceptionAfterCommitServer(const URI &uri,
  722. ServerRequest::ptr request)
  723. {
  724. request->response().entity.contentLength = 10;
  725. request->responseStream()->write("hello", 5);
  726. request->responseStream()->flush();
  727. throw std::runtime_error("Expected");
  728. }
  729. MORDOR_UNITTEST(HTTPServer, exceptionAfterCommit)
  730. {
  731. WorkerPool pool;
  732. MockConnectionBroker server(&exceptionAfterCommitServer);
  733. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  734. &nop<ConnectionBroker *>));
  735. Request requestHeaders;
  736. requestHeaders.requestLine.uri = "http://localhost/";
  737. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  738. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  739. MORDOR_TEST_ASSERT_EQUAL(request->response().entity.contentLength, 10u);
  740. char buffer[10];
  741. Stream::ptr responseStream = request->responseStream();
  742. MORDOR_TEST_ASSERT_EQUAL(responseStream->read(buffer, 10), 5u);
  743. buffer[5] = '\0';
  744. MORDOR_TEST_ASSERT_EQUAL((const char *)buffer, "hello");
  745. MORDOR_TEST_ASSERT_EXCEPTION(responseStream->read(buffer, 10),
  746. BrokenPipeException);
  747. }
  748. MORDOR_UNITTEST(HTTPServer, respondStreamIfRange)
  749. {
  750. WorkerPool pool;
  751. MockConnectionBroker server(&streamServer);
  752. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  753. &nop<ConnectionBroker *>));
  754. Request requestHeaders;
  755. requestHeaders.requestLine.uri = "http://localhost/";
  756. // Should respect the Range
  757. requestHeaders.request.range.push_back(std::make_pair(0ull, 0ull));
  758. requestHeaders.request.ifRange = ETag("hello");
  759. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  760. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, PARTIAL_CONTENT);
  761. request->finish();
  762. // Should be a strong comparison
  763. requestHeaders.request.ifRange = ETag("hello", true);
  764. request = requestBroker.request(requestHeaders);
  765. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  766. request->finish();
  767. // Should return the entire entity
  768. requestHeaders.request.ifRange = ETag("world");
  769. request = requestBroker.request(requestHeaders);
  770. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  771. request->finish();
  772. }
  773. static void ifMatchServer(const URI &uri, ServerRequest::ptr request)
  774. {
  775. if (request->request().requestLine.uri != "/new")
  776. request->response().response.eTag = ETag("hello");
  777. if (!ifMatch(request, request->response().response.eTag))
  778. return;
  779. respondError(request, OK);
  780. }
  781. MORDOR_UNITTEST(HTTPServer, ifMatch)
  782. {
  783. WorkerPool pool;
  784. MockConnectionBroker server(&ifMatchServer);
  785. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  786. &nop<ConnectionBroker *>));
  787. Request requestHeaders;
  788. requestHeaders.requestLine.uri = "http://localhost/";
  789. requestHeaders.requestLine.method = PUT;
  790. // PUT / If-Match: "world"
  791. requestHeaders.request.ifMatch.insert(ETag("world"));
  792. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  793. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  794. PRECONDITION_FAILED);
  795. // PUT / If-Match: "hello", "world"
  796. requestHeaders.request.ifMatch.insert(ETag("hello"));
  797. request = requestBroker.request(requestHeaders);
  798. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  799. // PUT / If-Match: *
  800. requestHeaders.request.ifMatch.clear();
  801. requestHeaders.request.ifMatch.insert(ETag());
  802. request = requestBroker.request(requestHeaders);
  803. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, OK);
  804. // PUT /new If-Match: *
  805. requestHeaders.requestLine.uri.path.append("new");
  806. request = requestBroker.request(requestHeaders);
  807. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  808. PRECONDITION_FAILED);
  809. // PUT /new If-Match: "world"
  810. requestHeaders.request.ifMatch.clear();
  811. requestHeaders.request.ifMatch.insert(ETag("world"));
  812. request = requestBroker.request(requestHeaders);
  813. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  814. PRECONDITION_FAILED);
  815. // PUT / If-None-Match: "world"
  816. requestHeaders.requestLine.uri = "http://localhost/";
  817. requestHeaders.request.ifMatch.clear();
  818. requestHeaders.request.ifNoneMatch.insert(ETag("world"));
  819. request = requestBroker.request(requestHeaders);
  820. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  821. OK);
  822. // PUT / If-None-Match: "hello", "world"
  823. requestHeaders.request.ifNoneMatch.insert(ETag("hello"));
  824. request = requestBroker.request(requestHeaders);
  825. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  826. PRECONDITION_FAILED);
  827. // PUT / If-None-Match: *
  828. requestHeaders.request.ifNoneMatch.clear();
  829. requestHeaders.request.ifNoneMatch.insert(ETag());
  830. request = requestBroker.request(requestHeaders);
  831. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  832. PRECONDITION_FAILED);
  833. // PUT /new If-None-Match: *
  834. requestHeaders.requestLine.uri.path.append("new");
  835. request = requestBroker.request(requestHeaders);
  836. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  837. OK);
  838. // PUT /new If-None-Match: "hello"
  839. requestHeaders.request.ifNoneMatch.clear();
  840. requestHeaders.request.ifNoneMatch.insert(ETag("hello"));
  841. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  842. OK);
  843. requestHeaders.requestLine.method = GET;
  844. // GET / If-None-Match: "world"
  845. requestHeaders.requestLine.uri = "http://localhost/";
  846. requestHeaders.request.ifNoneMatch.clear();
  847. requestHeaders.request.ifNoneMatch.insert(ETag("world"));
  848. request = requestBroker.request(requestHeaders);
  849. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  850. OK);
  851. // GET / If-None-Match: "hello", "world"
  852. requestHeaders.request.ifNoneMatch.insert(ETag("hello"));
  853. request = requestBroker.request(requestHeaders);
  854. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  855. NOT_MODIFIED);
  856. MORDOR_TEST_ASSERT_EQUAL(request->response().response.eTag, ETag("hello"));
  857. // GET / If-None-Match: *
  858. requestHeaders.request.ifNoneMatch.clear();
  859. requestHeaders.request.ifNoneMatch.insert(ETag());
  860. request = requestBroker.request(requestHeaders);
  861. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status,
  862. NOT_MODIFIED);
  863. MORDOR_TEST_ASSERT_EQUAL(request->response().response.eTag, ETag("hello"));
  864. }
  865. void keepAliveAfterImplicitFinishWithRequestBodyServer(const URI &uri,
  866. ServerRequest::ptr request)
  867. {
  868. respondError(request, OK);
  869. }
  870. void writeHelloBody(ClientRequest::ptr request)
  871. {
  872. request->requestStream()->write("hello", 5u);
  873. request->requestStream()->close();
  874. }
  875. MORDOR_UNITTEST(HTTPServer, keepAliveAfterImplicitFinishWithRequestBody)
  876. {
  877. WorkerPool pool;
  878. MockConnectionBroker server(&keepAliveAfterImplicitFinishWithRequestBodyServer);
  879. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  880. &nop<ConnectionBroker *>));
  881. Request requestHeaders;
  882. requestHeaders.requestLine.uri = "http://localhost/";
  883. requestHeaders.requestLine.method = PUT;
  884. requestHeaders.entity.contentLength = 5;
  885. ClientRequest::ptr request = requestBroker.request(requestHeaders, false,
  886. &writeHelloBody);
  887. request->finish();
  888. // Now do it again, the server shouldn't have closed the connection
  889. request = requestBroker.request(requestHeaders, false, &writeHelloBody);
  890. }
  891. MORDOR_UNITTEST(HTTPServer, badRequestOnExpectContinueWithNoRequestBody)
  892. {
  893. WorkerPool pool;
  894. MockConnectionBroker server(&keepAliveAfterImplicitFinishWithRequestBodyServer);
  895. BaseRequestBroker requestBroker(ConnectionBroker::ptr(&server,
  896. &nop<ConnectionBroker *>));
  897. Request requestHeaders;
  898. requestHeaders.requestLine.uri = "http://localhost/";
  899. requestHeaders.request.expect.push_back("100-continue");
  900. ClientRequest::ptr request = requestBroker.request(requestHeaders);
  901. MORDOR_TEST_ASSERT_EQUAL(request->response().status.status, BAD_REQUEST);
  902. }