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
17 namespace net {
18
19 namespace {
20
UpgradeUrlToHttps(const GURL & original_url)21 GURL 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
HttpStreamFactoryImpl(HttpNetworkSession * session)34 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session)
35 : session_(session) {}
36
~HttpStreamFactoryImpl()37 HttpStreamFactoryImpl::~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
RequestStream(const HttpRequestInfo & request_info,const SSLConfig & ssl_config,HttpStreamRequest::Delegate * delegate,const BoundNetLog & net_log)52 HttpStreamRequest* 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
PreconnectStreams(int num_streams,const HttpRequestInfo & request_info,const SSLConfig & ssl_config,const BoundNetLog & net_log)88 void 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
AddTLSIntolerantServer(const HostPortPair & server)109 void HttpStreamFactoryImpl::AddTLSIntolerantServer(const HostPortPair& server) {
110 tls_intolerant_servers_.insert(server);
111 }
112
IsTLSIntolerantServer(const HostPortPair & server) const113 bool HttpStreamFactoryImpl::IsTLSIntolerantServer(
114 const HostPortPair& server) const {
115 return ContainsKey(tls_intolerant_servers_, server);
116 }
117
GetAlternateProtocolRequestFor(const GURL & original_url,GURL * alternate_url) const118 bool 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
OrphanJob(Job * job,const Request * request)155 void 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
OnSpdySessionReady(scoped_refptr<SpdySession> spdy_session,bool direct,const SSLConfig & used_ssl_config,const ProxyInfo & used_proxy_info,bool was_npn_negotiated,bool using_spdy,const NetLog::Source & source)166 void 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
OnOrphanedJobComplete(const Job * job)197 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) {
198 orphaned_job_set_.erase(job);
199 delete job;
200 }
201
OnPreconnectsComplete(const Job * job)202 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) {
203 preconnect_job_set_.erase(job);
204 delete job;
205 OnPreconnectsCompleteInternal();
206 }
207
208 } // namespace net
209