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.
5 #include "net/http/http_stream_factory_impl.h"
9 #include "base/stl_util.h"
10 #include "base/strings/string_number_conversions.h"
11 #include "net/base/net_log.h"
12 #include "net/base/net_util.h"
13 #include "net/http/http_network_session.h"
14 #include "net/http/http_pipelined_connection.h"
15 #include "net/http/http_pipelined_host.h"
16 #include "net/http/http_pipelined_stream.h"
17 #include "net/http/http_server_properties.h"
18 #include "net/http/http_stream_factory_impl_job.h"
19 #include "net/http/http_stream_factory_impl_request.h"
20 #include "net/spdy/spdy_http_stream.h"
27 const PortAlternateProtocolPair kNoAlternateProtocol
= {
28 0, UNINITIALIZED_ALTERNATE_PROTOCOL
31 GURL
UpgradeUrlToHttps(const GURL
& original_url
, int port
) {
32 GURL::Replacements replacements
;
33 // new_sheme and new_port need to be in scope here because GURL::Replacements
34 // references the memory contained by them directly.
35 const std::string new_scheme
= "https";
36 const std::string new_port
= base::IntToString(port
);
37 replacements
.SetSchemeStr(new_scheme
);
38 replacements
.SetPortStr(new_port
);
39 return original_url
.ReplaceComponents(replacements
);
44 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession
* session
,
47 http_pipelined_host_pool_(this, NULL
,
48 session_
->http_server_properties(),
49 session_
->force_http_pipelining()),
50 for_websockets_(for_websockets
) {}
52 HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
53 DCHECK(request_map_
.empty());
54 DCHECK(spdy_session_request_map_
.empty());
55 DCHECK(http_pipelining_request_map_
.empty());
57 std::set
<const Job
*> tmp_job_set
;
58 tmp_job_set
.swap(orphaned_job_set_
);
59 STLDeleteContainerPointers(tmp_job_set
.begin(), tmp_job_set
.end());
60 DCHECK(orphaned_job_set_
.empty());
63 tmp_job_set
.swap(preconnect_job_set_
);
64 STLDeleteContainerPointers(tmp_job_set
.begin(), tmp_job_set
.end());
65 DCHECK(preconnect_job_set_
.empty());
68 HttpStreamRequest
* HttpStreamFactoryImpl::RequestStream(
69 const HttpRequestInfo
& request_info
,
70 RequestPriority priority
,
71 const SSLConfig
& server_ssl_config
,
72 const SSLConfig
& proxy_ssl_config
,
73 HttpStreamRequest::Delegate
* delegate
,
74 const BoundNetLog
& net_log
) {
75 DCHECK(!for_websockets_
);
76 return RequestStreamInternal(request_info
,
85 HttpStreamRequest
* HttpStreamFactoryImpl::RequestWebSocketStream(
86 const HttpRequestInfo
& request_info
,
87 RequestPriority priority
,
88 const SSLConfig
& server_ssl_config
,
89 const SSLConfig
& proxy_ssl_config
,
90 HttpStreamRequest::Delegate
* delegate
,
91 WebSocketStreamBase::Factory
* factory
,
92 const BoundNetLog
& net_log
) {
93 DCHECK(for_websockets_
);
95 return RequestStreamInternal(request_info
,
104 HttpStreamRequest
* HttpStreamFactoryImpl::RequestStreamInternal(
105 const HttpRequestInfo
& request_info
,
106 RequestPriority priority
,
107 const SSLConfig
& server_ssl_config
,
108 const SSLConfig
& proxy_ssl_config
,
109 HttpStreamRequest::Delegate
* delegate
,
110 WebSocketStreamBase::Factory
* websocket_stream_factory
,
111 const BoundNetLog
& net_log
) {
112 Request
* request
= new Request(request_info
.url
,
115 websocket_stream_factory
,
119 PortAlternateProtocolPair alternate
=
120 GetAlternateProtocolRequestFor(request_info
.url
, &alternate_url
);
121 Job
* alternate_job
= NULL
;
122 if (alternate
.protocol
!= UNINITIALIZED_ALTERNATE_PROTOCOL
) {
123 // Never share connection with other jobs for FTP requests.
124 DCHECK(!request_info
.url
.SchemeIs("ftp"));
126 HttpRequestInfo alternate_request_info
= request_info
;
127 alternate_request_info
.url
= alternate_url
;
129 new Job(this, session_
, alternate_request_info
, priority
,
130 server_ssl_config
, proxy_ssl_config
, net_log
.net_log());
131 request
->AttachJob(alternate_job
);
132 alternate_job
->MarkAsAlternate(request_info
.url
, alternate
);
135 Job
* job
= new Job(this, session_
, request_info
, priority
,
136 server_ssl_config
, proxy_ssl_config
, net_log
.net_log());
137 request
->AttachJob(job
);
139 // Never share connection with other jobs for FTP requests.
140 DCHECK(!request_info
.url
.SchemeIs("ftp"));
142 job
->WaitFor(alternate_job
);
143 // Make sure to wait until we call WaitFor(), before starting
144 // |alternate_job|, otherwise |alternate_job| will not notify |job|
146 alternate_job
->Start(request
);
148 // Even if |alternate_job| has already finished, it won't have notified the
149 // request yet, since we defer that to the next iteration of the MessageLoop,
150 // so starting |job| is always safe.
155 void HttpStreamFactoryImpl::PreconnectStreams(
157 const HttpRequestInfo
& request_info
,
158 RequestPriority priority
,
159 const SSLConfig
& server_ssl_config
,
160 const SSLConfig
& proxy_ssl_config
) {
161 DCHECK(!for_websockets_
);
163 PortAlternateProtocolPair alternate
=
164 GetAlternateProtocolRequestFor(request_info
.url
, &alternate_url
);
166 if (alternate
.protocol
!= UNINITIALIZED_ALTERNATE_PROTOCOL
) {
167 HttpRequestInfo alternate_request_info
= request_info
;
168 alternate_request_info
.url
= alternate_url
;
169 job
= new Job(this, session_
, alternate_request_info
, priority
,
170 server_ssl_config
, proxy_ssl_config
, session_
->net_log());
171 job
->MarkAsAlternate(request_info
.url
, alternate
);
173 job
= new Job(this, session_
, request_info
, priority
,
174 server_ssl_config
, proxy_ssl_config
, session_
->net_log());
176 preconnect_job_set_
.insert(job
);
177 job
->Preconnect(num_streams
);
180 base::Value
* HttpStreamFactoryImpl::PipelineInfoToValue() const {
181 return http_pipelined_host_pool_
.PipelineInfoToValue();
184 const HostMappingRules
* HttpStreamFactoryImpl::GetHostMappingRules() const {
185 return session_
->params().host_mapping_rules
;
188 PortAlternateProtocolPair
HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
189 const GURL
& original_url
,
190 GURL
* alternate_url
) const {
191 if (!use_alternate_protocols())
192 return kNoAlternateProtocol
;
194 if (original_url
.SchemeIs("ftp"))
195 return kNoAlternateProtocol
;
197 HostPortPair origin
= HostPortPair(original_url
.HostNoBrackets(),
198 original_url
.EffectiveIntPort());
200 const HttpServerProperties
& http_server_properties
=
201 *session_
->http_server_properties();
202 if (!http_server_properties
.HasAlternateProtocol(origin
))
203 return kNoAlternateProtocol
;
205 PortAlternateProtocolPair alternate
=
206 http_server_properties
.GetAlternateProtocol(origin
);
207 if (alternate
.protocol
== ALTERNATE_PROTOCOL_BROKEN
)
208 return kNoAlternateProtocol
;
210 DCHECK_LE(NPN_SPDY_1
, alternate
.protocol
);
211 DCHECK_GT(NUM_ALTERNATE_PROTOCOLS
, alternate
.protocol
);
213 if (alternate
.protocol
< NPN_SPDY_2
)
214 return kNoAlternateProtocol
;
216 // Some shared unix systems may have user home directories (like
217 // http://foo.com/~mike) which allow users to emit headers. This is a bad
218 // idea already, but with Alternate-Protocol, it provides the ability for a
219 // single user on a multi-user system to hijack the alternate protocol.
220 // These systems also enforce ports <1024 as restricted ports. So don't
221 // allow protocol upgrades to user-controllable ports.
222 const int kUnrestrictedPort
= 1024;
223 if (!session_
->params().enable_user_alternate_protocol_ports
&&
224 (alternate
.port
>= kUnrestrictedPort
&&
225 origin
.port() < kUnrestrictedPort
))
226 return kNoAlternateProtocol
;
228 origin
.set_port(alternate
.port
);
229 if (alternate
.protocol
>= NPN_SPDY_MINIMUM_VERSION
&&
230 alternate
.protocol
<= NPN_SPDY_MAXIMUM_VERSION
) {
232 return kNoAlternateProtocol
;
234 if (HttpStreamFactory::HasSpdyExclusion(origin
))
235 return kNoAlternateProtocol
;
237 *alternate_url
= UpgradeUrlToHttps(original_url
, alternate
.port
);
239 DCHECK_EQ(QUIC
, alternate
.protocol
);
240 if (!session_
->params().enable_quic
||
241 !(original_url
.SchemeIs("http") ||
242 session_
->params().enable_quic_https
)) {
243 return kNoAlternateProtocol
;
245 // TODO(rch): Figure out how to make QUIC iteract with PAC
246 // scripts. By not re-writing the URL, we will query the PAC script
247 // for the proxy to use to reach the original URL via TCP. But
248 // the alternate request will be going via UDP to a different port.
249 *alternate_url
= original_url
;
254 void HttpStreamFactoryImpl::OrphanJob(Job
* job
, const Request
* request
) {
255 DCHECK(ContainsKey(request_map_
, job
));
256 DCHECK_EQ(request_map_
[job
], request
);
257 DCHECK(!ContainsKey(orphaned_job_set_
, job
));
259 request_map_
.erase(job
);
261 orphaned_job_set_
.insert(job
);
262 job
->Orphan(request
);
265 void HttpStreamFactoryImpl::OnNewSpdySessionReady(
266 const base::WeakPtr
<SpdySession
>& spdy_session
,
268 const SSLConfig
& used_ssl_config
,
269 const ProxyInfo
& used_proxy_info
,
270 bool was_npn_negotiated
,
271 NextProto protocol_negotiated
,
273 const BoundNetLog
& net_log
) {
277 const SpdySessionKey
& spdy_session_key
= spdy_session
->spdy_session_key();
278 // Each iteration may empty out the RequestSet for |spdy_session_key| in
279 // |spdy_session_request_map_|. So each time, check for RequestSet and use
282 // TODO(willchan): If it's important, switch RequestSet out for a FIFO
283 // queue (Order by priority first, then FIFO within same priority). Unclear
284 // that it matters here.
285 if (!ContainsKey(spdy_session_request_map_
, spdy_session_key
))
287 Request
* request
= *spdy_session_request_map_
[spdy_session_key
].begin();
288 request
->Complete(was_npn_negotiated
,
292 if (for_websockets_
) {
293 WebSocketStreamBase::Factory
* factory
=
294 request
->websocket_stream_factory();
296 bool use_relative_url
= direct
|| request
->url().SchemeIs("wss");
297 request
->OnWebSocketStreamReady(
301 factory
->CreateSpdyStream(spdy_session
, use_relative_url
));
303 bool use_relative_url
= direct
|| request
->url().SchemeIs("https");
304 request
->OnStreamReady(
308 new SpdyHttpStream(spdy_session
, use_relative_url
));
311 // TODO(mbelshe): Alert other valid requests.
314 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job
* job
) {
315 orphaned_job_set_
.erase(job
);
319 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job
* job
) {
320 preconnect_job_set_
.erase(job
);
322 OnPreconnectsCompleteInternal();
325 void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity(
326 HttpPipelinedHost
* host
) {
327 while (ContainsKey(http_pipelining_request_map_
, host
->GetKey())) {
328 HttpPipelinedStream
* stream
=
329 http_pipelined_host_pool_
.CreateStreamOnExistingPipeline(
335 Request
* request
= *http_pipelining_request_map_
[host
->GetKey()].begin();
336 request
->Complete(stream
->was_npn_negotiated(),
337 stream
->protocol_negotiated(),
338 false, // not using_spdy
340 request
->OnStreamReady(NULL
,
341 stream
->used_ssl_config(),
342 stream
->used_proxy_info(),
347 void HttpStreamFactoryImpl::AbortPipelinedRequestsWithKey(
348 const Job
* job
, const HttpPipelinedHost::Key
& key
, int status
,
349 const SSLConfig
& used_ssl_config
) {
350 RequestVector requests_to_fail
= http_pipelining_request_map_
[key
];
351 for (RequestVector::const_iterator it
= requests_to_fail
.begin();
352 it
!= requests_to_fail
.end(); ++it
) {
353 Request
* request
= *it
;
354 if (request
== request_map_
[job
]) {
357 request
->OnStreamFailed(NULL
, status
, used_ssl_config
);