PageRenderTime 546ms CodeModel.GetById 69ms app.highlight 415ms RepoModel.GetById 47ms app.codeStats 1ms

/mordor/http/client.cpp

http://github.com/mozy/mordor
C++ | 1369 lines | 1236 code | 67 blank | 66 comment | 380 complexity | 36e8329611c7fb7db6ff5d883831f85c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1// Copyright (c) 2009 - Mozy, Inc.
   2
   3#include "client.h"
   4
   5#include <algorithm>
   6
   7#include <boost/bind.hpp>
   8
   9#include "chunked.h"
  10#include "mordor/assert.h"
  11#include "mordor/fiber.h"
  12#include "mordor/log.h"
  13#include "mordor/scheduler.h"
  14#include "mordor/streams/limited.h"
  15#include "mordor/streams/notify.h"
  16#include "mordor/streams/null.h"
  17#include "mordor/streams/timeout.h"
  18#include "mordor/streams/transfer.h"
  19#include "mordor/timer.h"
  20#include "mordor/util.h"
  21#include "mordor/atomic.h"
  22#include "mordor/socket.h"
  23#include "multipart.h"
  24#include "parser.h"
  25
  26namespace Mordor {
  27namespace HTTP {
  28
  29static Logger::ptr g_log = Log::lookup("mordor:http:client");
  30
  31ClientConnection::ClientConnection(Stream::ptr stream, TimerManager *timerManager)
  32: Connection(stream),
  33  m_readTimeout(~0ull),
  34  m_idleTimeout(~0ull),
  35  m_timerManager(timerManager),
  36  m_currentRequest(m_pendingRequests.end()),
  37  m_allowNewRequests(true),
  38  m_priorRequestFailed(false),
  39  m_requestCount(0),
  40  m_maxRequestCount(~0ull),
  41  m_priorResponseFailed(~0ull),
  42  m_priorResponseClosed(~0ull)
  43{
  44    static Atomic<size_t> connectionCount(0);
  45    m_connectionNumber = ++connectionCount;
  46    MORDOR_LOG_TRACE(g_log) << "ClientConnection " << m_connectionNumber << " = " << this;
  47
  48    if (timerManager) {
  49        FilterStream::ptr previous;
  50        FilterStream::ptr filter = boost::dynamic_pointer_cast<FilterStream>(m_stream);
  51        while (filter) {
  52            previous = filter;
  53            filter = boost::dynamic_pointer_cast<FilterStream>(filter->parent());
  54        }
  55        // Put the timeout stream as close to the actual source stream as
  56        // possible, to avoid registering timeouts for stuff that's going to
  57        // complete immediately anyway
  58        if (previous) {
  59            m_timeoutStream.reset(new TimeoutStream(previous->parent(),
  60                *timerManager));
  61            previous->parent(m_timeoutStream);
  62        } else {
  63            m_timeoutStream.reset(new TimeoutStream(m_stream, *timerManager));
  64            m_stream = m_timeoutStream;
  65        }
  66    }
  67}
  68
  69ClientConnection::~ClientConnection()
  70{
  71    if (m_idleTimer)
  72        m_idleTimer->cancel();
  73}
  74
  75ClientRequest::ptr
  76ClientConnection::request(const Request &requestHeaders)
  77{
  78    ClientRequest::ptr request(new ClientRequest(shared_from_this(), requestHeaders));
  79    request->waitForRequest();
  80    return request;
  81}
  82
  83bool
  84ClientConnection::newRequestsAllowed()
  85{
  86    boost::mutex::scoped_lock lock(m_mutex);
  87    return m_allowNewRequests && m_priorResponseClosed == ~0ull &&
  88        !m_priorRequestFailed && m_priorResponseFailed == ~0ull &&
  89        m_requestCount < m_maxRequestCount;
  90}
  91
  92size_t
  93ClientConnection::outstandingRequests()
  94{
  95    boost::mutex::scoped_lock lock(m_mutex);
  96    invariant();
  97    return m_pendingRequests.size();
  98}
  99
 100bool
 101ClientConnection::supportsTimeouts() const
 102{
 103    return !!m_timerManager;
 104}
 105
 106void
 107ClientConnection::readTimeout(unsigned long long us)
 108{
 109    MORDOR_ASSERT(m_timeoutStream);
 110    m_readTimeout = us;
 111}
 112
 113void
 114ClientConnection::writeTimeout(unsigned long long us)
 115{
 116    MORDOR_ASSERT(m_timeoutStream);
 117    m_timeoutStream->writeTimeout(us);
 118}
 119
 120void
 121ClientConnection::idleTimeout(unsigned long long us, boost::function<void ()> dg)
 122{
 123    MORDOR_ASSERT(us == ~0ull || m_timerManager);
 124    boost::mutex::scoped_lock lock(m_mutex);
 125    if (m_idleTimer) {
 126        m_idleTimer->cancel();
 127        m_idleTimer.reset();
 128    }
 129    m_idleTimeout = us;
 130    m_idleDg = dg;
 131    if (m_idleTimeout != ~0ull && m_pendingRequests.empty())
 132        m_idleTimer = m_timerManager->registerTimer(m_idleTimeout, dg);
 133}
 134
 135void
 136ClientConnection::scheduleNextRequest(ClientRequest *request)
 137{
 138    bool flush = false;
 139    boost::mutex::scoped_lock lock(m_mutex);
 140    invariant();
 141    MORDOR_ASSERT(m_currentRequest != m_pendingRequests.end());
 142    MORDOR_ASSERT(request == *m_currentRequest);
 143    MORDOR_ASSERT(request->m_requestState == ClientRequest::BODY ||
 144        request->m_requestState == ClientRequest::HEADERS);
 145    MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-"
 146        << request->m_requestNumber << " request complete";
 147    std::list<ClientRequest *>::iterator it(m_currentRequest);
 148    ++it;
 149    if (it == m_pendingRequests.end()) {
 150        // Do *not* advance m_currentRequest, because we can't let someone else
 151        // start another request until our flush completes below
 152        flush = true;
 153    } else {
 154        request->m_requestState = ClientRequest::COMPLETE;
 155        if (request->m_responseState >= ClientRequest::COMPLETE) {
 156            MORDOR_ASSERT(request == m_pendingRequests.front());
 157            m_pendingRequests.pop_front();
 158        }
 159        m_currentRequest = it;
 160        request = *it;
 161        request->m_requestState = ClientRequest::HEADERS;
 162        MORDOR_ASSERT(request->m_scheduler);
 163        MORDOR_ASSERT(request->m_fiber);
 164        MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-"
 165            << request->m_requestNumber << " scheduling request";
 166        request->m_scheduler->schedule(request->m_fiber);
 167        request->m_scheduler = NULL;
 168        request->m_fiber.reset();
 169    }
 170    Stream::CloseType closetype = Stream::NONE;
 171    if (flush) {
 172        // Take a trip through the Scheduler, trying to let someone else
 173        // attempt to pipeline
 174        if (Scheduler::getThis()) {
 175            lock.unlock();
 176            Scheduler::yield();
 177            lock.lock();
 178        }
 179        invariant();
 180        std::list<ClientRequest *>::iterator it(m_currentRequest);
 181        ++it;
 182        if (it == m_pendingRequests.end()) {
 183            // Nope, still the end, we really do have to flush
 184            lock.unlock();
 185        } else {
 186            flush = false;
 187        }
 188        if (flush) {
 189            flush = false;
 190            try {
 191                if (!m_allowNewRequests)
 192                    closetype = Stream::WRITE;
 193                MORDOR_LOG_TRACE(g_log) << m_connectionNumber << " flushing";
 194                m_stream->flush();
 195            } catch (...) {
 196                request->requestFailed();
 197                throw;
 198            }
 199            lock.lock();
 200            invariant();
 201        }
 202        request->m_requestState = ClientRequest::COMPLETE;
 203        ++m_currentRequest;
 204        if (request->m_responseState >= ClientRequest::COMPLETE) {
 205            MORDOR_ASSERT(request == m_pendingRequests.front());
 206            m_pendingRequests.pop_front();
 207            if (m_priorResponseClosed <= request->m_requestNumber ||
 208                m_priorResponseFailed <= request->m_requestNumber) {
 209                MORDOR_ASSERT(m_pendingRequests.empty());
 210                closetype = Stream::BOTH;
 211                lock.unlock();
 212                MORDOR_LOG_TRACE(g_log) << m_connectionNumber << " closing";
 213            }
 214        }
 215        // Someone else may have queued up while we were flushing
 216        if (!flush && m_currentRequest != m_pendingRequests.end()) {
 217            request = *m_currentRequest;
 218            request->m_requestState = ClientRequest::HEADERS;
 219            MORDOR_ASSERT(request->m_scheduler);
 220            MORDOR_ASSERT(request->m_fiber);
 221            MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-" << request->m_requestNumber << " scheduling request";
 222            request->m_scheduler->schedule(request->m_fiber);
 223            request->m_scheduler = NULL;
 224            request->m_fiber.reset();
 225        } else {
 226            if (m_timeoutStream) {
 227                if (lock.owns_lock()) {
 228                    // m_timeoutStream can be yield out. unlock the boost locker to avoid
 229                    // holding a boost thread lock while not occupying the thread which can lead
 230                    // to thread re-enter and deadlock refs #138359
 231                    lock.unlock();
 232                    m_timeoutStream->readTimeout(m_readTimeout);
 233                    lock.lock();
 234                } else {
 235                    m_timeoutStream->readTimeout(m_readTimeout);
 236                }
 237            }
 238        }
 239    }
 240    if (closetype != Stream::NONE) {
 241        if (closetype == Stream::BOTH || m_stream->supportsHalfClose()) {
 242            try {
 243                m_stream->close(closetype);
 244            } catch (...) {
 245            }
 246        }
 247    }
 248}
 249
 250void
 251ClientConnection::scheduleNextResponse(ClientRequest *request)
 252{
 253    bool close = false;
 254    {
 255        boost::mutex::scoped_lock lock(m_mutex);
 256        invariant();
 257        MORDOR_ASSERT(!m_pendingRequests.empty());
 258        MORDOR_ASSERT(request == m_pendingRequests.front());
 259        MORDOR_ASSERT(request->m_responseState == ClientRequest::BODY ||
 260            request->m_responseState == ClientRequest::HEADERS);
 261        request->m_responseState = ClientRequest::COMPLETE;
 262        MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-" << request->m_requestNumber << " response complete";
 263        std::list<ClientRequest *>::iterator it = m_pendingRequests.begin();
 264        ++it;
 265        if (request->m_requestState >= ClientRequest::COMPLETE) {
 266            m_pendingRequests.pop_front();
 267            if (m_priorResponseClosed <= request->m_requestNumber ||
 268                m_priorResponseFailed <= request->m_requestNumber)
 269                close = true;
 270        }
 271        if (it != m_pendingRequests.end()) {
 272            request = *it;
 273            MORDOR_ASSERT(request);
 274            MORDOR_ASSERT(request->m_responseState <= ClientRequest::WAITING ||
 275                request->m_responseState > ClientRequest::COMPLETE);
 276            if (request->m_responseState == ClientRequest::WAITING) {
 277                std::set<ClientRequest *>::iterator it2 = m_waitingResponses.find(request);
 278                MORDOR_ASSERT(it2 != m_waitingResponses.end());
 279                m_waitingResponses.erase(it2);
 280                request->m_responseState = ClientRequest::HEADERS;
 281                MORDOR_ASSERT(request->m_scheduler);
 282                MORDOR_ASSERT(request->m_fiber);
 283                MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-" << request->m_requestNumber << " scheduling response";
 284                request->m_scheduler->schedule(request->m_fiber);
 285                request->m_scheduler = NULL;
 286                request->m_fiber.reset();
 287                request = NULL;
 288            } else if (request->m_responseState == ClientRequest::PENDING ||
 289                request->m_responseState == ClientRequest::ERROR) {
 290                request = NULL;
 291            }
 292        } else {
 293            if (m_idleTimeout != ~0ull) {
 294                MORDOR_ASSERT(!m_idleTimer);
 295                MORDOR_ASSERT(m_timerManager);
 296                MORDOR_ASSERT(m_idleDg);
 297                m_idleTimer = m_timerManager->registerTimer(m_idleTimeout, m_idleDg);
 298            }
 299            request = NULL;
 300        }
 301    }
 302    if (request) {
 303        MORDOR_ASSERT(request->m_responseState == ClientRequest::CANCELED);
 304        MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-" << request->m_requestNumber << " skipping response";
 305        request->finish();
 306        request = NULL;
 307    }
 308    if (close && m_stream->supportsHalfClose()) {
 309        MORDOR_ASSERT(!request);
 310        MORDOR_LOG_TRACE(g_log) << m_connectionNumber << " closing";
 311        try {
 312            m_stream->close(Stream::READ);
 313        } catch (...) {
 314        }
 315    }
 316}
 317
 318void
 319ClientConnection::scheduleAllWaitingRequests()
 320{
 321    MORDOR_ASSERT(m_priorRequestFailed || m_priorResponseFailed != ~0ull ||
 322        m_priorResponseClosed != ~0ull);
 323    // MORDOR_ASSERT(m_mutex.locked());
 324    MORDOR_LOG_TRACE(g_log) << m_connectionNumber << " scheduling all requests";
 325
 326    for (std::list<ClientRequest *>::iterator it(m_currentRequest);
 327        it != m_pendingRequests.end();
 328        ) {
 329        ClientRequest *request = *it;
 330        MORDOR_ASSERT(request->m_requestState != ClientRequest::COMPLETE);
 331        if (request->m_requestState == ClientRequest::WAITING) {
 332            MORDOR_ASSERT(request->m_scheduler);
 333            MORDOR_ASSERT(request->m_fiber);
 334            MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-" << request->m_requestNumber << " scheduling request";
 335            request->m_scheduler->schedule(request->m_fiber);
 336            request->m_scheduler = NULL;
 337            request->m_fiber.reset();
 338            if (m_currentRequest == it) {
 339                m_currentRequest = it = m_pendingRequests.erase(it);
 340            } else {
 341                it = m_pendingRequests.erase(it);
 342            }
 343        } else {
 344            ++it;
 345        }
 346    }
 347}
 348
 349void
 350ClientConnection::scheduleAllWaitingResponses()
 351{
 352    MORDOR_ASSERT(m_priorResponseFailed != ~0ull || m_priorResponseClosed != ~0ull);
 353    // MORDOR_ASSERT(m_mutex.locked());
 354    MORDOR_LOG_TRACE(g_log) << m_connectionNumber << " scheduling all responses";
 355    unsigned long long firstResponseToSchedule =
 356        (std::min)(m_priorResponseFailed, m_priorResponseClosed);
 357
 358    std::list<ClientRequest *>::iterator end = m_currentRequest;
 359    if (end != m_pendingRequests.end())
 360        ++end;
 361    for (std::list<ClientRequest *>::iterator it(m_pendingRequests.begin());
 362        it != end;) {
 363        ClientRequest *request = *it;
 364        if (request->m_requestNumber > firstResponseToSchedule) {
 365            switch (request->m_responseState) {
 366                case ClientRequest::PENDING:
 367                case ClientRequest::ERROR:
 368                    ++it;
 369                    continue;
 370                case ClientRequest::WAITING:
 371                {
 372                    std::set<ClientRequest *>::iterator waiting =
 373                        m_waitingResponses.find(request);
 374                    MORDOR_ASSERT(waiting != m_waitingResponses.end());
 375                    MORDOR_ASSERT(request->m_scheduler);
 376                    MORDOR_ASSERT(request->m_fiber);
 377                    MORDOR_ASSERT(request->m_responseState ==
 378                        ClientRequest::WAITING);
 379                    MORDOR_LOG_TRACE(g_log) << m_connectionNumber << "-"
 380                        << request->m_requestNumber << " scheduling response";
 381                    request->m_responseState = ClientRequest::ERROR;
 382                    request->m_scheduler->schedule(request->m_fiber);
 383                    request->m_scheduler = NULL;
 384                    request->m_fiber.reset();
 385                    if (request->m_requestState >= ClientRequest::COMPLETE)
 386                        it = m_pendingRequests.erase(it);
 387                    m_waitingResponses.erase(waiting);
 388                    continue;
 389                }
 390                default:
 391                    MORDOR_NOTREACHED();
 392            }
 393        }
 394        ++it;
 395    }
 396}
 397
 398void
 399ClientConnection::invariant() const
 400{
 401#ifndef NDEBUG
 402    // MORDOR_ASSERT(m_mutex.locked());
 403    bool seenFirstUnrequested = false;
 404    unsigned long long lastRequestNumber = 0;
 405    for (std::list<ClientRequest *>::const_iterator it(m_pendingRequests.begin());
 406        it != m_pendingRequests.end();
 407        ++it) {
 408        ClientRequest *request = *it;
 409        MORDOR_ASSERT(request->m_requestNumber != 0);
 410        if (lastRequestNumber == 0) {
 411            lastRequestNumber = request->m_requestNumber;
 412        } else {
 413            MORDOR_ASSERT(lastRequestNumber + 1 == request->m_requestNumber ||
 414                request->m_requestNumber >= m_priorResponseFailed ||
 415                request->m_requestNumber >= m_priorResponseClosed);
 416            lastRequestNumber = request->m_requestNumber;
 417        }
 418        MORDOR_ASSERT(request->m_requestState < ClientRequest::COMPLETE ||
 419            request->m_responseState < ClientRequest::COMPLETE ||
 420            request->m_responseState == ClientRequest::CANCELED);
 421        // NOTE: it is allowed to have a response complete before the request
 422        // completes, BUT you can't have a response start before the request
 423        // starts
 424        if (request->m_responseState > ClientRequest::WAITING)
 425            MORDOR_ASSERT(request->m_requestState > ClientRequest::WAITING);
 426        if (!seenFirstUnrequested) {
 427            if (request->m_requestState < ClientRequest::COMPLETE) {
 428                seenFirstUnrequested = true;
 429                MORDOR_ASSERT(request->m_requestState >
 430                    ClientRequest::WAITING);
 431                MORDOR_ASSERT(m_currentRequest == it);
 432            }
 433        } else {
 434            MORDOR_ASSERT(request->m_requestState == ClientRequest::WAITING);
 435        }
 436        if (it != m_pendingRequests.begin())
 437            MORDOR_ASSERT(request->m_responseState <= ClientRequest::WAITING ||
 438                request->m_responseState > ClientRequest::COMPLETE);
 439    }
 440    if (!seenFirstUnrequested)
 441        MORDOR_ASSERT(m_currentRequest == m_pendingRequests.end());
 442    std::list<ClientRequest *>::const_iterator end = m_currentRequest;
 443    if (end != m_pendingRequests.end())
 444        ++end;
 445    for (std::set<ClientRequest *>::const_iterator it(m_waitingResponses.begin());
 446        it != m_waitingResponses.end();
 447        ++it) {
 448        ClientRequest *request = *it;
 449        MORDOR_ASSERT(request);
 450        MORDOR_ASSERT(request->m_responseState == ClientRequest::WAITING);
 451
 452        MORDOR_ASSERT(std::find<std::list<ClientRequest *>::const_iterator>
 453            (m_pendingRequests.begin(), end, request) != end);
 454    }
 455#endif
 456}
 457
 458void ClientRequest::RequestLogger::logRequest(size_t connNum, long long requestNum, const Request &request, bool censorAuth)
 459{
 460    if (g_log->enabled(Log::DEBUG)) {
 461        std::ostringstream os;
 462        bool basicAuth = censorAuth ? (stricmp(request.request.authorization.scheme.c_str(), "Basic") == 0) : false;
 463        bool basicProxyAuth = censorAuth ? (stricmp(request.request.proxyAuthorization.scheme.c_str(), "Basic") == 0) : false;
 464        bool oauth = censorAuth ? (stricmp(request.request.authorization.scheme.c_str(), "Bearer") == 0) : false;
 465        bool oauthProxy = censorAuth ? (stricmp(request.request.proxyAuthorization.scheme.c_str(), "Bearer") == 0) : false;
 466        if (basicAuth || basicProxyAuth || oauth || oauthProxy) {
 467            Request censoredRequest(request);
 468            if (basicAuth || oauth)
 469                censoredRequest.request.authorization.param = "<hidden>";
 470            if (basicProxyAuth || oauthProxy)
 471                censoredRequest.request.proxyAuthorization.param = "<hidden>";
 472            os << censoredRequest;
 473        } else {
 474            os << request;
 475        }
 476        MORDOR_LOG_DEBUG(g_log) << connNum << "-" << requestNum << " " << os.str();
 477    } else {
 478        MORDOR_LOG_VERBOSE(g_log) << connNum << "-" << requestNum << " " << request.requestLine;
 479    }
 480}
 481
 482void ClientRequest::RequestLogger::logResponse(size_t connNum, long long requestNum, const Request &request, const Response &response)
 483{
 484    if (g_log->enabled(Log::DEBUG)) {
 485        MORDOR_LOG_DEBUG(g_log) << connNum << "-" << requestNum << " " << response;
 486    } else {
 487        MORDOR_LOG_VERBOSE(g_log) << connNum << "-" << requestNum << " " << response.status;
 488    }
 489}
 490
 491/* static */ boost::shared_ptr<ClientRequest::RequestLogger> ClientRequest::msp_requestLogger( new ClientRequest::RequestLogger );
 492
 493void ClientRequest::setRequestLogger(boost::shared_ptr<ClientRequest::RequestLogger> newRequestLogger)
 494{
 495    if (newRequestLogger)
 496        msp_requestLogger = newRequestLogger;
 497    else
 498        msp_requestLogger.reset( new ClientRequest::RequestLogger );
 499}
 500
 501ClientRequest::ClientRequest(ClientConnection::ptr conn, const Request &request)
 502: m_conn(conn),
 503  m_requestNumber(0),
 504  m_scheduler(NULL),
 505  m_request(request),
 506  m_requestState(WAITING),
 507  m_responseState(PENDING),
 508  m_badTrailer(false),
 509  m_incompleteTrailer(false),
 510  m_hasResponseBody(false)
 511{
 512    MORDOR_ASSERT(m_conn);
 513}
 514
 515ClientRequest::~ClientRequest()
 516{
 517    cancel(true);
 518#ifndef NDEBUG
 519    MORDOR_NOTHROW_ASSERT(m_conn);
 520    boost::mutex::scoped_lock lock(m_conn->m_mutex);
 521    MORDOR_NOTHROW_ASSERT(std::find(m_conn->m_pendingRequests.begin(),
 522        m_conn->m_pendingRequests.end(),
 523        this) == m_conn->m_pendingRequests.end());
 524    MORDOR_NOTHROW_ASSERT(m_conn->m_waitingResponses.find(this) ==
 525        m_conn->m_waitingResponses.end());
 526#endif
 527}
 528
 529Request &
 530ClientRequest::request()
 531{
 532    return m_request;
 533}
 534
 535bool
 536ClientRequest::hasRequestBody() const
 537{
 538    return Connection::hasMessageBody(m_request.general,
 539        m_request.entity, m_request.requestLine.method, INVALID, false);
 540}
 541
 542Stream::ptr
 543ClientRequest::requestStream()
 544{
 545    if (m_requestState == ERROR) {
 546        if (m_conn->m_priorResponseClosed <= m_requestNumber)
 547            MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
 548        else
 549            MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
 550    }
 551
 552    if (m_requestStream) {
 553        MORDOR_ASSERT(m_request.entity.contentType.type != "multipart");
 554        return m_requestStream;
 555    }
 556    doRequest();
 557    MORDOR_ASSERT(!m_requestMultipart);
 558    MORDOR_ASSERT(m_request.entity.contentType.type != "multipart");
 559    if (!hasRequestBody()) {
 560        m_requestStream = Stream::ptr(&NullStream::get(), &nop<Stream *>);
 561        m_requestStream.reset(new LimitedStream(m_requestStream, 0));
 562        return m_requestStream;
 563    }
 564    MORDOR_ASSERT(m_requestState == BODY);
 565    return m_requestStream = m_conn->getStream(m_request.general, m_request.entity,
 566        m_request.requestLine.method, INVALID,
 567        boost::bind(&ClientRequest::requestDone, this),
 568        boost::bind(&ClientRequest::requestFailed, this), false);
 569}
 570
 571Multipart::ptr
 572ClientRequest::requestMultipart()
 573{
 574    if (m_requestMultipart)
 575        return m_requestMultipart;
 576    doRequest();
 577    MORDOR_ASSERT(m_request.entity.contentType.type == "multipart");
 578    MORDOR_ASSERT(!m_requestStream);
 579    MORDOR_ASSERT(m_requestState == BODY);
 580    StringMap::const_iterator it = m_request.entity.contentType.parameters.find("boundary");
 581    if (it == m_request.entity.contentType.parameters.end()) {
 582        MORDOR_THROW_EXCEPTION(MissingMultipartBoundaryException());
 583    }
 584    m_requestStream = m_conn->getStream(m_request.general, m_request.entity,
 585        m_request.requestLine.method, INVALID,
 586        boost::bind(&ClientRequest::requestDone, this),
 587        boost::bind(&ClientRequest::requestFailed, this), false);
 588    m_requestMultipart.reset(new Multipart(m_requestStream, it->second));
 589    m_requestMultipart->multipartFinished = boost::bind(&ClientRequest::requestMultipartDone, shared_from_this());
 590    return m_requestMultipart;
 591}
 592
 593EntityHeaders &
 594ClientRequest::requestTrailer()
 595{
 596    // If transferEncoding is not empty, it must include chunked,
 597    // and it must include chunked in order to have a trailer
 598    MORDOR_ASSERT(!m_request.general.transferEncoding.empty());
 599    return m_requestTrailer;
 600}
 601
 602const Response &
 603ClientRequest::response()
 604{
 605    ensureResponse();
 606    return m_response;
 607}
 608
 609bool
 610ClientRequest::hasResponseBody()
 611{
 612    ensureResponse();
 613    if (m_hasResponseBody)
 614        return true;
 615    return Connection::hasMessageBody(m_response.general,
 616        m_response.entity,
 617        m_request.requestLine.method,
 618        m_response.status.status);
 619}
 620
 621Stream::ptr
 622ClientRequest::responseStream()
 623{
 624    Stream::ptr result = m_responseStream.lock();
 625    if (result || m_hasResponseBody) {
 626        MORDOR_ASSERT(result &&
 627            "responseStream() can only be accessed once without caching it");
 628        MORDOR_ASSERT(m_response.entity.contentType.type != "multipart");
 629        return result;
 630    }
 631    ensureResponse();
 632    if (m_responseState >= COMPLETE) {
 633        m_hasResponseBody = true;
 634        result.reset(&NullStream::get(), &nop<Stream *>);
 635        m_responseStream = result;
 636        return result;
 637    }
 638    MORDOR_ASSERT(m_responseState == BODY);
 639    MORDOR_ASSERT(m_response.entity.contentType.type != "multipart");
 640    result = m_conn->getStream(m_response.general, m_response.entity,
 641        m_request.requestLine.method, m_response.status.status,
 642        boost::bind(&ClientRequest::responseDone, shared_from_this()),
 643        boost::bind(&ClientRequest::cancel, shared_from_this(), true, true), true);
 644    m_hasResponseBody = true;
 645    m_responseStream = result;
 646    return result;
 647}
 648
 649const EntityHeaders &
 650ClientRequest::responseTrailer() const
 651{
 652    if (m_badTrailer)
 653        MORDOR_THROW_EXCEPTION(BadMessageHeaderException());
 654    if (m_incompleteTrailer)
 655        MORDOR_THROW_EXCEPTION(IncompleteMessageHeaderException());
 656    MORDOR_ASSERT(m_responseState >= COMPLETE);
 657    MORDOR_ASSERT(!m_response.general.transferEncoding.empty());
 658    return m_responseTrailer;
 659}
 660
 661Stream::ptr
 662ClientRequest::stream()
 663{
 664    MORDOR_ASSERT(m_request.requestLine.method == CONNECT);
 665    ensureResponse();
 666    MORDOR_ASSERT(m_response.status.status == OK);
 667    return m_conn->m_stream;
 668}
 669
 670Multipart::ptr
 671ClientRequest::responseMultipart()
 672{
 673    MORDOR_ASSERT(m_response.entity.contentType.type == "multipart");
 674    Multipart::ptr result = m_responseMultipart.lock();
 675    if (result) {
 676        MORDOR_ASSERT(m_hasResponseBody);
 677        return result;
 678    }
 679    // You can only ask for the response multipart once
 680    // (to avoid circular references)
 681    MORDOR_ASSERT(!m_hasResponseBody);
 682    ensureResponse();
 683    MORDOR_ASSERT(m_responseState == BODY);
 684    StringMap::const_iterator it = m_response.entity.contentType.parameters.find("boundary");
 685    if (it == m_response.entity.contentType.parameters.end()) {
 686        MORDOR_THROW_EXCEPTION(MissingMultipartBoundaryException());
 687    }
 688    Stream::ptr stream = m_conn->getStream(m_response.general, m_response.entity,
 689        m_request.requestLine.method, m_response.status.status,
 690        NULL,
 691        boost::bind(&ClientRequest::cancel, shared_from_this(), true, true), true);
 692    m_responseStream = stream;
 693    result.reset(new Multipart(stream, it->second));
 694    result->multipartFinished = boost::bind(&ClientRequest::responseDone, shared_from_this());
 695    m_responseMultipart = result;
 696    m_hasResponseBody = true;
 697    return result;
 698}
 699
 700void
 701ClientRequest::cancel(bool abort, bool error)
 702{
 703    if (m_requestState >= COMPLETE && m_responseState >= COMPLETE)
 704        return;
 705    MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber
 706        << (abort ? " aborting" : " cancelling");
 707    if (!abort && m_requestState == WAITING && m_responseState <= WAITING) {
 708        // Just abandon it
 709        m_requestState = CANCELED;
 710        m_responseState = CANCELED;
 711        boost::mutex::scoped_lock lock(m_conn->m_mutex);
 712        m_conn->invariant();
 713        std::list<ClientRequest *>::iterator it =
 714            std::find(m_conn->m_pendingRequests.begin(),
 715            m_conn->m_pendingRequests.end(), this);
 716        MORDOR_ASSERT(it != m_conn->m_pendingRequests.end());
 717        m_conn->m_pendingRequests.erase(it);
 718        if (m_responseState == WAITING) {
 719            std::set<ClientRequest *>::iterator waitIt =
 720                m_conn->m_waitingResponses.find(this);
 721            MORDOR_ASSERT(waitIt != m_conn->m_waitingResponses.end());
 722            m_conn->m_waitingResponses.erase(waitIt);
 723            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber
 724                << " scheduling response";
 725            m_scheduler->schedule(m_fiber);
 726            m_scheduler = NULL;
 727            m_fiber.reset();
 728        }
 729        return;
 730    }
 731    if (m_requestStream) {
 732        FilterStream *filter = static_cast<FilterStream *>(m_requestStream.get());
 733        if (filter->parent().get() != &NullStream::get()) {
 734            // Break the circular reference
 735            NotifyStream::ptr notify =
 736                boost::dynamic_pointer_cast<NotifyStream>(m_requestStream);
 737            MORDOR_ASSERT(notify);
 738            notify->notifyOnClose(NULL);
 739            notify->notifyOnEof = NULL;
 740            notify->notifyOnException = NULL;
 741            notify->parent(Stream::ptr(new Stream()));
 742        }
 743    }
 744    Stream::ptr responseStream = m_responseStream.lock();
 745    ClientRequest::ptr self;
 746    if (responseStream) {
 747        // notify may be holding the last reference to this, so keep ourself in scope
 748        self = shared_from_this();
 749        NotifyStream::ptr notify =
 750            boost::dynamic_pointer_cast<NotifyStream>(responseStream);
 751        MORDOR_ASSERT(notify);
 752        notify->notifyOnClose(NULL);
 753        notify->notifyOnEof = NULL;
 754        notify->notifyOnException = NULL;
 755    }
 756    bool close = false, waiting = m_responseState == WAITING;
 757    if (m_responseState != HEADERS)
 758        abort = true;
 759    {
 760        boost::mutex::scoped_lock lock(m_conn->m_mutex);
 761        m_conn->invariant();
 762        m_conn->m_priorResponseFailed = m_requestNumber;
 763        if (m_requestState < COMPLETE)
 764            m_requestState = error ? ERROR : CANCELED;
 765        if (m_responseState < COMPLETE && abort)
 766            m_responseState = error ? ERROR : CANCELED;
 767
 768        std::list<ClientRequest *>::iterator it =
 769            std::find(m_conn->m_pendingRequests.begin(),
 770            m_conn->m_pendingRequests.end(), this);
 771        MORDOR_ASSERT(it != m_conn->m_pendingRequests.end());
 772        close = it == m_conn->m_pendingRequests.begin();
 773        if (abort) {
 774            if (it == m_conn->m_currentRequest)
 775                m_conn->m_currentRequest = m_conn->m_pendingRequests.erase(it);
 776            else
 777                m_conn->m_pendingRequests.erase(it);
 778        } else if (it == m_conn->m_currentRequest) {
 779            ++m_conn->m_currentRequest;
 780        }
 781        if (waiting) {
 782            std::set<ClientRequest *>::iterator waitIt =
 783                m_conn->m_waitingResponses.find(this);
 784            MORDOR_ASSERT(waitIt != m_conn->m_waitingResponses.end());
 785            m_conn->m_waitingResponses.erase(waitIt);
 786            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber
 787                << " scheduling response";
 788            m_scheduler->schedule(m_fiber);
 789            m_scheduler = NULL;
 790            m_fiber.reset();
 791        }
 792        m_conn->scheduleAllWaitingRequests();
 793        m_conn->scheduleAllWaitingResponses();
 794    }
 795    if (close)
 796        m_conn->m_stream->cancelRead();
 797    m_conn->m_stream->cancelWrite();
 798}
 799
 800void
 801ClientRequest::finish()
 802{
 803    if (m_requestState != COMPLETE) {
 804        cancel(true);
 805        return;
 806    }
 807    if (hasResponseBody()) {
 808        if (m_response.entity.contentType.type == "multipart") {
 809            Multipart::ptr multipart;
 810            if (m_hasResponseBody)
 811                multipart = m_responseMultipart.lock();
 812            else
 813                multipart = responseMultipart();
 814            if (!multipart)
 815                cancel(true);
 816            else
 817                while(multipart->nextPart());
 818        } else {
 819            Stream::ptr stream;
 820            if (m_hasResponseBody)
 821                stream = m_responseStream.lock();
 822            else
 823                stream = responseStream();
 824            if (!stream)
 825                cancel(true);
 826            else
 827                transferStream(stream, NullStream::get());
 828        }
 829    }
 830}
 831
 832void
 833ClientRequest::waitForRequest()
 834{
 835    bool firstRequest;
 836    // Put the request in the queue
 837    {
 838        boost::mutex::scoped_lock lock(m_conn->m_mutex);
 839        m_conn->invariant();
 840        if (!m_conn->m_allowNewRequests || m_conn->m_priorResponseClosed != ~0ull) {
 841            m_requestState = m_responseState = ERROR;
 842            MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
 843        }
 844        if (m_conn->m_priorRequestFailed || m_conn->m_priorResponseFailed != ~0ull) {
 845            m_requestState = m_responseState = ERROR;
 846            MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
 847        }
 848        if (m_conn->m_idleTimer) {
 849            m_conn->m_idleTimer->cancel();
 850            m_conn->m_idleTimer.reset();
 851        }
 852        firstRequest = m_conn->m_currentRequest == m_conn->m_pendingRequests.end();
 853        m_requestNumber = ++m_conn->m_requestCount;
 854        m_conn->m_pendingRequests.push_back(this);
 855        if (firstRequest) {
 856            m_conn->m_currentRequest = m_conn->m_pendingRequests.end();
 857            --m_conn->m_currentRequest;
 858            m_requestState = HEADERS;
 859            // Disable read timeouts while a request is in progress
 860            if (m_conn->m_timeoutStream) {
 861                lock.unlock();
 862                m_conn->m_timeoutStream->readTimeout(~0ull);
 863                lock.lock();
 864            }
 865            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " requesting";
 866        } else {
 867            m_scheduler = Scheduler::getThis();
 868            m_fiber = Fiber::getThis();
 869            MORDOR_ASSERT(m_scheduler);
 870            MORDOR_ASSERT(m_fiber);
 871            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " waiting to request";
 872        }
 873    }
 874    // If we weren't the first request in the queue, we have to wait for
 875    // another request to schedule us
 876    if (!firstRequest) {
 877        Scheduler::yieldTo();
 878        MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " requesting";
 879        // Check for problems that occurred while we were waiting
 880        boost::mutex::scoped_lock lock(m_conn->m_mutex);
 881        m_conn->invariant();
 882        if (m_conn->m_priorResponseClosed != ~0ull ||
 883            m_conn->m_priorRequestFailed ||
 884            m_conn->m_priorResponseFailed != ~0ull) {
 885            if (m_requestState == HEADERS) {
 886                MORDOR_ASSERT(m_conn->m_currentRequest !=
 887                    m_conn->m_pendingRequests.end());
 888                MORDOR_ASSERT(*m_conn->m_currentRequest == this);
 889                m_conn->m_currentRequest =
 890                    m_conn->m_pendingRequests.erase(m_conn->m_currentRequest);
 891                MORDOR_ASSERT(m_conn->m_currentRequest ==
 892                    m_conn->m_pendingRequests.end());
 893            }
 894            m_requestState = m_responseState = ERROR;
 895            if (m_conn->m_priorResponseClosed != ~0ull)
 896                MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
 897            else
 898                MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
 899        }
 900    }
 901    MORDOR_ASSERT(m_requestState == HEADERS);
 902}
 903
 904void
 905ClientRequest::doRequest()
 906{
 907    if (m_requestState > HEADERS)
 908        return;
 909
 910    RequestLine &requestLine = m_request.requestLine;
 911    // 1.0, 1.1, or defaulted
 912    MORDOR_ASSERT(requestLine.ver == Version() ||
 913           requestLine.ver == Version(1, 0) ||
 914           requestLine.ver == Version(1, 1));
 915    // Have to request *something*
 916    MORDOR_ASSERT(requestLine.uri.isDefined());
 917    // Host header required with HTTP/1.1
 918    MORDOR_ASSERT(!m_request.request.host.empty() || requestLine.ver != Version(1, 1));
 919    // If any transfer encodings, must include chunked, must have chunked only once, and must be the last one
 920    const ParameterizedList &transferEncoding = m_request.general.transferEncoding;
 921    if (!transferEncoding.empty()) {
 922        MORDOR_ASSERT(stricmp(transferEncoding.back().value.c_str(), "chunked") == 0);
 923        for (ParameterizedList::const_iterator it(transferEncoding.begin());
 924            it + 1 != transferEncoding.end();
 925            ++it) {
 926            // Only the last one can be chunked
 927            MORDOR_ASSERT(stricmp(it->value.c_str(), "chunked") != 0);
 928            // identity is only acceptable in the TE header field
 929            MORDOR_ASSERT(it->value != "identity");
 930            if (it->value == "gzip" ||
 931                it->value == "x-gzip" ||
 932                it->value == "deflate") {
 933                // Known Transfer-Codings
 934                continue;
 935            } else if (it->value == "compress" ||
 936                it->value == "x-compress") {
 937                // Unsupported Transfer-Codings
 938                MORDOR_NOTREACHED();
 939            } else {
 940                // Unknown Transfer-Coding
 941                MORDOR_NOTREACHED();
 942            }
 943        }
 944    }
 945
 946    bool close;
 947    // Default HTTP version... 1.1 if possible
 948    if (requestLine.ver == Version()) {
 949        if (m_request.request.host.empty())
 950            requestLine.ver = Version(1, 0);
 951        else
 952            requestLine.ver = Version(1, 1);
 953    }
 954    // If not specified, try to keep the connection open
 955    StringSet &connection = m_request.general.connection;
 956    if (connection.find("close") == connection.end() && requestLine.ver == Version(1, 0)) {
 957        connection.insert("Keep-Alive");
 958    }
 959    // Determine if we're closing the connection after this request
 960    if (requestLine.ver == Version(1, 0)) {
 961        if (connection.find("Keep-Alive") != connection.end()) {
 962            close = false;
 963        } else {
 964            close = true;
 965            connection.insert("close");
 966        }
 967    } else {
 968        if (connection.find("close") != connection.end()) {
 969            close = true;
 970        } else {
 971            close = false;
 972        }
 973    }
 974    if (close) {
 975        boost::mutex::scoped_lock lock(m_conn->m_mutex);
 976        m_conn->invariant();
 977        m_conn->m_allowNewRequests = false;
 978    }
 979    // TE is a connection-specific header
 980    if (!m_request.request.te.empty())
 981        m_request.general.connection.insert("TE");
 982
 983    try {
 984        // Do the request
 985        std::ostringstream os;
 986        os << m_request;
 987        std::string str = os.str();
 988        msp_requestLogger->logRequest(m_conn->m_connectionNumber, m_requestNumber, m_request);
 989        m_conn->m_stream->write(str.c_str(), str.size());
 990
 991        if (!Connection::hasMessageBody(m_request.general, m_request.entity, requestLine.method, INVALID, false)) {
 992            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " no request body";
 993            m_conn->scheduleNextRequest(this);
 994        } else {
 995            m_requestState = BODY;
 996        }
 997    } catch(...) {
 998        requestFailed();
 999        throw;
1000    }
1001}
1002
1003void
1004ClientRequest::ensureResponse()
1005{
1006    if (m_priorResponseException)
1007        ::Mordor::rethrow_exception(m_priorResponseException);
1008    if (m_responseState == BODY || m_responseState >= COMPLETE)
1009        return;
1010    try {
1011        bool wait = false;
1012        MORDOR_ASSERT(m_responseState == PENDING);
1013        {
1014            boost::mutex::scoped_lock lock(m_conn->m_mutex);
1015            m_conn->invariant();
1016            if (m_conn->m_priorResponseFailed <= m_requestNumber ||
1017                m_conn->m_priorResponseClosed <= m_requestNumber) {
1018                if (m_requestState >= COMPLETE) {
1019                    std::list<ClientRequest *>::iterator it;
1020                    it = std::find(m_conn->m_pendingRequests.begin(),
1021                        m_conn->m_pendingRequests.end(), this);
1022                    MORDOR_ASSERT(it != m_conn->m_pendingRequests.end());
1023                    m_conn->m_pendingRequests.erase(it);
1024                }
1025                m_responseState = ERROR;
1026                if (m_conn->m_priorResponseClosed <= m_requestNumber)
1027                    MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
1028                else
1029                    MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
1030            }
1031            MORDOR_ASSERT(!m_conn->m_pendingRequests.empty());
1032            ClientRequest *request = m_conn->m_pendingRequests.front();
1033            if (request != this) {
1034                m_scheduler = Scheduler::getThis();
1035                m_fiber = Fiber::getThis();
1036                MORDOR_ASSERT(m_scheduler);
1037                MORDOR_ASSERT(m_fiber);
1038                MORDOR_VERIFY(m_conn->m_waitingResponses.insert(this).second);
1039                wait = true;
1040                m_responseState = WAITING;
1041                MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber<< " waiting for response";
1042            } else {
1043                m_responseState = HEADERS;
1044                MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " reading response";
1045            }
1046        }
1047
1048        // If we weren't the first response in the queue, wait for someone
1049        // else to schedule us
1050        if (wait) {
1051            Scheduler::yieldTo();
1052            MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " reading response";
1053            // Check for problems that occurred while we were waiting
1054            boost::mutex::scoped_lock lock(m_conn->m_mutex);
1055            m_conn->invariant();
1056            if (m_responseState == CANCELED)
1057                MORDOR_THROW_EXCEPTION(OperationAbortedException());
1058            if (m_responseState == ERROR) {
1059                if (m_conn->m_priorResponseClosed <= m_requestNumber)
1060                    MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
1061                else
1062                    MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
1063            }
1064            // Probably means that the Scheduler exited in the above yieldTo,
1065            // and returned to us, because there is no other work to be done
1066            try {
1067                MORDOR_ASSERT(!m_conn->m_pendingRequests.empty());
1068                MORDOR_ASSERT(m_conn->m_pendingRequests.front() == this);
1069            } catch(...) {
1070                m_responseState = PENDING;
1071                std::set<ClientRequest *>::iterator it = m_conn->m_waitingResponses.find(this);
1072                if (it != m_conn->m_waitingResponses.end())
1073                    m_conn->m_waitingResponses.erase(it);
1074                throw;
1075            }
1076        }
1077
1078        try {
1079            MORDOR_ASSERT(m_responseState == HEADERS);
1080            // Read and parse headers
1081            ResponseParser parser(m_response);
1082            unsigned long long read = parser.run(m_conn->m_stream);
1083            MORDOR_ASSERT(m_responseState == HEADERS || m_responseState > COMPLETE);
1084            if (m_responseState > COMPLETE)
1085                MORDOR_THROW_EXCEPTION(OperationAbortedException());
1086            if (read == 0ull)
1087                MORDOR_THROW_EXCEPTION(UnexpectedEofException());
1088            if (parser.error())
1089                MORDOR_THROW_EXCEPTION(BadMessageHeaderException());
1090            if (!parser.complete())
1091                MORDOR_THROW_EXCEPTION(IncompleteMessageHeaderException());
1092            msp_requestLogger->logResponse(m_conn->m_connectionNumber, m_requestNumber, m_request, m_response);
1093
1094            bool close = false;
1095            StringSet &connection = m_response.general.connection;
1096            StringSet &proxyConnection = m_response.general.proxyConnection; // NON-STANDARD!!!
1097
1098            if (m_response.status.ver == Version(1, 0)) {
1099                // When using a HTTP 1.0 proxy server then Keep-Alive may come via the Proxy-Connection header
1100                // instead of more standard "Connection"
1101                if (connection.find("Keep-Alive") == connection.end() && proxyConnection.find("Keep-Alive") == proxyConnection.end())
1102                    close = true;
1103            } else if (m_response.status.ver == Version(1, 1)) {
1104                if (connection.find("close") != connection.end())
1105                    close = true;
1106            } else {
1107                MORDOR_THROW_EXCEPTION(BadMessageHeaderException());
1108            }
1109
1110            if (proxyConnection.find("close") != proxyConnection.end())
1111                close = true;
1112
1113            ParameterizedList &transferEncoding = m_response.general.transferEncoding;
1114            // Remove identity from the Transfer-Encodings
1115            for (ParameterizedList::iterator it(transferEncoding.begin());
1116                it != transferEncoding.end();
1117                ++it) {
1118                if (stricmp(it->value.c_str(), "identity") == 0) {
1119                    it = transferEncoding.erase(it);
1120                    --it;
1121                }
1122            }
1123            if (!transferEncoding.empty()) {
1124                if (stricmp(transferEncoding.back().value.c_str(), "chunked") != 0) {
1125                    MORDOR_THROW_EXCEPTION(InvalidTransferEncodingException("The last transfer-coding is not chunked."));
1126                }
1127                for (ParameterizedList::const_iterator it(transferEncoding.begin());
1128                    it + 1 != transferEncoding.end();
1129                    ++it) {
1130                    if (stricmp(it->value.c_str(), "chunked") == 0) {
1131                        MORDOR_THROW_EXCEPTION(InvalidTransferEncodingException("chunked transfer-coding applied multiple times"));
1132                    } else if (stricmp(it->value.c_str(), "deflate") == 0 ||
1133                        stricmp(it->value.c_str(), "gzip") == 0 ||
1134                        stricmp(it->value.c_str(), "x-gzip") == 0) {
1135                        // Supported transfer-codings
1136                    } else if (stricmp(it->value.c_str(), "compress") == 0 ||
1137                        stricmp(it->value.c_str(), "x-compress") == 0) {
1138                        MORDOR_THROW_EXCEPTION(InvalidTransferEncodingException("compress transfer-coding is unsupported"));
1139                    } else {
1140                        MORDOR_THROW_EXCEPTION(InvalidTransferEncodingException("Unrecognized transfer-coding: " + it->value));
1141                    }
1142                }
1143            }
1144
1145            // If the there is a message body, but it's undelimited, make sure we're
1146            // closing the connection
1147            bool hasBody = Connection::hasMessageBody(m_response.general, m_response.entity,
1148                m_request.requestLine.method, m_response.status.status, false);
1149            if (hasBody &&
1150                transferEncoding.empty() && m_response.entity.contentLength == ~0ull &&
1151                m_response.entity.contentType.type != "multipart") {
1152                close = true;
1153            }
1154
1155            bool connect = m_request.requestLine.method == CONNECT &&
1156                m_response.status.status == OK;
1157            if (connect)
1158                close = true;
1159
1160            if (close) {
1161                boost::mutex::scoped_lock lock(m_conn->m_mutex);
1162                m_conn->invariant();
1163                m_conn->m_priorResponseClosed = m_requestNumber;
1164                MORDOR_ASSERT(!m_conn->m_pendingRequests.empty());
1165                MORDOR_ASSERT(m_conn->m_pendingRequests.front() == this);
1166                if (!hasBody && m_requestState >= COMPLETE)
1167                    m_conn->m_pendingRequests.pop_front();
1168                m_responseState = hasBody ? BODY : COMPLETE;
1169                m_conn->scheduleAllWaitingRequests();
1170                m_conn->scheduleAllWaitingResponses();
1171            } else {
1172                m_responseState = connect ? COMPLETE : BODY;
1173            }
1174
1175            if (!hasBody && !connect) {
1176                MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " no response body";
1177                if (close) {
1178                    if (m_conn->m_stream->supportsHalfClose()) {
1179                        try {
1180                            m_conn->m_stream->close(Stream::READ);
1181                        } catch (...) {
1182                        }
1183                    }
1184                } else {
1185                    m_conn->scheduleNextResponse(this);
1186                }
1187            }
1188        } catch (...) {
1189            boost::mutex::scoped_lock lock(m_conn->m_mutex);
1190            m_conn->invariant();
1191            m_conn->m_priorResponseFailed = (std::min)(m_requestNumber,
1192                m_conn->m_priorResponseFailed);
1193            m_responseState = ERROR;
1194            if (!m_conn->m_pendingRequests.empty() &&
1195                m_conn->m_pendingRequests.front() == this) {
1196                if (m_requestState >= COMPLETE)
1197                    m_conn->m_pendingRequests.pop_front();
1198                m_conn->scheduleAllWaitingRequests();
1199                m_conn->scheduleAllWaitingResponses();
1200            }
1201            if (m_conn->m_priorResponseClosed < m_requestNumber)
1202                MORDOR_THROW_EXCEPTION(ConnectionVoluntarilyClosedException());
1203            if (m_conn->m_priorResponseFailed < m_requestNumber)
1204                MORDOR_THROW_EXCEPTION(PriorRequestFailedException());
1205            throw;
1206        }
1207    } catch (...) {
1208        m_priorResponseException = boost::current_exception();
1209        throw;
1210    }
1211}
1212
1213void
1214ClientRequest::requestMultipartDone()
1215{
1216    MORDOR_ASSERT(m_requestStream);
1217    m_requestStream->close();
1218}
1219
1220void
1221ClientRequest::requestDone()
1222{
1223    MORDOR_ASSERT(m_requestState == BODY);
1224    MORDOR_ASSERT(m_requestStream);
1225    MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " request complete";
1226    // Break the circular reference
1227    NotifyStream::ptr notify =
1228        boost::dynamic_pointer_cast<NotifyStream>(m_requestStream);
1229    MORDOR_ASSERT(notify);
1230    notify->notifyOnClose(NULL);
1231    notify->notifyOnEof = NULL;
1232    notify->notifyOnException = NULL;
1233    if (m_requestStream->supportsSize() && m_requestStream->supportsTell())
1234        MORDOR_ASSERT(m_requestStream->size() == m_requestStream->tell());
1235    if (!m_request.general.transferEncoding.empty()) {
1236        std::ostringstream os;
1237        os << m_requestTrailer << "\r\n";
1238        std::string str = os.str();
1239        MORDOR_LOG_DEBUG(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " " << str;
1240        m_conn->m_stream->write(str.c_str(), str.size());
1241    }
1242    m_conn->scheduleNextRequest(this);
1243}
1244
1245void
1246ClientRequest::requestFailed()
1247{
1248    if (m_requestState == ERROR)
1249        return;
1250    MORDOR_ASSERT(m_requestState == BODY || m_requestState == HEADERS);
1251    MORDOR_LOG_TRACE(g_log) << m_conn->m_connectionNumber << "-" << m_requestNumber << " request failed";
1252    if (m_requestStream) {
1253        // Break the circular reference
1254        NotifyStream::ptr notify =
1255            boost::dynamic_pointer_cast<NotifyStream>(m_requestStream);
1256        MORDOR_ASSERT(notify);
1257        notify->notifyOnClose(NULL);
1258        notify->notifyOnEof = NULL;
1259        notify->notifyOnException = NULL;
1260    }
1261    boost::mutex::scoped_lock lock(m_conn->m_mutex);
1262    m_conn->invariant();
1263    MORDOR_ASSERT(!m_conn->m_pendingRequests.empty());
1264    MORDOR_ASSERT(this == *m_conn->m_currentRequest);
1265    m_conn->m_priorRequestFailed = true;
1266    if (m_requestState == HEADERS) {
1267        switch (m_responseState) {
1268            case PENDING:
1269           

Large files files are truncated, but you can click here to view the full file