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/logging.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "net/base/net_log.h"
13 #include "net/base/net_util.h"
14 #include "net/http/http_network_session.h"
15 #include "net/http/http_pipelined_connection.h"
16 #include "net/http/http_pipelined_host.h"
17 #include "net/http/http_pipelined_stream.h"
18 #include "net/http/http_server_properties.h"
19 #include "net/http/http_stream_factory_impl_job.h"
20 #include "net/http/http_stream_factory_impl_request.h"
21 #include "net/spdy/spdy_http_stream.h"
28 const PortAlternateProtocolPair kNoAlternateProtocol
= {
29 0, UNINITIALIZED_ALTERNATE_PROTOCOL
32 GURL
UpgradeUrlToHttps(const GURL
& original_url
, int port
) {
33 GURL::Replacements replacements
;
34 // new_sheme and new_port need to be in scope here because GURL::Replacements
35 // references the memory contained by them directly.
36 const std::string new_scheme
= "https";
37 const std::string new_port
= base::IntToString(port
);
38 replacements
.SetSchemeStr(new_scheme
);
39 replacements
.SetPortStr(new_port
);
40 return original_url
.ReplaceComponents(replacements
);
45 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession
* session
,
48 http_pipelined_host_pool_(this, NULL
,
49 session_
->http_server_properties(),
50 session_
->force_http_pipelining()),
51 for_websockets_(for_websockets
) {}
53 HttpStreamFactoryImpl::~HttpStreamFactoryImpl() {
54 DCHECK(request_map_
.empty());
55 DCHECK(spdy_session_request_map_
.empty());
56 DCHECK(http_pipelining_request_map_
.empty());
58 std::set
<const Job
*> tmp_job_set
;
59 tmp_job_set
.swap(orphaned_job_set_
);
60 STLDeleteContainerPointers(tmp_job_set
.begin(), tmp_job_set
.end());
61 DCHECK(orphaned_job_set_
.empty());
64 tmp_job_set
.swap(preconnect_job_set_
);
65 STLDeleteContainerPointers(tmp_job_set
.begin(), tmp_job_set
.end());
66 DCHECK(preconnect_job_set_
.empty());
69 HttpStreamRequest
* HttpStreamFactoryImpl::RequestStream(
70 const HttpRequestInfo
& request_info
,
71 RequestPriority priority
,
72 const SSLConfig
& server_ssl_config
,
73 const SSLConfig
& proxy_ssl_config
,
74 HttpStreamRequest::Delegate
* delegate
,
75 const BoundNetLog
& net_log
) {
76 DCHECK(!for_websockets_
);
77 return RequestStreamInternal(request_info
,
86 HttpStreamRequest
* HttpStreamFactoryImpl::RequestWebSocketHandshakeStream(
87 const HttpRequestInfo
& request_info
,
88 RequestPriority priority
,
89 const SSLConfig
& server_ssl_config
,
90 const SSLConfig
& proxy_ssl_config
,
91 HttpStreamRequest::Delegate
* delegate
,
92 WebSocketHandshakeStreamBase::CreateHelper
* create_helper
,
93 const BoundNetLog
& net_log
) {
94 DCHECK(for_websockets_
);
95 DCHECK(create_helper
);
96 return RequestStreamInternal(request_info
,
105 HttpStreamRequest
* HttpStreamFactoryImpl::RequestStreamInternal(
106 const HttpRequestInfo
& request_info
,
107 RequestPriority priority
,
108 const SSLConfig
& server_ssl_config
,
109 const SSLConfig
& proxy_ssl_config
,
110 HttpStreamRequest::Delegate
* delegate
,
111 WebSocketHandshakeStreamBase::CreateHelper
*
112 websocket_handshake_stream_create_helper
,
113 const BoundNetLog
& net_log
) {
114 Request
* request
= new Request(request_info
.url
,
117 websocket_handshake_stream_create_helper
,
121 PortAlternateProtocolPair alternate
=
122 GetAlternateProtocolRequestFor(request_info
.url
, &alternate_url
);
123 Job
* alternate_job
= NULL
;
124 if (alternate
.protocol
!= UNINITIALIZED_ALTERNATE_PROTOCOL
) {
125 // Never share connection with other jobs for FTP requests.
126 DCHECK(!request_info
.url
.SchemeIs("ftp"));
128 HttpRequestInfo alternate_request_info
= request_info
;
129 alternate_request_info
.url
= alternate_url
;
131 new Job(this, session_
, alternate_request_info
, priority
,
132 server_ssl_config
, proxy_ssl_config
, net_log
.net_log());
133 request
->AttachJob(alternate_job
);
134 alternate_job
->MarkAsAlternate(request_info
.url
, alternate
);
137 Job
* job
= new Job(this, session_
, request_info
, priority
,
138 server_ssl_config
, proxy_ssl_config
, net_log
.net_log());
139 request
->AttachJob(job
);
141 // Never share connection with other jobs for FTP requests.
142 DCHECK(!request_info
.url
.SchemeIs("ftp"));
144 job
->WaitFor(alternate_job
);
145 // Make sure to wait until we call WaitFor(), before starting
146 // |alternate_job|, otherwise |alternate_job| will not notify |job|
148 alternate_job
->Start(request
);
150 // Even if |alternate_job| has already finished, it won't have notified the
151 // request yet, since we defer that to the next iteration of the MessageLoop,
152 // so starting |job| is always safe.
157 void HttpStreamFactoryImpl::PreconnectStreams(
159 const HttpRequestInfo
& request_info
,
160 RequestPriority priority
,
161 const SSLConfig
& server_ssl_config
,
162 const SSLConfig
& proxy_ssl_config
) {
163 DCHECK(!for_websockets_
);
165 PortAlternateProtocolPair alternate
=
166 GetAlternateProtocolRequestFor(request_info
.url
, &alternate_url
);
168 if (alternate
.protocol
!= UNINITIALIZED_ALTERNATE_PROTOCOL
) {
169 HttpRequestInfo alternate_request_info
= request_info
;
170 alternate_request_info
.url
= alternate_url
;
171 job
= new Job(this, session_
, alternate_request_info
, priority
,
172 server_ssl_config
, proxy_ssl_config
, session_
->net_log());
173 job
->MarkAsAlternate(request_info
.url
, alternate
);
175 job
= new Job(this, session_
, request_info
, priority
,
176 server_ssl_config
, proxy_ssl_config
, session_
->net_log());
178 preconnect_job_set_
.insert(job
);
179 job
->Preconnect(num_streams
);
182 base::Value
* HttpStreamFactoryImpl::PipelineInfoToValue() const {
183 return http_pipelined_host_pool_
.PipelineInfoToValue();
186 const HostMappingRules
* HttpStreamFactoryImpl::GetHostMappingRules() const {
187 return session_
->params().host_mapping_rules
;
190 PortAlternateProtocolPair
HttpStreamFactoryImpl::GetAlternateProtocolRequestFor(
191 const GURL
& original_url
,
192 GURL
* alternate_url
) {
193 if (!use_alternate_protocols())
194 return kNoAlternateProtocol
;
196 if (original_url
.SchemeIs("ftp"))
197 return kNoAlternateProtocol
;
199 HostPortPair origin
= HostPortPair(original_url
.HostNoBrackets(),
200 original_url
.EffectiveIntPort());
202 HttpServerProperties
& http_server_properties
=
203 *session_
->http_server_properties();
204 if (!http_server_properties
.HasAlternateProtocol(origin
))
205 return kNoAlternateProtocol
;
207 PortAlternateProtocolPair alternate
=
208 http_server_properties
.GetAlternateProtocol(origin
);
209 if (alternate
.protocol
== ALTERNATE_PROTOCOL_BROKEN
)
210 return kNoAlternateProtocol
;
212 if (!IsAlternateProtocolValid(alternate
.protocol
)) {
214 return kNoAlternateProtocol
;
217 // Some shared unix systems may have user home directories (like
218 // http://foo.com/~mike) which allow users to emit headers. This is a bad
219 // idea already, but with Alternate-Protocol, it provides the ability for a
220 // single user on a multi-user system to hijack the alternate protocol.
221 // These systems also enforce ports <1024 as restricted ports. So don't
222 // allow protocol upgrades to user-controllable ports.
223 const int kUnrestrictedPort
= 1024;
224 if (!session_
->params().enable_user_alternate_protocol_ports
&&
225 (alternate
.port
>= kUnrestrictedPort
&&
226 origin
.port() < kUnrestrictedPort
))
227 return kNoAlternateProtocol
;
229 origin
.set_port(alternate
.port
);
230 if (alternate
.protocol
>= NPN_SPDY_MINIMUM_VERSION
&&
231 alternate
.protocol
<= NPN_SPDY_MAXIMUM_VERSION
) {
233 return kNoAlternateProtocol
;
235 if (HttpStreamFactory::HasSpdyExclusion(origin
))
236 return kNoAlternateProtocol
;
238 *alternate_url
= UpgradeUrlToHttps(original_url
, alternate
.port
);
240 DCHECK_EQ(QUIC
, alternate
.protocol
);
241 if (!session_
->params().enable_quic
||
242 !(original_url
.SchemeIs("http") ||
243 session_
->params().enable_quic_https
)) {
244 return kNoAlternateProtocol
;
246 // TODO(rch): Figure out how to make QUIC iteract with PAC
247 // scripts. By not re-writing the URL, we will query the PAC script
248 // for the proxy to use to reach the original URL via TCP. But
249 // the alternate request will be going via UDP to a different port.
250 *alternate_url
= original_url
;
255 void HttpStreamFactoryImpl::OrphanJob(Job
* job
, const Request
* request
) {
256 DCHECK(ContainsKey(request_map_
, job
));
257 DCHECK_EQ(request_map_
[job
], request
);
258 DCHECK(!ContainsKey(orphaned_job_set_
, job
));
260 request_map_
.erase(job
);
262 orphaned_job_set_
.insert(job
);
263 job
->Orphan(request
);
266 void HttpStreamFactoryImpl::OnNewSpdySessionReady(
267 const base::WeakPtr
<SpdySession
>& spdy_session
,
269 const SSLConfig
& used_ssl_config
,
270 const ProxyInfo
& used_proxy_info
,
271 bool was_npn_negotiated
,
272 NextProto protocol_negotiated
,
274 const BoundNetLog
& net_log
) {
278 const SpdySessionKey
& spdy_session_key
= spdy_session
->spdy_session_key();
279 // Each iteration may empty out the RequestSet for |spdy_session_key| in
280 // |spdy_session_request_map_|. So each time, check for RequestSet and use
283 // TODO(willchan): If it's important, switch RequestSet out for a FIFO
284 // queue (Order by priority first, then FIFO within same priority). Unclear
285 // that it matters here.
286 if (!ContainsKey(spdy_session_request_map_
, spdy_session_key
))
288 Request
* request
= *spdy_session_request_map_
[spdy_session_key
].begin();
289 request
->Complete(was_npn_negotiated
,
293 if (for_websockets_
) {
294 // TODO(ricea): Restore this code path when WebSocket over SPDY
295 // implementation is ready.
298 bool use_relative_url
= direct
|| request
->url().SchemeIs("https");
299 request
->OnStreamReady(
303 new SpdyHttpStream(spdy_session
, use_relative_url
));
306 // TODO(mbelshe): Alert other valid requests.
309 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job
* job
) {
310 orphaned_job_set_
.erase(job
);
314 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job
* job
) {
315 preconnect_job_set_
.erase(job
);
317 OnPreconnectsCompleteInternal();
320 void HttpStreamFactoryImpl::OnHttpPipelinedHostHasAdditionalCapacity(
321 HttpPipelinedHost
* host
) {
322 while (ContainsKey(http_pipelining_request_map_
, host
->GetKey())) {
323 HttpPipelinedStream
* stream
=
324 http_pipelined_host_pool_
.CreateStreamOnExistingPipeline(
330 Request
* request
= *http_pipelining_request_map_
[host
->GetKey()].begin();
331 request
->Complete(stream
->was_npn_negotiated(),
332 stream
->protocol_negotiated(),
333 false, // not using_spdy
335 request
->OnStreamReady(NULL
,
336 stream
->used_ssl_config(),
337 stream
->used_proxy_info(),
342 void HttpStreamFactoryImpl::AbortPipelinedRequestsWithKey(
343 const Job
* job
, const HttpPipelinedHost::Key
& key
, int status
,
344 const SSLConfig
& used_ssl_config
) {
345 RequestVector requests_to_fail
= http_pipelining_request_map_
[key
];
346 for (RequestVector::const_iterator it
= requests_to_fail
.begin();
347 it
!= requests_to_fail
.end(); ++it
) {
348 Request
* request
= *it
;
349 if (request
== request_map_
[job
]) {
352 request
->OnStreamFailed(NULL
, status
, used_ssl_config
);