root/net/http/http_stream_factory_impl_request.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. using_spdy_
  2. SetSpdySessionKey
  3. SetHttpPipeliningKey
  4. AttachJob
  5. Complete
  6. OnStreamReady
  7. OnWebSocketHandshakeStreamReady
  8. OnStreamFailed
  9. OnCertificateError
  10. OnNeedsProxyAuth
  11. OnNeedsClientAuth
  12. OnHttpsProxyTunnelResponse
  13. RestartTunnelWithProxyAuth
  14. SetPriority
  15. GetLoadState
  16. was_npn_negotiated
  17. protocol_negotiated
  18. using_spdy
  19. RemoveRequestFromSpdySessionRequestMap
  20. RemoveRequestFromHttpPipeliningRequestMap
  21. OnNewSpdySessionReady
  22. OrphanJobsExcept
  23. OrphanJobs
  24. OnJobSucceeded

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/http_stream_factory_impl_request.h"

#include "base/callback.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "net/http/http_stream_factory_impl_job.h"
#include "net/spdy/spdy_http_stream.h"
#include "net/spdy/spdy_session.h"

namespace net {

HttpStreamFactoryImpl::Request::Request(
    const GURL& url,
    HttpStreamFactoryImpl* factory,
    HttpStreamRequest::Delegate* delegate,
    WebSocketHandshakeStreamBase::CreateHelper*
        websocket_handshake_stream_create_helper,
    const BoundNetLog& net_log)
    : url_(url),
      factory_(factory),
      websocket_handshake_stream_create_helper_(
          websocket_handshake_stream_create_helper),
      delegate_(delegate),
      net_log_(net_log),
      completed_(false),
      was_npn_negotiated_(false),
      protocol_negotiated_(kProtoUnknown),
      using_spdy_(false) {
  DCHECK(factory_);
  DCHECK(delegate_);

  net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);
}

HttpStreamFactoryImpl::Request::~Request() {
  if (bound_job_.get())
    DCHECK(jobs_.empty());
  else
    DCHECK(!jobs_.empty());

  net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);

  for (std::set<Job*>::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
    factory_->request_map_.erase(*it);

  RemoveRequestFromSpdySessionRequestMap();
  RemoveRequestFromHttpPipeliningRequestMap();

  STLDeleteElements(&jobs_);
}

void HttpStreamFactoryImpl::Request::SetSpdySessionKey(
    const SpdySessionKey& spdy_session_key) {
  DCHECK(!spdy_session_key_.get());
  spdy_session_key_.reset(new SpdySessionKey(spdy_session_key));
  RequestSet& request_set =
      factory_->spdy_session_request_map_[spdy_session_key];
  DCHECK(!ContainsKey(request_set, this));
  request_set.insert(this);
}

bool HttpStreamFactoryImpl::Request::SetHttpPipeliningKey(
    const HttpPipelinedHost::Key& http_pipelining_key) {
  CHECK(!http_pipelining_key_.get());
  http_pipelining_key_.reset(new HttpPipelinedHost::Key(http_pipelining_key));
  bool was_new_key = !ContainsKey(factory_->http_pipelining_request_map_,
                                  http_pipelining_key);
  RequestVector& request_vector =
      factory_->http_pipelining_request_map_[http_pipelining_key];
  request_vector.push_back(this);
  return was_new_key;
}

void HttpStreamFactoryImpl::Request::AttachJob(Job* job) {
  DCHECK(job);
  jobs_.insert(job);
  factory_->request_map_[job] = this;
}

void HttpStreamFactoryImpl::Request::Complete(
    bool was_npn_negotiated,
    NextProto protocol_negotiated,
    bool using_spdy,
    const BoundNetLog& job_net_log) {
  DCHECK(!completed_);
  completed_ = true;
  was_npn_negotiated_ = was_npn_negotiated;
  protocol_negotiated_ = protocol_negotiated;
  using_spdy_ = using_spdy;
  net_log_.AddEvent(
      NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB,
      job_net_log.source().ToEventParametersCallback());
  job_net_log.AddEvent(
      NetLog::TYPE_HTTP_STREAM_JOB_BOUND_TO_REQUEST,
      net_log_.source().ToEventParametersCallback());
}

void HttpStreamFactoryImpl::Request::OnStreamReady(
    Job* job,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    HttpStreamBase* stream) {
  DCHECK(!factory_->for_websockets_);
  DCHECK(stream);
  DCHECK(completed_);

  OnJobSucceeded(job);
  delegate_->OnStreamReady(used_ssl_config, used_proxy_info, stream);
}

