PageRenderTime 313ms CodeModel.GetById 61ms app.highlight 202ms RepoModel.GetById 39ms app.codeStats 1ms

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