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_request.h" 6 7 #include "base/callback.h" 8 #include "base/logging.h" 9 #include "base/stl_util.h" 10 #include "net/http/http_stream_factory_impl_job.h" 11 #include "net/spdy/spdy_http_stream.h" 12 #include "net/spdy/spdy_session.h" 13 14 namespace net { 15 16 HttpStreamFactoryImpl::Request::Request( 17 const GURL& url, 18 HttpStreamFactoryImpl* factory, 19 HttpStreamRequest::Delegate* delegate, 20 WebSocketHandshakeStreamBase::CreateHelper* 21 websocket_handshake_stream_create_helper, 22 const BoundNetLog& net_log) 23 : url_(url), 24 factory_(factory), 25 websocket_handshake_stream_create_helper_( 26 websocket_handshake_stream_create_helper), 27 delegate_(delegate), 28 net_log_(net_log), 29 completed_(false), 30 was_npn_negotiated_(false), 31 protocol_negotiated_(kProtoUnknown), 32 using_spdy_(false) { 33 DCHECK(factory_); 34 DCHECK(delegate_); 35 36 net_log_.BeginEvent(NetLog::TYPE_HTTP_STREAM_REQUEST); 37 } 38 39 HttpStreamFactoryImpl::Request::~Request() { 40 if (bound_job_.get()) 41 DCHECK(jobs_.empty()); 42 else 43 DCHECK(!jobs_.empty()); 44 45 net_log_.EndEvent(NetLog::TYPE_HTTP_STREAM_REQUEST); 46 47 for (std::set<Job*>::iterator it = jobs_.begin(); it != jobs_.end(); ++it) 48 factory_->request_map_.erase(*it); 49 50 RemoveRequestFromSpdySessionRequestMap(); 51 RemoveRequestFromHttpPipeliningRequestMap(); 52 53 STLDeleteElements(&jobs_); 54 } 55 56 void HttpStreamFactoryImpl::Request::SetSpdySessionKey( 57 const SpdySessionKey& spdy_session_key) { 58 DCHECK(!spdy_session_key_.get()); 59 spdy_session_key_.reset(new SpdySessionKey(spdy_session_key)); 60 RequestSet& request_set = 61 factory_->spdy_session_request_map_[spdy_session_key]; 62 DCHECK(!ContainsKey(request_set, this)); 63 request_set.insert(this); 64 } 65 66 bool HttpStreamFactoryImpl::Request::SetHttpPipeliningKey( 67 const HttpPipelinedHost::Key& http_pipelining_key) { 68 CHECK(!http_pipelining_key_.get()); 69 http_pipelining_key_.reset(new HttpPipelinedHost::Key(http_pipelining_key)); 70 bool was_new_key = !ContainsKey(factory_->http_pipelining_request_map_, 71 http_pipelining_key); 72 RequestVector& request_vector = 73 factory_->http_pipelining_request_map_[http_pipelining_key]; 74 request_vector.push_back(this); 75 return was_new_key; 76 } 77 78 void HttpStreamFactoryImpl::Request::AttachJob(Job* job) { 79 DCHECK(job); 80 jobs_.insert(job); 81 factory_->request_map_[job] = this; 82 } 83 84 void HttpStreamFactoryImpl::Request::Complete( 85 bool was_npn_negotiated, 86 NextProto protocol_negotiated, 87 bool using_spdy, 88 const BoundNetLog& job_net_log) { 89 DCHECK(!completed_); 90 completed_ = true; 91 was_npn_negotiated_ = was_npn_negotiated; 92 protocol_negotiated_ = protocol_negotiated; 93 using_spdy_ = using_spdy; 94 net_log_.AddEvent( 95 NetLog::TYPE_HTTP_STREAM_REQUEST_BOUND_TO_JOB, 96 job_net_log.source().ToEventParametersCallback()); 97 job_net_log.AddEvent( 98 NetLog::TYPE_HTTP_STREAM_JOB_BOUND_TO_REQUEST, 99 net_log_.source().ToEventParametersCallback()); 100 } 101 102 void HttpStreamFactoryImpl::Request::OnStreamReady( 103 Job* job, 104 const SSLConfig& used_ssl_config, 105 const ProxyInfo& used_proxy_info, 106 HttpStreamBase* stream) { 107 DCHECK(!factory_->for_websockets_); 108 DCHECK(stream); 109 DCHECK(completed_); 110 111 OnJobSucceeded(job); 112 delegate_->OnStreamReady(used_ssl_config, used_proxy_info, stream); 113 } 114 115 void HttpStreamFactoryImpl::Request::OnWebSocketHandshakeStreamReady( 116 Job* job, 117 const SSLConfig& used_ssl_config, 118 const ProxyInfo& used_proxy_info, 119 WebSocketHandshakeStreamBase* stream) { 120 DCHECK(factory_->for_websockets_); 121 DCHECK(stream); 122 DCHECK(completed_); 123 124 OnJobSucceeded(job); 125 delegate_->OnWebSocketHandshakeStreamReady( 126 used_ssl_config, used_proxy_info, stream); 127 } 128 129 void HttpStreamFactoryImpl::Request::OnStreamFailed( 130 Job* job, 131 int status, 132 const SSLConfig& used_ssl_config) { 133 DCHECK_NE(OK, status); 134 // |job| should only be NULL if we're being canceled by a late bound 135 // HttpPipelinedConnection (one that was not created by a job in our |jobs_| 136 // set). 137 if (!job) { 138 DCHECK(!bound_job_.get()); 139 DCHECK(!jobs_.empty()); 140 // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because 141 // we *WANT* to cancel the unnecessary Jobs from other requests if another 142 // Job completes first. 143 } else if (!bound_job_.get()) { 144 // Hey, we've got other jobs! Maybe one of them will succeed, let's just 145 // ignore this failure. 146 if (jobs_.size() > 1) { 147 jobs_.erase(job); 148 factory_->request_map_.erase(job); 149 delete job; 150 return; 151 } else { 152 bound_job_.reset(job); 153 jobs_.erase(job); 154 DCHECK(jobs_.empty()); 155 factory_->request_map_.erase(job); 156 } 157 } else { 158 DCHECK(jobs_.empty()); 159 } 160 delegate_->OnStreamFailed(status, used_ssl_config); 161 } 162 163 void HttpStreamFactoryImpl::Request::OnCertificateError( 164 Job* job, 165 int status, 166 const SSLConfig& used_ssl_config, 167 const SSLInfo& ssl_info) { 168 DCHECK_NE(OK, status); 169 if (!bound_job_.get()) 170 OrphanJobsExcept(job); 171 else 172 DCHECK(jobs_.empty()); 173 delegate_->OnCertificateError(status, used_ssl_config, ssl_info); 174 } 175 176 void HttpStreamFactoryImpl::Request::OnNeedsProxyAuth( 177 Job* job, 178 const HttpResponseInfo& proxy_response, 179 const SSLConfig& used_ssl_config, 180 const ProxyInfo& used_proxy_info, 181 HttpAuthController* auth_controller) { 182 if (!bound_job_.get()) 183 OrphanJobsExcept(job); 184 else 185 DCHECK(jobs_.empty()); 186 delegate_->OnNeedsProxyAuth( 187 proxy_response, used_ssl_config, used_proxy_info, auth_controller); 188 } 189 190 void HttpStreamFactoryImpl::Request::OnNeedsClientAuth( 191 Job* job, 192 const SSLConfig& used_ssl_config, 193 SSLCertRequestInfo* cert_info) { 194 if (!bound_job_.get()) 195 OrphanJobsExcept(job); 196 else 197 DCHECK(jobs_.empty()); 198 delegate_->OnNeedsClientAuth(used_ssl_config, cert_info); 199 } 200 201 void HttpStreamFactoryImpl::Request::OnHttpsProxyTunnelResponse( 202 Job *job, 203 const HttpResponseInfo& response_info, 204 const SSLConfig& used_ssl_config, 205 const ProxyInfo& used_proxy_info, 206 HttpStreamBase* stream) { 207 if (!bound_job_.get()) 208 OrphanJobsExcept(job); 209 else 210 DCHECK(jobs_.empty()); 211 delegate_->OnHttpsProxyTunnelResponse( 212 response_info, used_ssl_config, used_proxy_info, stream); 213 } 214 215 int HttpStreamFactoryImpl::Request::RestartTunnelWithProxyAuth( 216 const AuthCredentials& credentials) { 217 DCHECK(bound_job_.get()); 218 return bound_job_->RestartTunnelWithProxyAuth(credentials); 219 } 220 221 void HttpStreamFactoryImpl::Request::SetPriority(RequestPriority priority) { 222 for (std::set<HttpStreamFactoryImpl::Job*>::const_iterator it = jobs_.begin(); 223 it != jobs_.end(); ++it) { 224 (*it)->SetPriority(priority); 225 } 226 if (bound_job_) 227 bound_job_->SetPriority(priority); 228 } 229 230 LoadState HttpStreamFactoryImpl::Request::GetLoadState() const { 231 if (bound_job_.get()) 232 return bound_job_->GetLoadState(); 233 DCHECK(!jobs_.empty()); 234 235 // Just pick the first one. 236 return (*jobs_.begin())->GetLoadState(); 237 } 238 239 bool HttpStreamFactoryImpl::Request::was_npn_negotiated() const { 240 DCHECK(completed_); 241 return was_npn_negotiated_; 242 } 243 244 NextProto HttpStreamFactoryImpl::Request::protocol_negotiated() 245 const { 246 DCHECK(completed_); 247 return protocol_negotiated_; 248 } 249 250 bool HttpStreamFactoryImpl::Request::using_spdy() const { 251 DCHECK(completed_); 252 return using_spdy_; 253 } 254 255 void 256 HttpStreamFactoryImpl::Request::RemoveRequestFromSpdySessionRequestMap() { 257 if (spdy_session_key_.get()) { 258 SpdySessionRequestMap& spdy_session_request_map = 259 factory_->spdy_session_request_map_; 260 DCHECK(ContainsKey(spdy_session_request_map, *spdy_session_key_)); 261 RequestSet& request_set = 262 spdy_session_request_map[*spdy_session_key_]; 263 DCHECK(ContainsKey(request_set, this)); 264 request_set.erase(this); 265 if (request_set.empty()) 266 spdy_session_request_map.erase(*spdy_session_key_); 267 spdy_session_key_.reset(); 268 } 269 } 270 271 void 272 HttpStreamFactoryImpl::Request::RemoveRequestFromHttpPipeliningRequestMap() { 273 if (http_pipelining_key_.get()) { 274 HttpPipeliningRequestMap& http_pipelining_request_map = 275 factory_->http_pipelining_request_map_; 276 DCHECK(ContainsKey(http_pipelining_request_map, *http_pipelining_key_)); 277 RequestVector& request_vector = 278 http_pipelining_request_map[*http_pipelining_key_]; 279 for (RequestVector::iterator it = request_vector.begin(); 280 it != request_vector.end(); ++it) { 281 if (*it == this) { 282 request_vector.erase(it); 283 break; 284 } 285 } 286 if (request_vector.empty()) 287 http_pipelining_request_map.erase(*http_pipelining_key_); 288 http_pipelining_key_.reset(); 289 } 290 } 291 292 void HttpStreamFactoryImpl::Request::OnNewSpdySessionReady( 293 Job* job, 294 const base::WeakPtr<SpdySession>& spdy_session, 295 bool direct) { 296 DCHECK(job); 297 DCHECK(job->using_spdy()); 298 299 // The first case is the usual case. 300 if (!bound_job_.get()) { 301 OrphanJobsExcept(job); 302 } else { // This is the case for HTTPS proxy tunneling. 303 DCHECK_EQ(bound_job_.get(), job); 304 DCHECK(jobs_.empty()); 305 } 306 307 // Cache these values in case the job gets deleted. 308 const SSLConfig used_ssl_config = job->server_ssl_config(); 309 const ProxyInfo used_proxy_info = job->proxy_info(); 310 const bool was_npn_negotiated = job->was_npn_negotiated(); 311 const NextProto protocol_negotiated = 312 job->protocol_negotiated(); 313 const bool using_spdy = job->using_spdy(); 314 const BoundNetLog net_log = job->net_log(); 315 316 Complete(was_npn_negotiated, protocol_negotiated, using_spdy, net_log); 317 318 // Cache this so we can still use it if the request is deleted. 319 HttpStreamFactoryImpl* factory = factory_; 320 if (factory->for_websockets_) { 321 DCHECK(websocket_handshake_stream_create_helper_); 322 bool use_relative_url = direct || url().SchemeIs("wss"); 323 delegate_->OnWebSocketHandshakeStreamReady( 324 job->server_ssl_config(), 325 job->proxy_info(), 326 websocket_handshake_stream_create_helper_->CreateSpdyStream( 327 spdy_session, use_relative_url)); 328 } else { 329 bool use_relative_url = direct || url().SchemeIs("https"); 330 delegate_->OnStreamReady( 331 job->server_ssl_config(), 332 job->proxy_info(), 333 new SpdyHttpStream(spdy_session, use_relative_url)); 334 } 335 // |this| may be deleted after this point. 336 factory->OnNewSpdySessionReady(spdy_session, 337 direct, 338 used_ssl_config, 339 used_proxy_info, 340 was_npn_negotiated, 341 protocol_negotiated, 342 using_spdy, 343 net_log); 344 } 345 346 void HttpStreamFactoryImpl::Request::OrphanJobsExcept(Job* job) { 347 DCHECK(job); 348 DCHECK(!bound_job_.get()); 349 DCHECK(ContainsKey(jobs_, job)); 350 bound_job_.reset(job); 351 jobs_.erase(job); 352 factory_->request_map_.erase(job); 353 354 OrphanJobs(); 355 } 356 357 void HttpStreamFactoryImpl::Request::OrphanJobs() { 358 RemoveRequestFromSpdySessionRequestMap(); 359 RemoveRequestFromHttpPipeliningRequestMap(); 360 361 std::set<Job*> tmp; 362 tmp.swap(jobs_); 363 364 for (std::set<Job*>::iterator it = tmp.begin(); it != tmp.end(); ++it) 365 factory_->OrphanJob(*it, this); 366 } 367 368 void HttpStreamFactoryImpl::Request::OnJobSucceeded(Job* job) { 369 // |job| should only be NULL if we're being serviced by a late bound 370 // SpdySession or HttpPipelinedConnection (one that was not created by a job 371 // in our |jobs_| set). 372 if (!job) { 373 DCHECK(!bound_job_.get()); 374 DCHECK(!jobs_.empty()); 375 // NOTE(willchan): We do *NOT* call OrphanJobs() here. The reason is because 376 // we *WANT* to cancel the unnecessary Jobs from other requests if another 377 // Job completes first. 378 // TODO(mbelshe): Revisit this when we implement ip connection pooling of 379 // SpdySessions. Do we want to orphan the jobs for a different hostname so 380 // they complete? Or do we want to prevent connecting a new SpdySession if 381 // we've already got one available for a different hostname where the ip 382 // address matches up? 383 } else if (!bound_job_.get()) { 384 // We may have other jobs in |jobs_|. For example, if we start multiple jobs 385 // for Alternate-Protocol. 386 OrphanJobsExcept(job); 387 } else { 388 DCHECK(jobs_.empty()); 389 } 390 } 391 392 } // namespace net 393