1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/http/http_stream_factory_impl_request.h"
6
7#include "base/logging.h"
8#include "base/stl_util-inl.h"
9#include "net/http/http_stream_factory_impl_job.h"
10#include "net/spdy/spdy_http_stream.h"
11#include "net/spdy/spdy_session.h"
12
13namespace net {
14
15HttpStreamFactoryImpl::Request::Request(const GURL& url,
16                                        HttpStreamFactoryImpl* factory,
17                                        HttpStreamRequest::Delegate* delegate,
18                                        const BoundNetLog& net_log)
19    : url_(url),
20      factory_(factory),
21      delegate_(delegate),
22      net_log_(net_log),
23      completed_(false),
24      was_npn_negotiated_(false),
25      using_spdy_(false) {
26  DCHECK(factory_);
27  DCHECK(delegate_);
28
29  net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_REQUEST, NULL);
30}
31
32HttpStreamFactoryImpl::Request::~Request() {
33  if (bound_job_.get())
34    DCHECK(jobs_.empty());
35  else
36    DCHECK(!jobs_.empty());
37
38  net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_REQUEST, NULL);
39
40  for (std::set<Job*>::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
41    factory_->request_map_.erase(*it);
42
43  STLDeleteElements(&jobs_);
44
45  RemoveRequestFromSpdySessionRequestMap();
46}
47
48void HttpStreamFactoryImpl::Request::SetSpdySessionKey(
49    const HostPortProxyPair& spdy_session_key) {
50  DCHECK(!spdy_session_key_.get());
51  spdy_session_key_.reset(new HostPortProxyPair(spdy_session_key));
52  RequestSet& request_set =
53      factory_->spdy_session_request_map_[spdy_session_key];
54  DCHECK(!ContainsKey(request_set, this));
55  request_set.insert(this);
56}
57
58void HttpStreamFactoryImpl::Request::AttachJob(Job* job) {
59  DCHECK(job);
60  jobs_.insert(job);
61  factory_->request_map_[job] = this;
62}
63
64void HttpStreamFactoryImpl::Request::Complete(
65    bool was_npn_negotiated,
66    bool using_spdy,
67    const NetLog::Source& job_source) {
68  DCHECK(!completed_);
69  completed_ = true;
70  was_npn_negotiated_ = was_npn_negotiated;
71  using_spdy_ = using_spdy;
72  net_log_.AddEvent(
73      NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB,
74      make_scoped_refptr(new NetLogSourceParameter(
75          "source_dependency", job_source)));
76}
77
78void HttpStreamFactoryImpl::Request::OnStreamReady(
79    Job* job,
80    const SSLConfig& used_ssl_config,
81    const ProxyInfo& used_proxy_info,
82    HttpStream* stream) {
83  DCHECK(stream);
84  DCHECK(completed_);
85
86  // |job| should only be NULL if we're being serviced by a late bound
87  // SpdySession (one that was not created by a job in our |jobs_| set).
88  if (!job) {
89    DCHECK(!bound_job_.get());
90    DCHECK(!jobs_.empty());
91    // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
92    // we *WANT* to cancel the unnecessary Jobs from other requests if another
93    // Job completes first.
94    // TODO(mbelshe): Revisit this when we implement ip connection pooling of
95    // SpdySessions. Do we want to orphan the jobs for a different hostname so
96    // they complete? Or do we want to prevent connecting a new SpdySession if
97    // we've already got one available for a different hostname where the ip
98    // address matches up?
99  } else if (!bound_job_.get()) {
100    // We may have other jobs in |jobs_|. For example, if we start multiple jobs
101    // for Alternate-Protocol.
102    OrphanJobsExcept(job);
103  } else {
104    DCHECK(jobs_.empty());
105  }
106  delegate_->OnStreamReady(used_ssl_config, used_proxy_info, stream);
107}
108
109void HttpStreamFactoryImpl::Request::OnStreamFailed(
110    Job* job,
111    int status,
112    const SSLConfig& used_ssl_config) {
113  DCHECK_NE(OK, status);
114  if (!bound_job_.get()) {
115    // Hey, we've got other jobs! Maybe one of them will succeed, let's just
116    // ignore this failure.
117    if (jobs_.size() > 1) {
118      jobs_.erase(job);
119      factory_->request_map_.erase(job);
120      delete job;
121      return;
122    } else {
123      bound_job_.reset(job);
124      jobs_.erase(job);
125      DCHECK(jobs_.empty());
126      factory_->request_map_.erase(job);
127    }
128  } else {
129    DCHECK(jobs_.empty());
130  }
131  delegate_->OnStreamFailed(status, used_ssl_config);
132}
133
134void HttpStreamFactoryImpl::Request::OnCertificateError(
135    Job* job,
136    int status,
137    const SSLConfig& used_ssl_config,
138    const SSLInfo& ssl_info) {
139  DCHECK_NE(OK, status);
140  if (!bound_job_.get())
141    OrphanJobsExcept(job);
142  else
143    DCHECK(jobs_.empty());
144  delegate_->OnCertificateError(status, used_ssl_config, ssl_info);
145}
146
147void HttpStreamFactoryImpl::Request::OnNeedsProxyAuth(
148    Job* job,
149    const HttpResponseInfo& proxy_response,
150    const SSLConfig& used_ssl_config,
151    const ProxyInfo& used_proxy_info,
152    HttpAuthController* auth_controller) {
153  if (!bound_job_.get())
154    OrphanJobsExcept(job);
155  else
156    DCHECK(jobs_.empty());
157  delegate_->OnNeedsProxyAuth(
158      proxy_response, used_ssl_config, used_proxy_info, auth_controller);
159}
160
161void HttpStreamFactoryImpl::Request::OnNeedsClientAuth(
162    Job* job,
163    const SSLConfig& used_ssl_config,
164    SSLCertRequestInfo* cert_info) {
165  if (!bound_job_.get())
166    OrphanJobsExcept(job);
167  else
168    DCHECK(jobs_.empty());
169  delegate_->OnNeedsClientAuth(used_ssl_config, cert_info);
170}
171
172void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse(
173    Job *job,
174    const HttpResponseInfo& response_info,
175    const SSLConfig& used_ssl_config,
176    const ProxyInfo& used_proxy_info,
177    HttpStream* stream) {
178  if (!bound_job_.get())
179    OrphanJobsExcept(job);
180  else
181    DCHECK(jobs_.empty());
182  delegate_->OnHttpsProxyTunnelResponse(
183      response_info, used_ssl_config, used_proxy_info, stream);
184}
185
186int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth(
187    const string16& username,
188    const string16& password) {
189  DCHECK(bound_job_.get());
190  return bound_job_->RestartTunnelWithProxyAuth(username, password);
191}
192
193LoadState HttpStreamFactoryImpl::Request::GetLoadState() const {
194  if (bound_job_.get())
195    return bound_job_->GetLoadState();
196  DCHECK(!jobs_.empty());
197
198  // Just pick the first one.
199  return (*jobs_.begin())->GetLoadState();
200}
201
202bool HttpStreamFactoryImpl::Request::was_npn_negotiated() const {
203  DCHECK(completed_);
204  return was_npn_negotiated_;
205}
206
207bool HttpStreamFactoryImpl::Request::using_spdy() const {
208  DCHECK(completed_);
209  return using_spdy_;
210}
211
212void
213HttpStreamFactoryImpl::Request::RemoveRequestFromSpdySessionRequestMap() {
214  if (spdy_session_key_.get()) {
215    SpdySessionRequestMap& spdy_session_request_map =
216        factory_->spdy_session_request_map_;
217    DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key_));
218    RequestSet& request_set =
219        spdy_session_request_map[*spdy_session_key_];
220    DCHECK(ContainsKey(request_set, this));
221    request_set.erase(this);
222    if (request_set.empty())
223      spdy_session_request_map.erase(*spdy_session_key_);
224    spdy_session_key_.reset();
225  }
226}
227
228void HttpStreamFactoryImpl::Request::OnSpdySessionReady(
229    Job* job,
230    scoped_refptr<SpdySession> spdy_session,
231    bool direct) {
232  DCHECK(job);
233  DCHECK(job->using_spdy());
234
235  // The first case is the usual case.
236  if (!bound_job_.get()) {
237    OrphanJobsExcept(job);
238  } else { // This is the case for HTTPS proxy tunneling.
239    DCHECK_EQ(bound_job_.get(), job);
240    DCHECK(jobs_.empty());
241  }
242
243  // Cache these values in case the job gets deleted.
244  const SSLConfig used_ssl_config = job->ssl_config();
245  const ProxyInfo used_proxy_info = job->proxy_info();
246  const bool was_npn_negotiated = job->was_npn_negotiated();
247  const bool using_spdy = job->using_spdy();
248  const NetLog::Source source = job->net_log().source();
249
250  Complete(was_npn_negotiated,
251           using_spdy,
252           source);
253
254  // Cache this so we can still use it if the request is deleted.
255  HttpStreamFactoryImpl* factory = factory_;
256
257  bool use_relative_url = direct || url().SchemeIs("https");
258  delegate_->OnStreamReady(
259      job->ssl_config(),
260      job->proxy_info(),
261      new SpdyHttpStream(spdy_session, use_relative_url));
262  // |this| may be deleted after this point.
263  factory->OnSpdySessionReady(
264      spdy_session, direct, used_ssl_config, used_proxy_info,
265      was_npn_negotiated, using_spdy, source);
266}
267
268void HttpStreamFactoryImpl::Request::OrphanJobsExcept(Job* job) {
269  DCHECK(job);
270  DCHECK(!bound_job_.get());
271  DCHECK(ContainsKey(jobs_, job));
272  bound_job_.reset(job);
273  jobs_.erase(job);
274  factory_->request_map_.erase(job);
275
276  OrphanJobs();
277}
278
279void HttpStreamFactoryImpl::Request::OrphanJobs() {
280  RemoveRequestFromSpdySessionRequestMap();
281
282  std::set<Job*> tmp;
283  tmp.swap(jobs_);
284
285  for (std::set<Job*>::iterator it = tmp.begin(); it != tmp.end(); ++it)
286    factory_->OrphanJob(*it, this);
287}
288
289}  // namespace net
290