1// Copyright (c) 2010 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.h"
6
7#include "base/string_number_conversions.h"
8#include "base/stl_util-inl.h"
9#include "googleurl/src/gurl.h"
10#include "net/base/net_log.h"
11#include "net/base/net_util.h"
12#include "net/http/http_network_session.h"
13#include "net/http/http_stream_factory_impl_job.h"
14#include "net/http/http_stream_factory_impl_request.h"
15#include "net/spdy/spdy_http_stream.h"
16
17namespace net {
18
19namespace {
20
21GURL UpgradeUrlToHttps(const GURL& original_url) {
22  GURL::Replacements replacements;
23  // new_sheme and new_port need to be in scope here because GURL::Replacements
24  // references the memory contained by them directly.
25  const std::string new_scheme = "https";
26  const std::string new_port = base::IntToString(443);
27  replacements.SetSchemeStr(new_scheme);
28  replacements.SetPortStr(new_port);
29  return original_url.ReplaceComponents(replacements);
30}
31
32}  // namespace
33
34HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session)
35    : session_(session) {}
36
37HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
38  DCHECK(request_map_.empty());
39  DCHECK(spdy_session_request_map_.empty());
40
41  std::set<const Job*> tmp_job_set;
42  tmp_job_set.swap(orphaned_job_set_);
43  STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
44  DCHECK(orphaned_job_set_.empty());
45
46  tmp_job_set.clear();
47  tmp_job_set.swap(preconnect_job_set_);
48  STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end());
49  DCHECK(preconnect_job_set_.empty());
50}
51
52HttpStreamRequest* HttpStreamFactoryImpl::RequestStream(
53    const HttpRequestInfo& request_info,
54    const SSLConfig& ssl_config,
55    HttpStreamRequest::Delegate* delegate,
56    const BoundNetLog& net_log) {
57  Request* request = new Request(request_info.url, this, delegate, net_log);
58
59  GURL alternate_url;
60  bool has_alternate_protocol =
61      GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
62  Job* alternate_job = NULL;
63  if (has_alternate_protocol) {
64    HttpRequestInfo alternate_request_info = request_info;
65    alternate_request_info.url = alternate_url;
66    alternate_job =
67        new Job(this, session_, alternate_request_info, ssl_config, net_log);
68    request->AttachJob(alternate_job);
69    alternate_job->MarkAsAlternate(request_info.url);
70  }
71
72  Job* job = new Job(this, session_, request_info, ssl_config, net_log);
73  request->AttachJob(job);
74  if (alternate_job) {
75    job->WaitFor(alternate_job);
76    // Make sure to wait until we call WaitFor(), before starting
77    // |alternate_job|, otherwise |alternate_job| will not notify |job|
78    // appropriately.
79    alternate_job->Start(request);
80  }
81  // Even if |alternate_job| has already finished, it won't have notified the
82  // request yet, since we defer that to the next iteration of the MessageLoop,
83  // so starting |job| is always safe.
84  job->Start(request);
85  return request;
86}
87
88void HttpStreamFactoryImpl::PreconnectStreams(
89    int num_streams,
90    const HttpRequestInfo& request_info,
91    const SSLConfig& ssl_config,
92    const BoundNetLog& net_log) {
93  GURL alternate_url;
94  bool has_alternate_protocol =
95      GetAlternateProtocolRequestFor(request_info.url, &alternate_url);
96  Job* job = NULL;
97  if (has_alternate_protocol) {
98    HttpRequestInfo alternate_request_info = request_info;
99    alternate_request_info.url = alternate_url;
100    job = new Job(this, session_, alternate_request_info, ssl_config, net_log);
101    job->MarkAsAlternate(request_info.url);
102  } else {
103    job = new Job(this, session_, request_info, ssl_config, net_log);
104  }
105  preconnect_job_set_.insert(job);
106  job->Preconnect(num_streams);
107}
108
109void HttpStreamFactoryImpl::AddTLSIntolerantServer(const HostPortPair& server) {
110  tls_intolerant_servers_.insert(server);
111}
112
113bool HttpStreamFactoryImpl::IsTLSIntolerantServer(
114    const HostPortPair& server) const {
115  return ContainsKey(tls_intolerant_servers_, server);
116}
117
118bool HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
119    const GURL& original_url,
120    GURL* alternate_url) const {
121  if (!spdy_enabled())
122    return false;
123
124  if (!use_alternate_protocols())
125    return false;
126
127  HostPortPair origin = HostPortPair(original_url.HostNoBrackets(),
128                                     original_url.EffectiveIntPort());
129
130  const HttpAlternateProtocols& alternate_protocols =
131      session_->alternate_protocols();
132  if (!alternate_protocols.HasAlternateProtocolFor(origin))
133    return false;
134
135  HttpAlternateProtocols::PortProtocolPair alternate =
136      alternate_protocols.GetAlternateProtocolFor(origin);
137  if (alternate.protocol == HttpAlternateProtocols::BROKEN)
138    return false;
139
140  DCHECK_LE(HttpAlternateProtocols::NPN_SPDY_1, alternate.protocol);
141  DCHECK_GT(HttpAlternateProtocols::NUM_ALTERNATE_PROTOCOLS,
142            alternate.protocol);
143
144  if (alternate.protocol != HttpAlternateProtocols::NPN_SPDY_2)
145    return false;
146
147  origin.set_port(alternate.port);
148  if (HttpStreamFactory::HasSpdyExclusion(origin))
149    return false;
150
151  *alternate_url = UpgradeUrlToHttps(original_url);
152  return true;
153}
154
155void HttpStreamFactoryImpl::OrphanJob(Job* job, const Request* request) {
156  DCHECK(ContainsKey(request_map_, job));
157  DCHECK_EQ(request_map_[job], request);
158  DCHECK(!ContainsKey(orphaned_job_set_, job));
159
160  request_map_.erase(job);
161
162  orphaned_job_set_.insert(job);
163  job->Orphan(request);
164}
165
166void HttpStreamFactoryImpl::OnSpdySessionReady(
167    scoped_refptr<SpdySession> spdy_session,
168    bool direct,
169    const SSLConfig& used_ssl_config,
170    const ProxyInfo& used_proxy_info,
171    bool was_npn_negotiated,
172    bool using_spdy,
173    const NetLog::Source& source) {
174  const HostPortProxyPair& spdy_session_key =
175      spdy_session->host_port_proxy_pair();
176  while (!spdy_session->IsClosed()) {
177    // Each iteration may empty out the RequestSet for |spdy_session_key_ in
178    // |spdy_session_request_map_|. So each time, check for RequestSet and use
179    // the first one.
180    //
181    // TODO(willchan): If it's important, switch RequestSet out for a FIFO
182    // pqueue (Order by priority first, then FIFO within same priority). Unclear
183    // that it matters here.
184    if (!ContainsKey(spdy_session_request_map_, spdy_session_key))
185      break;
186    Request* request = *spdy_session_request_map_[spdy_session_key].begin();
187    request->Complete(was_npn_negotiated,
188                      using_spdy,
189                      source);
190    bool use_relative_url = direct || request->url().SchemeIs("https");
191    request->OnStreamReady(NULL, used_ssl_config, used_proxy_info,
192                           new SpdyHttpStream(spdy_session, use_relative_url));
193  }
194  // TODO(mbelshe): Alert other valid requests.
195}
196
197void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) {
198  orphaned_job_set_.erase(job);
199  delete job;
200}
201
202void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) {
203  preconnect_job_set_.erase(job);
204  delete job;
205  OnPreconnectsCompleteInternal();
206}
207
208}  // namespace net
209