http_stream_factory_impl_request.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 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/callback.h"
8#include "base/logging.h"
9#include "base/stl_util.h"
10#include "net/http/http_stream_factory_impl_job.h"
11#include "net/spdy/spdy_http_stream.h"
12#include "net/spdy/spdy_session.h"
13
14namespace net {
15
16HttpStreamFactoryImpl::Request::Request(
17    const GURL& url,
18    HttpStreamFactoryImpl* factory,
19    HttpStreamRequest::Delegate* delegate,
20    WebSocketStreamBase::Factory* websocket_stream_factory,
21    const BoundNetLog& net_log)
22    : url_(url),
23      factory_(factory),
24      websocket_stream_factory_(websocket_stream_factory),
25      delegate_(delegate),
26      net_log_(net_log),
27      completed_(false),
28      was_npn_negotiated_(false),
29      protocol_negotiated_(kProtoUnknown),
30      using_spdy_(false) {
31  DCHECK(factory_);
32  DCHECK(delegate_);
33
34  net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);
35}
36
37HttpStreamFactoryImpl::Request::~Request() {
38  if (bound_job_.get())
39    DCHECK(jobs_.empty());
40  else
41    DCHECK(!jobs_.empty());
42
43  net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_REQUEST);
44
45  for (std::set<Job*>::iterator it = jobs_.begin(); it != jobs_.end(); ++it)
46    factory_->request_map_.erase(*it);
47
48  RemoveRequestFromSpdySessionRequestMap();
49  RemoveRequestFromHttpPipeliningRequestMap();
50
51  STLDeleteElements(&jobs_);
52}
53
54void HttpStreamFactoryImpl::Request::SetSpdySessionKey(
55    const SpdySessionKey& spdy_session_key) {
56  DCHECK(!spdy_session_key_.get());
57  spdy_session_key_.reset(new SpdySessionKey(spdy_session_key));
58  RequestSet& request_set =
59      factory_->spdy_session_request_map_[spdy_session_key];
60  DCHECK(!ContainsKey(request_set, this));
61  request_set.insert(this);
62}
63
64bool HttpStreamFactoryImpl::Request::SetHttpPipeliningKey(
65    const HttpPipelinedHost::Key& http_pipelining_key) {
66  CHECK(!http_pipelining_key_.get());
67  http_pipelining_key_.reset(new HttpPipelinedHost::Key(http_pipelining_key));
68  bool was_new_key = !ContainsKey(factory_->http_pipelining_request_map_,
69                                  http_pipelining_key);
70  RequestVector& request_vector =
71      factory_->http_pipelining_request_map_[http_pipelining_key];
72  request_vector.push_back(this);
73  return was_new_key;
74}
75
76void HttpStreamFactoryImpl::Request::AttachJob(Job* job) {
77  DCHECK(job);
78  jobs_.insert(job);
79  factory_->request_map_[job] = this;
80}
81
82void HttpStreamFactoryImpl::Request::Complete(
83    bool was_npn_negotiated,
84    NextProto protocol_negotiated,
85    bool using_spdy,
86    const BoundNetLog& job_net_log) {
87  DCHECK(!completed_);
88  completed_ = true;
89  was_npn_negotiated_ = was_npn_negotiated;
90  protocol_negotiated_ = protocol_negotiated;
91  using_spdy_ = using_spdy;
92  net_log_.AddEvent(
93      NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB,
94      job_net_log.source().ToEventParametersCallback());
95  job_net_log.AddEvent(
96      NetLog::TYPE_HTTP_STREAM_JOB_BOUND_TO_REQUEST,
97      net_log_.source().ToEventParametersCallback());
98}
99
100void HttpStreamFactoryImpl::Request::OnStreamReady(
101    Job* job,
102    const SSLConfig& used_ssl_config,
103    const ProxyInfo& used_proxy_info,
104    HttpStreamBase* stream) {
105  DCHECK(!factory_->for_websockets_);
106  DCHECK(stream);
107  DCHECK(completed_);
108
109  OnJobSucceeded(job);
110  delegate_->OnStreamReady(used_ssl_config, used_proxy_info, stream);
111}
112
113void HttpStreamFactoryImpl::Request::OnWebSocketStreamReady(
114    Job* job,
115    const SSLConfig& used_ssl_config,
116    const ProxyInfo& used_proxy_info,
117    WebSocketStreamBase* stream) {
118  DCHECK(factory_->for_websockets_);
119  DCHECK(stream);
120  DCHECK(completed_);
121
122  OnJobSucceeded(job);
123  delegate_->OnWebSocketStreamReady(used_ssl_config, used_proxy_info, stream);
124}
125
126void HttpStreamFactoryImpl::Request::OnStreamFailed(
127    Job* job,
128    int status,
129    const SSLConfig& used_ssl_config) {
130  DCHECK_NE(OK, status);
131  // |job| should only be NULL if we're being canceled by a late bound
132  // HttpPipelinedConnection (one that was not created by a job in our |jobs_|
133  // set).
134  if (!job) {
135    DCHECK(!bound_job_.get());
136    DCHECK(!jobs_.empty());
137    // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
138    // we *WANT* to cancel the unnecessary Jobs from other requests if another
139    // Job completes first.
140  } else if (!bound_job_.get()) {
141    // Hey, we've got other jobs! Maybe one of them will succeed, let's just
142    // ignore this failure.
143    if (jobs_.size() > 1) {
144      jobs_.erase(job);
145      factory_->request_map_.erase(job);
146      delete job;
147      return;
148    } else {
149      bound_job_.reset(job);
150      jobs_.erase(job);
151      DCHECK(jobs_.empty());
152      factory_->request_map_.erase(job);
153    }
154  } else {
155    DCHECK(jobs_.empty());
156  }
157  delegate_->OnStreamFailed(status, used_ssl_config);
158}
159
160void HttpStreamFactoryImpl::Request::OnCertificateError(
161    Job* job,
162    int status,
163    const SSLConfig& used_ssl_config,
164    const SSLInfo& ssl_info) {
165  DCHECK_NE(OK, status);
166  if (!bound_job_.get())
167    OrphanJobsExcept(job);
168  else
169    DCHECK(jobs_.empty());
170  delegate_->OnCertificateError(status, used_ssl_config, ssl_info);
171}
172
173void HttpStreamFactoryImpl::Request::OnNeedsProxyAuth(
174    Job* job,
175    const HttpResponseInfo& proxy_response,
176    const SSLConfig& used_ssl_config,
177    const ProxyInfo& used_proxy_info,
178    HttpAuthController* auth_controller) {
179  if (!bound_job_.get())
180    OrphanJobsExcept(job);
181  else
182    DCHECK(jobs_.empty());
183  delegate_->OnNeedsProxyAuth(
184      proxy_response, used_ssl_config, used_proxy_info, auth_controller);
185}
186
187void HttpStreamFactoryImpl::Request::OnNeedsClientAuth(
188    Job* job,
189    const SSLConfig& used_ssl_config,
190    SSLCertRequestInfo* cert_info) {
191  if (!bound_job_.get())
192    OrphanJobsExcept(job);
193  else
194    DCHECK(jobs_.empty());
195  delegate_->OnNeedsClientAuth(used_ssl_config, cert_info);
196}
197
198void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse(
199    Job *job,
200    const HttpResponseInfo& response_info,
201    const SSLConfig& used_ssl_config,
202    const ProxyInfo& used_proxy_info,
203    HttpStreamBase* stream) {
204  if (!bound_job_.get())
205    OrphanJobsExcept(job);
206  else
207    DCHECK(jobs_.empty());
208  delegate_->OnHttpsProxyTunnelResponse(
209      response_info, used_ssl_config, used_proxy_info, stream);
210}
211
212int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth(
213    const AuthCredentials& credentials) {
214  DCHECK(bound_job_.get());
215  return bound_job_->RestartTunnelWithProxyAuth(credentials);
216}
217
218LoadState HttpStreamFactoryImpl::Request::GetLoadState() const {
219  if (bound_job_.get())
220    return bound_job_->GetLoadState();
221  DCHECK(!jobs_.empty());
222
223  // Just pick the first one.
224  return (*jobs_.begin())->GetLoadState();
225}
226
227bool HttpStreamFactoryImpl::Request::was_npn_negotiated() const {
228  DCHECK(completed_);
229  return was_npn_negotiated_;
230}
231
232NextProto HttpStreamFactoryImpl::Request::protocol_negotiated()
233    const {
234  DCHECK(completed_);
235  return protocol_negotiated_;
236}
237
238bool HttpStreamFactoryImpl::Request::using_spdy() const {
239  DCHECK(completed_);
240  return using_spdy_;
241}
242
243void
244HttpStreamFactoryImpl::Request::RemoveRequestFromSpdySessionRequestMap() {
245  if (spdy_session_key_.get()) {
246    SpdySessionRequestMap& spdy_session_request_map =
247        factory_->spdy_session_request_map_;
248    DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key_));
249    RequestSet& request_set =
250        spdy_session_request_map[*spdy_session_key_];
251    DCHECK(ContainsKey(request_set, this));
252    request_set.erase(this);
253    if (request_set.empty())
254      spdy_session_request_map.erase(*spdy_session_key_);
255    spdy_session_key_.reset();
256  }
257}
258
259void
260HttpStreamFactoryImpl::Request::RemoveRequestFromHttpPipeliningRequestMap() {
261  if (http_pipelining_key_.get()) {
262    HttpPipeliningRequestMap& http_pipelining_request_map =
263        factory_->http_pipelining_request_map_;
264    DCHECK(ContainsKey(http_pipelining_request_map, *http_pipelining_key_));
265    RequestVector& request_vector =
266        http_pipelining_request_map[*http_pipelining_key_];
267    for (RequestVector::iterator it = request_vector.begin();
268         it != request_vector.end(); ++it) {
269      if (*it == this) {
270        request_vector.erase(it);
271        break;
272      }
273    }
274    if (request_vector.empty())
275      http_pipelining_request_map.erase(*http_pipelining_key_);
276    http_pipelining_key_.reset();
277  }
278}
279
280void HttpStreamFactoryImpl::Request::OnNewSpdySessionReady(
281    Job* job,
282    scoped_refptr<SpdySession> spdy_session,
283    bool direct) {
284  DCHECK(job);
285  DCHECK(job->using_spdy());
286
287  // The first case is the usual case.
288  if (!bound_job_.get()) {
289    OrphanJobsExcept(job);
290  } else {  // This is the case for HTTPS proxy tunneling.
291    DCHECK_EQ(bound_job_.get(), job);
292    DCHECK(jobs_.empty());
293  }
294
295  // Cache these values in case the job gets deleted.
296  const SSLConfig used_ssl_config = job->server_ssl_config();
297  const ProxyInfo used_proxy_info = job->proxy_info();
298  const bool was_npn_negotiated = job->was_npn_negotiated();
299  const NextProto protocol_negotiated =
300      job->protocol_negotiated();
301  const bool using_spdy = job->using_spdy();
302  const BoundNetLog net_log = job->net_log();
303
304  Complete(was_npn_negotiated, protocol_negotiated, using_spdy, net_log);
305
306  // Cache this so we can still use it if the request is deleted.
307  HttpStreamFactoryImpl* factory = factory_;
308  if (factory->for_websockets_) {
309    DCHECK(websocket_stream_factory_);
310    bool use_relative_url = direct || url().SchemeIs("wss");
311    delegate_->OnWebSocketStreamReady(
312        job->server_ssl_config(),
313        job->proxy_info(),
314        websocket_stream_factory_->CreateSpdyStream(spdy_session.get(),
315                                                    use_relative_url));
316  } else {
317    bool use_relative_url = direct || url().SchemeIs("https");
318    delegate_->OnStreamReady(
319        job->server_ssl_config(),
320        job->proxy_info(),
321        new SpdyHttpStream(spdy_session.get(), use_relative_url));
322  }
323  // |this| may be deleted after this point.
324  factory->OnNewSpdySessionReady(spdy_session.get(),
325                                 direct,
326                                 used_ssl_config,
327                                 used_proxy_info,
328                                 was_npn_negotiated,
329                                 protocol_negotiated,
330                                 using_spdy,
331                                 net_log);
332}
333
334void HttpStreamFactoryImpl::Request::OrphanJobsExcept(Job* job) {
335  DCHECK(job);
336  DCHECK(!bound_job_.get());
337  DCHECK(ContainsKey(jobs_, job));
338  bound_job_.reset(job);
339  jobs_.erase(job);
340  factory_->request_map_.erase(job);
341
342  OrphanJobs();
343}
344
345void HttpStreamFactoryImpl::Request::OrphanJobs() {
346  RemoveRequestFromSpdySessionRequestMap();
347  RemoveRequestFromHttpPipeliningRequestMap();
348
349  std::set<Job*> tmp;
350  tmp.swap(jobs_);
351
352  for (std::set<Job*>::iterator it = tmp.begin(); it != tmp.end(); ++it)
353    factory_->OrphanJob(*it, this);
354}
355
356void HttpStreamFactoryImpl::Request::OnJobSucceeded(Job* job) {
357  // |job| should only be NULL if we're being serviced by a late bound
358  // SpdySession or HttpPipelinedConnection (one that was not created by a job
359  // in our |jobs_| set).
360  if (!job) {
361    DCHECK(!bound_job_.get());
362    DCHECK(!jobs_.empty());
363    // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because
364    // we *WANT* to cancel the unnecessary Jobs from other requests if another
365    // Job completes first.
366    // TODO(mbelshe): Revisit this when we implement ip connection pooling of
367    // SpdySessions. Do we want to orphan the jobs for a different hostname so
368    // they complete? Or do we want to prevent connecting a new SpdySession if
369    // we've already got one available for a different hostname where the ip
370    // address matches up?
371  } else if (!bound_job_.get()) {
372    // We may have other jobs in |jobs_|. For example, if we start multiple jobs
373    // for Alternate-Protocol.
374    OrphanJobsExcept(job);
375  } else {
376    DCHECK(jobs_.empty());
377  }
378}
379
380}  // namespace net
381