void HttpStreamFactoryImpl::Request::OnWebSocketHandshakeStreamReady(
    Job* job,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    WebSocketHandshakeStreamBase* stream) {
  DCHECK(factory_->for_websockets_);
  DCHECK(stream);
  DCHECK(completed_);

  OnJobSucceeded(job);
  delegate_->OnWebSocketHandshakeStreamReady(
      used_ssl_config, used_proxy_info, stream);
}

void HttpStreamFactoryImpl::Request::OnStreamFailed(
    Job* job,
    int status,
    const SSLConfig& used_ssl_config) {
  DCHECK_NE(OK, status);
  // |job| should only be NULL if we're being canceled by a late bound
  // HttpPipelinedConnection (one that was not created by a job in our |jobs_|
  // set).
  if (!job) {
    DCHECK(!bound_job_.get());
    DCHECK(!jobs_.empty());
    // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
    // we *WANT* to cancel the unnecessary Jobs from other requests if another
    // Job completes first.
  } else if (!bound_job_.get()) {
    // Hey, we've got other jobs! Maybe one of them will succeed, let's just
    // ignore this failure.
    if (jobs_.size() > 1) {
      jobs_.erase(job);
      factory_->request_map_.erase(job);
      delete job;
      return;
    } else {
      bound_job_.reset(job);
      jobs_.erase(job);
      DCHECK(jobs_.empty());
      factory_->request_map_.erase(job);
    }
  } else {
    DCHECK(jobs_.empty());
  }
  delegate_->OnStreamFailed(status, used_ssl_config);
}

void HttpStreamFactoryImpl::Request::OnCertificateError(
    Job* job,
    int status,
    const SSLConfig& used_ssl_config,
    const SSLInfo& ssl_info) {
  DCHECK_NE(OK, status);
  if (!bound_job_.get())
    OrphanJobsExcept(job);
  else
    DCHECK(jobs_.empty());
  delegate_->OnCertificateError(status, used_ssl_config, ssl_info);
}

void HttpStreamFactoryImpl::Request::OnNeedsProxyAuth(
    Job* job,
    const HttpResponseInfo& proxy_response,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    HttpAuthController* auth_controller) {
  if (!bound_job_.get())
    OrphanJobsExcept(job);
  else
    DCHECK(jobs_.empty());
  delegate_->OnNeedsProxyAuth(
      proxy_response, used_ssl_config, used_proxy_info, auth_controller);
}

void HttpStreamFactoryImpl::Request::OnNeedsClientAuth(
    Job* job,
    const SSLConfig& used_ssl_config,
    SSLCertRequestInfo* cert_info) {
  if (!bound_job_.get())
    OrphanJobsExcept(job);
  else
    DCHECK(jobs_.empty());
  delegate_->OnNeedsClientAuth(used_ssl_config, cert_info);
}

void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse(
    Job *job,
    const HttpResponseInfo& response_info,
    const SSLConfig& used_ssl_config,
    const ProxyInfo& used_proxy_info,
    HttpStreamBase* stream) {
  if (!bound_job_.get())
    OrphanJobsExcept(job);
  else
    DCHECK(jobs_.empty());
  delegate_->OnHttpsProxyTunnelResponse(
      response_info, used_ssl_config, used_proxy_info, stream);
}

int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth(
    const AuthCredentials& credentials) {
  DCHECK(bound_job_.get());
  return bound_job_->RestartTunnelWithProxyAuth(credentials);
}

void HttpStreamFactoryImpl::Request::SetPriority(RequestPriority priority) {
  for (std::set<HttpStreamFactoryImpl::Job*>::const_iterator it = jobs_.begin();
       it != jobs_.end(); ++it) {
    (*it)->SetPriority(priority);
  }
  if (bound_job_)
    bound_job_->SetPriority(priority);
}

LoadState HttpStreamFactoryImpl::Request::GetLoadState() const {
  if (bound_job_.get())
    return bound_job_->GetLoadState();
  DCHECK(!jobs_.empty());

  // Just pick the first one.
  return (*jobs_.begin())->GetLoadState();
}

bool HttpStreamFactoryImpl::Request::was_npn_negotiated() const {
  DCHECK(completed_);
  return was_npn_negotiated_;
}

NextProto HttpStreamFactoryImpl::Request::protocol_negotiated()
    const {
  DCHECK(completed_);
  return protocol_negotiated_;
}

bool HttpStreamFactoryImpl::Request::using_spdy() const {
  DCHECK(completed_);
  return using_spdy_;
}

void
HttpStreamFactoryImpl::Request::RemoveRequestFromSpdySessionRequestMap() {
  if (spdy_session_key_.get()) {
    SpdySessionRequestMap& spdy_session_request_map =
        factory_->spdy_session_request_map_;
    DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key_));
    RequestSet& request_set =
        spdy_session_request_map[*spdy_session_key_];
    DCHECK(ContainsKey(request_set, this));
    request_set.erase(this);
    if (request_set.empty())
      spdy_session_request_map.erase(*spdy_session_key_);
    spdy_session_key_.reset();
  }
}

