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.h" 6 7 #include <string> 8 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" 21 #include "url/gurl.h" 22 23 namespace net { 24 25 namespace { 26 27 const PortAlternateProtocolPair kNoAlternateProtocol = { 28 0, UNINITIALIZED_ALTERNATE_PROTOCOL 29 }; 30 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); 40 } 41 42 } // namespace 43 44 HttpStreamFactoryImpl::HttpStreamFactoryImpl(HttpNetworkSession* session, 45 bool for_websockets) 46 : session_(session), 47 http_pipelined_host_pool_(this, NULL, 48 session_->http_server_properties(), 49 session_->force_http_pipelining()), 50 for_websockets_(for_websockets) {} 51 52 HttpStreamFactoryImpl::~HttpStreamFactoryImpl() { 53 DCHECK(request_map_.empty()); 54 DCHECK(spdy_session_request_map_.empty()); 55 DCHECK(http_pipelining_request_map_.empty()); 56 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()); 61 62 tmp_job_set.clear(); 63 tmp_job_set.swap(preconnect_job_set_); 64 STLDeleteContainerPointers(tmp_job_set.begin(), tmp_job_set.end()); 65 DCHECK(preconnect_job_set_.empty()); 66 } 67 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, 77 priority, 78 server_ssl_config, 79 proxy_ssl_config, 80 delegate, 81 NULL, 82 net_log); 83 } 84 85 HttpStreamRequest* HttpStreamFactoryImpl::RequestWebSocketHandshakeStream( 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 WebSocketHandshakeStreamBase::CreateHelper* create_helper, 92 const BoundNetLog& net_log) { 93 DCHECK(for_websockets_); 94 DCHECK(create_helper); 95 return RequestStreamInternal(request_info, 96 priority, 97 server_ssl_config, 98 proxy_ssl_config, 99 delegate, 100 create_helper, 101 net_log); 102 } 103 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 WebSocketHandshakeStreamBase::CreateHelper* 111 websocket_handshake_stream_create_helper, 112 const BoundNetLog& net_log) { 113 Request* request = new Request(request_info.url, 114 this, 115 delegate, 116 websocket_handshake_stream_create_helper, 117 net_log); 118 119 GURL alternate_url; 120 PortAlternateProtocolPair alternate = 121 GetAlternateProtocolRequestFor(request_info.url, &alternate_url); 122 Job* alternate_job = NULL; 123 if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) { 124 // Never share connection with other jobs for FTP requests. 125 DCHECK(!request_info.url.SchemeIs("ftp")); 126 127 HttpRequestInfo alternate_request_info = request_info; 128 alternate_request_info.url = alternate_url; 129 alternate_job = 130 new Job(this, session_, alternate_request_info, priority, 131 server_ssl_config, proxy_ssl_config, net_log.net_log()); 132 request->AttachJob(alternate_job); 133 alternate_job->MarkAsAlternate(request_info.url, alternate); 134 } 135 136 Job* job = new Job(this, session_, request_info, priority, 137 server_ssl_config, proxy_ssl_config, net_log.net_log()); 138 request->AttachJob(job); 139 if (alternate_job) { 140 // Never share connection with other jobs for FTP requests. 141 DCHECK(!request_info.url.SchemeIs("ftp")); 142 143 job->WaitFor(alternate_job); 144 // Make sure to wait until we call WaitFor(), before starting 145 // |alternate_job|, otherwise |alternate_job| will not notify |job| 146 // appropriately. 147 alternate_job->Start(request); 148 } 149 // Even if |alternate_job| has already finished, it won't have notified the 150 // request yet, since we defer that to the next iteration of the MessageLoop, 151 // so starting |job| is always safe. 152 job->Start(request); 153 return request; 154 } 155 156 void HttpStreamFactoryImpl::PreconnectStreams( 157 int num_streams, 158 const HttpRequestInfo& request_info, 159 RequestPriority priority, 160 const SSLConfig& server_ssl_config, 161 const SSLConfig& proxy_ssl_config) { 162 DCHECK(!for_websockets_); 163 GURL alternate_url; 164 PortAlternateProtocolPair alternate = 165 GetAlternateProtocolRequestFor(request_info.url, &alternate_url); 166 Job* job = NULL; 167 if (alternate.protocol != UNINITIALIZED_ALTERNATE_PROTOCOL) { 168 HttpRequestInfo alternate_request_info = request_info; 169 alternate_request_info.url = alternate_url; 170 job = new Job(this, session_, alternate_request_info, priority, 171 server_ssl_config, proxy_ssl_config, session_->net_log()); 172 job->MarkAsAlternate(request_info.url, alternate); 173 } else { 174 job = new Job(this, session_, request_info, priority, 175 server_ssl_config, proxy_ssl_config, session_->net_log()); 176 } 177 preconnect_job_set_.insert(job); 178 job->Preconnect(num_streams); 179 } 180 181 base::Value* HttpStreamFactoryImpl::PipelineInfoToValue() const { 182 return http_pipelined_host_pool_.PipelineInfoToValue(); 183 } 184 185 const HostMappingRules* HttpStreamFactoryImpl::GetHostMappingRules() const { 186 return session_->params().host_mapping_rules; 187 } 188 189 PortAlternateProtocolPair HttpStreamFactoryImpl::GetAlternateProtocolRequestFor( 190 const GURL& original_url, 191 GURL* alternate_url) const { 192 if (!use_alternate_protocols()) 193 return kNoAlternateProtocol; 194 195 if (original_url.SchemeIs("ftp")) 196 return kNoAlternateProtocol; 197 198 HostPortPair origin = HostPortPair(original_url.HostNoBrackets(), 199 original_url.EffectiveIntPort()); 200 201 const HttpServerProperties& http_server_properties = 202 *session_->http_server_properties(); 203 if (!http_server_properties.HasAlternateProtocol(origin)) 204 return kNoAlternateProtocol; 205 206 PortAlternateProtocolPair alternate = 207 http_server_properties.GetAlternateProtocol(origin); 208 if (alternate.protocol == ALTERNATE_PROTOCOL_BROKEN) 209 return kNoAlternateProtocol; 210 211 if (!IsAlternateProtocolValid(alternate.protocol)) { 212 NOTREACHED(); 213 return kNoAlternateProtocol; 214 } 215 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; 227 228 origin.set_port(alternate.port); 229 if (alternate.protocol >= NPN_SPDY_MINIMUM_VERSION && 230 alternate.protocol <= NPN_SPDY_MAXIMUM_VERSION) { 231 if (!spdy_enabled()) 232 return kNoAlternateProtocol; 233 234 if (HttpStreamFactory::HasSpdyExclusion(origin)) 235 return kNoAlternateProtocol; 236 237 *alternate_url = UpgradeUrlToHttps(original_url, alternate.port); 238 } else { 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; 244 } 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; 250 } 251 return alternate; 252 } 253 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)); 258 259 request_map_.erase(job); 260 261 orphaned_job_set_.insert(job); 262 job->Orphan(request); 263 } 264 265 void HttpStreamFactoryImpl::OnNewSpdySessionReady( 266 const base::WeakPtr<SpdySession>& spdy_session, 267 bool direct, 268 const SSLConfig& used_ssl_config, 269 const ProxyInfo& used_proxy_info, 270 bool was_npn_negotiated, 271 NextProto protocol_negotiated, 272 bool using_spdy, 273 const BoundNetLog& net_log) { 274 while (true) { 275 if (!spdy_session) 276 break; 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 280 // the first one. 281 // 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)) 286 break; 287 Request* request = *spdy_session_request_map_[spdy_session_key].begin(); 288 request->Complete(was_npn_negotiated, 289 protocol_negotiated, 290 using_spdy, 291 net_log); 292 if (for_websockets_) { 293 WebSocketHandshakeStreamBase::CreateHelper* create_helper = 294 request->websocket_handshake_stream_create_helper(); 295 DCHECK(create_helper); 296 bool use_relative_url = direct || request->url().SchemeIs("wss"); 297 request->OnWebSocketHandshakeStreamReady( 298 NULL, 299 used_ssl_config, 300 used_proxy_info, 301 create_helper->CreateSpdyStream(spdy_session, use_relative_url)); 302 } else { 303 bool use_relative_url = direct || request->url().SchemeIs("https"); 304 request->OnStreamReady( 305 NULL, 306 used_ssl_config, 307 used_proxy_info, 308 new SpdyHttpStream(spdy_session, use_relative_url)); 309 } 310 } 311 // TODO(mbelshe): Alert other valid requests. 312 } 313 314 void HttpStreamFactoryImpl::OnOrphanedJobComplete(const Job* job) { 315 orphaned_job_set_.erase(job); 316 delete job; 317 } 318 319 void HttpStreamFactoryImpl::OnPreconnectsComplete(const Job* job) { 320 preconnect_job_set_.erase(job); 321 delete job; 322 OnPreconnectsCompleteInternal(); 323 } 324 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( 330 host->GetKey()); 331 if (!stream) { 332 break; 333 } 334 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 339 stream->net_log()); 340 request->OnStreamReady(NULL, 341 stream->used_ssl_config(), 342 stream->used_proxy_info(), 343 stream); 344 } 345 } 346 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]) { 355 continue; 356 } 357 request->OnStreamFailed(NULL, status, used_ssl_config); 358 } 359 } 360 361 } // namespace net 362