void
HttpStreamFactoryImpl::Request::RemoveRequestFromHttpPipeliningRequestMap() {
  if (http_pipelining_key_.get()) {
    HttpPipeliningRequestMap& http_pipelining_request_map =
        factory_->http_pipelining_request_map_;
    DCHECK(ContainsKey(http_pipelining_request_map, *http_pipelining_key_));
    RequestVector& request_vector =
        http_pipelining_request_map[*http_pipelining_key_];
    for (RequestVector::iterator it = request_vector.begin();
         it != request_vector.end(); ++it) {
      if (*it == this) {
        request_vector.erase(it);
        break;
      }
    }
    if (request_vector.empty())
      http_pipelining_request_map.erase(*http_pipelining_key_);
    http_pipelining_key_.reset();
  }
}

void HttpStreamFactoryImpl::Request::OnNewSpdySessionReady(
    Job* job,
    scoped_ptr<HttpStream> stream,
    const base::WeakPtr<SpdySession>& spdy_session,
    bool direct) {
  DCHECK(job);
  DCHECK(job->using_spdy());

  // Note: |spdy_session| may be NULL. In that case, |delegate_| should still
  // receive |stream| so the error propogates up correctly, however there is no
  // point in broadcasting |spdy_session| to other requests.

  // The first case is the usual case.
  if (!bound_job_.get()) {
    OrphanJobsExcept(job);
  } else {  // This is the case for HTTPS proxy tunneling.
    DCHECK_EQ(bound_job_.get(), job);
    DCHECK(jobs_.empty());
  }

  // Cache these values in case the job gets deleted.
  const SSLConfig used_ssl_config = job->server_ssl_config();
  const ProxyInfo used_proxy_info = job->proxy_info();
  const bool was_npn_negotiated = job->was_npn_negotiated();
  const NextProto protocol_negotiated =
      job->protocol_negotiated();
  const bool using_spdy = job->using_spdy();
  const BoundNetLog net_log = job->net_log();

  Complete(was_npn_negotiated, protocol_negotiated, using_spdy, net_log);

  // Cache this so we can still use it if the request is deleted.
  HttpStreamFactoryImpl* factory = factory_;
  if (factory->for_websockets_) {
    // TODO(ricea): Re-instate this code when WebSockets over SPDY is
    // implemented.
    NOTREACHED();
  } else {
    delegate_->OnStreamReady(job->server_ssl_config(), job->proxy_info(),
                             stream.release());
  }
  // |this| may be deleted after this point.
  if (spdy_session) {
    factory->OnNewSpdySessionReady(spdy_session,
                                   direct,
                                   used_ssl_config,
                                   used_proxy_info,
                                   was_npn_negotiated,
                                   protocol_negotiated,
                                   using_spdy,
                                   net_log);
  }
}

void HttpStreamFactoryImpl::Request::OrphanJobsExcept(Job* job) {
  DCHECK(job);
  DCHECK(!bound_job_.get());
  DCHECK(ContainsKey(jobs_, job));
  bound_job_.reset(job);
  jobs_.erase(job);
  factory_->request_map_.erase(job);

  OrphanJobs();
}

void HttpStreamFactoryImpl::Request::OrphanJobs() {
  RemoveRequestFromSpdySessionRequestMap();
  RemoveRequestFromHttpPipeliningRequestMap();

  std::set<Job*> tmp;
  tmp.swap(jobs_);

  for (std::set<Job*>::iterator it = tmp.begin(); it != tmp.end(); ++it)
    factory_->OrphanJob(*it, this);
}

void HttpStreamFactoryImpl::Request::OnJobSucceeded(Job* job) {
  // |job| should only be NULL if we're being serviced by a late bound
  // SpdySession or HttpPipelinedConnection (one that was not created by a job
  // in our |jobs_| set).
  if (!job) {
    DCHECK(!bound_job_.get());
    DCHECK(!jobs_.empty());
    // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
    // we *WANT* to cancel the unnecessary Jobs from other requests if another
    // Job completes first.
    // TODO(mbelshe): Revisit this when we implement ip connection pooling of
    // SpdySessions. Do we want to orphan the jobs for a different hostname so
    // they complete? Or do we want to prevent connecting a new SpdySession if
    // we've already got one available for a different hostname where the ip
    // address matches up?
  } else if (!bound_job_.get()) {
    // We may have other jobs in |jobs_|. For example, if we start multiple jobs
    // for Alternate-Protocol.
    OrphanJobsExcept(job);
  } else {
    DCHECK(jobs_.empty());
  }
}

}  // namespace net

/* [<][>][^][v][top][bottom][index][help] */