| // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/http/http_stream_factory_impl_job_controller.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/scoped_mock_time_message_loop_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "net/base/test_proxy_delegate.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/http/http_basic_stream.h" |
| #include "net/http/http_stream_factory_impl_request.h" |
| #include "net/http/http_stream_factory_test_util.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/proxy/mock_proxy_resolver.h" |
| #include "net/proxy/proxy_config_service_fixed.h" |
| #include "net/proxy/proxy_info.h" |
| #include "net/proxy/proxy_service.h" |
| #include "net/quic/test_tools/quic_stream_factory_peer.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gmock_mutant.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Invoke; |
| |
| namespace net { |
| |
| namespace { |
| |
| void DeleteHttpStreamPointer(const SSLConfig& used_ssl_config, |
| const ProxyInfo& used_proxy_info, |
| HttpStream* stream) { |
| delete stream; |
| } |
| |
| class FailingProxyResolverFactory : public ProxyResolverFactory { |
| public: |
| FailingProxyResolverFactory() : ProxyResolverFactory(false) {} |
| |
| // ProxyResolverFactory override. |
| int CreateProxyResolver( |
| const scoped_refptr<ProxyResolverScriptData>& script_data, |
| std::unique_ptr<ProxyResolver>* result, |
| const CompletionCallback& callback, |
| std::unique_ptr<Request>* request) override { |
| return ERR_PAC_SCRIPT_FAILED; |
| } |
| }; |
| |
| class FailingHostResolver : public MockHostResolverBase { |
| public: |
| FailingHostResolver() : MockHostResolverBase(false /*use_caching*/) {} |
| ~FailingHostResolver() override {} |
| |
| int Resolve(const RequestInfo& info, |
| RequestPriority priority, |
| AddressList* addresses, |
| const CompletionCallback& callback, |
| std::unique_ptr<Request>* out_req, |
| const NetLogWithSource& net_log) override { |
| return ERR_NAME_NOT_RESOLVED; |
| } |
| }; |
| |
| class HangingResolver : public MockHostResolverBase { |
| public: |
| HangingResolver() : MockHostResolverBase(false /*use_caching*/) {} |
| ~HangingResolver() override {} |
| |
| int Resolve(const RequestInfo& info, |
| RequestPriority priority, |
| AddressList* addresses, |
| const CompletionCallback& callback, |
| std::unique_ptr<Request>* out_req, |
| const NetLogWithSource& net_log) override { |
| return ERR_IO_PENDING; |
| } |
| }; |
| |
| // A mock HttpServerProperties that always returns false for IsInitialized(). |
| class MockHttpServerProperties : public HttpServerPropertiesImpl { |
| public: |
| MockHttpServerProperties() {} |
| ~MockHttpServerProperties() override {} |
| bool IsInitialized() const override { return false; } |
| }; |
| |
| } // anonymous namespace |
| |
| class HttpStreamFactoryImplJobPeer { |
| public: |
| static void Start(HttpStreamFactoryImpl::Job* job, |
| HttpStreamRequest::StreamType stream_type) { |
| // Start() is mocked for MockHttpStreamFactoryImplJob. |
| // This is the alternative method to invoke real Start() method on Job. |
| job->stream_type_ = stream_type; |
| job->StartInternal(); |
| } |
| |
| // Returns |num_streams_| of |job|. It should be 0 for non-preconnect Jobs. |
| static int GetNumStreams(const HttpStreamFactoryImpl::Job* job) { |
| return job->num_streams_; |
| } |
| }; |
| |
| class JobControllerPeer { |
| public: |
| static bool main_job_is_blocked( |
| HttpStreamFactoryImpl::JobController* job_controller) { |
| return job_controller->main_job_is_blocked_; |
| } |
| }; |
| |
| class HttpStreamFactoryImplJobControllerTest : public ::testing::Test { |
| public: |
| HttpStreamFactoryImplJobControllerTest() |
| : session_deps_(ProxyService::CreateDirect()) { |
| session_deps_.enable_quic = true; |
| } |
| |
| void Initialize(const HttpRequestInfo& request_info, |
| bool use_alternative_proxy, |
| bool is_preconnect) { |
| std::unique_ptr<TestProxyDelegate> test_proxy_delegate( |
| new TestProxyDelegate()); |
| test_proxy_delegate_ = test_proxy_delegate.get(); |
| |
| test_proxy_delegate->set_alternative_proxy_server( |
| ProxyServer::FromPacString("QUIC myproxy.org:443")); |
| EXPECT_TRUE(test_proxy_delegate->alternative_proxy_server().is_quic()); |
| session_deps_.proxy_delegate = std::move(test_proxy_delegate); |
| |
| if (use_alternative_proxy) { |
| std::unique_ptr<ProxyService> proxy_service = |
| ProxyService::CreateFixedFromPacResult("HTTPS myproxy.org:443"); |
| session_deps_.proxy_service = std::move(proxy_service); |
| } |
| session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| factory_ = |
| static_cast<HttpStreamFactoryImpl*>(session_->http_stream_factory()); |
| job_controller_ = new HttpStreamFactoryImpl::JobController( |
| factory_, &request_delegate_, session_.get(), &job_factory_, |
| request_info, is_preconnect); |
| HttpStreamFactoryImplPeer::AddJobController(factory_, job_controller_); |
| } |
| |
| TestProxyDelegate* test_proxy_delegate() const { |
| return test_proxy_delegate_; |
| } |
| |
| ~HttpStreamFactoryImplJobControllerTest() override {} |
| |
| void SetAlternativeService(const HttpRequestInfo& request_info, |
| AlternativeService alternative_service) { |
| HostPortPair host_port_pair = HostPortPair::FromURL(request_info.url); |
| url::SchemeHostPort server(request_info.url); |
| base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); |
| session_->http_server_properties()->SetAlternativeService( |
| server, alternative_service, expiration); |
| } |
| |
| void VerifyBrokenAlternateProtocolMapping(const HttpRequestInfo& request_info, |
| bool should_mark_broken) { |
| const url::SchemeHostPort server(request_info.url); |
| const AlternativeServiceVector alternative_service_vector = |
| session_->http_server_properties()->GetAlternativeServices(server); |
| EXPECT_EQ(1u, alternative_service_vector.size()); |
| EXPECT_EQ(should_mark_broken, |
| session_->http_server_properties()->IsAlternativeServiceBroken( |
| alternative_service_vector[0])); |
| } |
| |
| // Not owned by |this|. |
| TestProxyDelegate* test_proxy_delegate_; |
| TestJobFactory job_factory_; |
| MockHttpStreamRequestDelegate request_delegate_; |
| SpdySessionDependencies session_deps_; |
| std::unique_ptr<HttpNetworkSession> session_; |
| HttpStreamFactoryImpl* factory_; |
| HttpStreamFactoryImpl::JobController* job_controller_; |
| std::unique_ptr<HttpStreamFactoryImpl::Request> request_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HttpStreamFactoryImplJobControllerTest); |
| }; |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| OnStreamFailedWithNoAlternativeJob) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| |
| // There's no other alternative job. Thus when stream failed, it should |
| // notify Request of the stream failure. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_FAILED, _)).Times(1); |
| job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, |
| SSLConfig()); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| OnStreamReadyWithNoAlternativeJob) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| |
| // There's no other alternative job. Thus when a stream is ready, it should |
| // notify Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| } |
| |
| // Test we cancel Jobs correctly when the Request is explicitly canceled |
| // before any Job is bound to Request. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, CancelJobsBeforeBinding) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset(new ProxyService( |
| base::WrapUnique(new ProxyConfigServiceFixed(proxy_config)), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Reset the Request will cancel all the Jobs since there's no Job determined |
| // to serve Request yet and JobController will notify the factory to delete |
| // itself upon completion. |
| request_.reset(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, OnStreamFailedForBothJobs) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // We have the main job with unknown status when the alternative job is failed |
| // thus should not notify Request of the alternative job's failure. But should |
| // notify the main job to mark the alternative job failed. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED, |
| SSLConfig()); |
| EXPECT_TRUE(!job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()); |
| |
| // The failure of second Job should be reported to Request as there's no more |
| // pending Job to serve the Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(1); |
| job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, |
| SSLConfig()); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| AltJobFailsAfterMainJobSucceeds) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Main job succeeds, starts serving Request and it should report status |
| // to Request. The alternative job will mark the main job complete and gets |
| // orphaned. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| |
| // JobController shouldn't report the status of second job as request |
| // is already successfully served. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED, |
| SSLConfig()); |
| |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| // Reset the request as it's been successfully served. |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Tests that if alt job succeeds and main job is blocked, main job should be |
| // cancelled immediately. |request_| completion will clean up the JobController. |
| // Regression test for crbug.com/678768. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| AltJobSucceedsMainJobBlockedControllerDestroyed) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // |alternative_job| succeeds and should report status to Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.alternative_job()->SetStream(http_stream); |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Invoke OnRequestComplete() which should delete |job_controller_| from |
| // |factory_|. |
| request_.reset(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| // This fails without the fix for crbug.com/678768. |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Tests that if an orphaned job completes after |request_| is gone, |
| // JobController will be cleaned up. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| OrphanedJobCompletesControllerDestroyed) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| // Hack to use different URL for the main job to help differentiate the proxy |
| // requests. |
| job_factory_.UseDifferentURLForMainJob(GURL("http://www.google.com")); |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // Complete main job now. |
| MockAsyncProxyResolver resolver; |
| proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder( |
| net::OK, &resolver); |
| int main_job_request_id = |
| resolver.pending_jobs()[0]->url().SchemeIs("http") ? 0 : 1; |
| |
| resolver.pending_jobs()[main_job_request_id]->results()->UseNamedProxy( |
| "result1:80"); |
| resolver.pending_jobs()[main_job_request_id]->CompleteNow(net::OK); |
| |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| // Invoke OnRequestComplete() which should not delete |job_controller_| from |
| // |factory_| because alt job is yet to finish. |
| request_.reset(); |
| ASSERT_FALSE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Make |alternative_job| succeed. |
| resolver.pending_jobs()[0]->results()->UseNamedProxy("result1:80"); |
| resolver.pending_jobs()[0]->CompleteNow(net::OK); |
| HttpStream* http_stream2 = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.alternative_job()->SetStream(http_stream2); |
| // This should not call request_delegate_::OnStreamReady. |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| // Make sure that controller does not leak. |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| AltJobSucceedsAfterMainJobFailed) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // |main_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| |
| job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, |
| SSLConfig()); |
| |
| // |alternative_job| succeeds and should report status to Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.alternative_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| MainJobSucceedsAfterAltJobFailed) { |
| base::HistogramTester histogram_tester; |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // |alternative_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED, |
| SSLConfig()); |
| |
| // |main_job| succeeds and should report status to Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| |
| // Verify that the alternate protocol is marked as broken. |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", -ERR_FAILED, |
| 1); |
| } |
| |
| // Verifies that if the alternative job fails due to a connection change event, |
| // then the alternative service is not marked as broken. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| MainJobSucceedsAfterConnectionChanged) { |
| base::HistogramTester histogram_tester; |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| session_deps_.quic_do_not_mark_as_broken_on_network_change = true; |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // |alternative_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), |
| ERR_NETWORK_CHANGED, SSLConfig()); |
| |
| // |main_job| succeeds and should report status to Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| |
| // Verify that the alternate protocol is not marked as broken. |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", |
| -ERR_NETWORK_CHANGED, 1); |
| } |
| |
| // Regression test for crbug/621069. |
| // Get load state after main job fails and before alternative job succeeds. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, GetLoadStateAfterMainJobFailed) { |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset(new ProxyService( |
| base::WrapUnique(new ProxyConfigServiceFixed(proxy_config)), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // |main_job| fails but should not report status to Request. |
| // The alternative job will mark the main job complete. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| |
| job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, |
| SSLConfig()); |
| |
| // Controller should use alternative job to get load state. |
| job_controller_->GetLoadState(); |
| |
| // |alternative_job| succeeds and should report status to Request. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.alternative_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, DoNotResumeMainJobBeforeWait) { |
| // Use failing ProxyResolverFactory which is unable to create ProxyResolver |
| // to stall the alternative job and report to controller to maybe resume the |
| // main job. |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| proxy_config.set_pac_mandatory(true); |
| session_deps_.proxy_service.reset(new ProxyService( |
| base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(new FailingProxyResolverFactory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Wait until OnStreamFailedCallback is executed on the alternative job. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(1); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, InvalidPortForQuic) { |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| // Using a restricted port 101 for QUIC should fail and the alternative job |
| // should post OnStreamFailedCall on the controller to resume the main job. |
| Initialize(request_info, false, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 101); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| |
| EXPECT_TRUE(job_factory_.main_job()->is_waiting()); |
| |
| // Wait until OnStreamFailedCallback is executed on the alternative job. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| NoAvailableSpdySessionToResumeMainJob) { |
| // Test the alternative job is not resumed when the alternative job is |
| // IO_PENDING for proxy resolution. Once all the proxy resolution succeeds, |
| // the latter part of this test tests controller resumes the main job |
| // when there's no SPDY session for the alternative job. |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HangingResolver* host_resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(host_resolver); |
| session_deps_.host_resolver->set_synchronous_mode(false); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| // Set a SPDY alternative service for the server. |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| // Hack to use different URL for the main job to help differentiate the proxy |
| // requests. |
| job_factory_.UseDifferentURLForMainJob(GURL("http://www.google.com")); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| // Both jobs should be created but stalled as proxy resolution not completed. |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| MockAsyncProxyResolver resolver; |
| proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder( |
| net::OK, &resolver); |
| |
| // Resolve proxy for the main job which then proceed to wait for the |
| // alternative job which is IO_PENDING. |
| int main_job_request_id = |
| resolver.pending_jobs()[0]->url().SchemeIs("http") ? 0 : 1; |
| |
| resolver.pending_jobs()[main_job_request_id]->results()->UseNamedProxy( |
| "result1:80"); |
| resolver.pending_jobs()[main_job_request_id]->CompleteNow(net::OK); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| // Resolve proxy for the alternative job to proceed to create a connection. |
| // Use hanging HostResolver to fail creation of a SPDY session for the |
| // alternative job. The alternative job will be IO_PENDING thus should resume |
| // the main job. |
| resolver.pending_jobs()[0]->CompleteNow(net::OK); |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| NoAvailableQuicSessionToResumeMainJob) { |
| // Use failing HostResolver which is unable to resolve the host name for QUIC. |
| // No QUIC session is created and thus should resume the main job. |
| FailingHostResolver* host_resolver = new FailingHostResolver(); |
| session_deps_.host_resolver.reset(host_resolver); |
| |
| ProxyConfig proxy_config; |
| proxy_config.set_auto_detect(true); |
| // Use asynchronous proxy resolver. |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| session_deps_.proxy_service.reset( |
| new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| // Hack to use different URL for the main job to help differentiate the proxy |
| // requests. |
| job_factory_.UseDifferentURLForMainJob(GURL("http://www.google.com")); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| MockAsyncProxyResolver resolver; |
| proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder( |
| net::OK, &resolver); |
| |
| // Resolve proxy for the main job which then proceed to wait for the |
| // alternative job which is IO_PENDING. |
| int main_job_request_id = |
| resolver.pending_jobs()[0]->url().SchemeIs("http") ? 0 : 1; |
| |
| resolver.pending_jobs()[main_job_request_id]->results()->UseNamedProxy( |
| "result1:80"); |
| resolver.pending_jobs()[main_job_request_id]->CompleteNow(net::OK); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| // Resolve proxy for the alternative job to proceed to create a connection. |
| // Use failing HostResolver to fail creation of a QUIC session for the |
| // alternative job. The alternative job will thus resume the main job. |
| resolver.pending_jobs()[0]->results()->UseNamedProxy("result1:80"); |
| resolver.pending_jobs()[0]->CompleteNow(net::OK); |
| |
| // Wait until OnStreamFailedCallback is executed on the alternative job. |
| // Request shouldn't be notified as the main job is still pending status. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, DelayedTCP) { |
| base::ScopedMockTimeMessageLoopTaskRunner test_task_runner; |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromMicroseconds(10); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("https://www.google.com")), stats1); |
| |
| // Set a SPDY alternative service for the server. |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| // The alternative job stalls as host resolution hangs when creating the QUIC |
| // request and controller should resume the main job after delay. |
| EXPECT_TRUE(test_task_runner->HasPendingTask()); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| test_task_runner->FastForwardBy(base::TimeDelta::FromMicroseconds(15)); |
| EXPECT_FALSE(test_task_runner->HasPendingTask()); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // |alternative_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| // OnStreamFailed will post a task to resume the main job immediately but |
| // won't call Resume() on the main job since it's been resumed already. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), |
| ERR_NETWORK_CHANGED, SSLConfig()); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| test_task_runner->FastForwardUntilNoTasksRemain(); |
| } |
| |
| // Test that main job is blocked for kMaxDelayTimeForMainJob(3s) if |
| // http_server_properties cached an inappropriate large srtt for the server, |
| // which would potentially delay the main job for a extremely long time in |
| // delayed tcp case. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, DelayedTCPWithLargeSrtt) { |
| // Overrides the main thread's message loop with a mock tick clock so that we |
| // could verify the main job is resumed with appropriate delay. |
| base::ScopedMockTimeMessageLoopTaskRunner test_task_runner; |
| // The max delay time should be in sync with .cc file. |
| base::TimeDelta kMaxDelayTimeForMainJob = base::TimeDelta::FromSeconds(3); |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| // Enable delayed TCP and set a extremely large time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromSeconds(100); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("https://www.google.com")), stats1); |
| |
| // Set a SPDY alternative service for the server. |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| // The alternative job stalls as host resolution hangs when creating the QUIC |
| // request and controller should resume the main job after delay. |
| EXPECT_TRUE(test_task_runner->HasPendingTask()); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| // Move forward the task runner with kMaxDelayTimeForMainJob and verify the |
| // main job is resumed. |
| test_task_runner->FastForwardBy(kMaxDelayTimeForMainJob); |
| EXPECT_FALSE(test_task_runner->HasPendingTask()); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| ResumeMainJobImmediatelyOnStreamFailed) { |
| // Overrides the main thread's message loop with a mock tick clock so that we |
| // could verify the main job is resumed with appropriate delay. |
| base::ScopedMockTimeMessageLoopTaskRunner test_task_runner; |
| |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info, false, false); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromMicroseconds(10); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("https://www.google.com")), stats1); |
| |
| // Set a SPDY alternative service for the server. |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // The alternative job stalls as host resolution hangs when creating the QUIC |
| // request and controller should resume the main job with delay. |
| // OnStreamFailed should resume the main job immediately. |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| EXPECT_TRUE(test_task_runner->HasPendingTask()); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| |
| // |alternative_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), |
| ERR_NETWORK_CHANGED, SSLConfig()); |
| EXPECT_EQ(2u, test_task_runner->GetPendingTaskCount()); |
| |
| // Verify the main job will be resumed immediately. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| // Execute tasks that have no remaining delay. Tasks with nonzero delay will |
| // remain queued. |
| test_task_runner->RunUntilIdle(); |
| |
| // Verify there is another task to resume main job with delay but should |
| // not call Resume() on the main job as main job has been resumed. |
| EXPECT_TRUE(test_task_runner->HasPendingTask()); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| test_task_runner->FastForwardBy(base::TimeDelta::FromMicroseconds(15)); |
| EXPECT_FALSE(test_task_runner->HasPendingTask()); |
| } |
| |
| // Verifies that the alternative proxy server job is not created if the URL |
| // scheme is HTTPS. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, HttpsURL) { |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://mail.example.org/"); |
| Initialize(request_info, false, false); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->main_job()->is_waiting()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0, test_proxy_delegate()->get_alternative_proxy_invocations()); |
| } |
| |
| // Verifies that the alternative proxy server job is not created if the main job |
| // does not fetch the resource through a proxy. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, HttpURLWithNoProxy) { |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| |
| Initialize(request_info, false, false); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->main_job()->is_waiting()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(0, test_proxy_delegate()->get_alternative_proxy_invocations()); |
| } |
| |
| // Verifies that the main job is resumed properly after a delay when the |
| // alternative proxy server job hangs. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, DelayedTCPAlternativeProxy) { |
| // Overrides the main thread's message loop with a mock tick clock so that we |
| // could verify the main job is resumed with appropriate delay. |
| base::ScopedMockTimeMessageLoopTaskRunner test_task_runner; |
| |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| HangingResolver* resolver = new HangingResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| Initialize(request_info, true, false); |
| |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromMicroseconds(10); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("https://myproxy.org")), stats1); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // Alternative proxy server job will start in the next message loop. |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| // Run tasks with no remaining delay, this will start the alternative proxy |
| // server job. The alternative proxy server job stalls when connecting to the |
| // alternative proxy server, and should schedule a task to resume the main job |
| // after delay. That task will be queued. |
| test_task_runner->RunUntilIdle(); |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| |
| // Move forward the delay and verify the main job is resumed. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| test_task_runner->FastForwardBy(base::TimeDelta::FromMicroseconds(15)); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| test_task_runner->RunUntilIdle(); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid()); |
| EXPECT_EQ(1, test_proxy_delegate()->get_alternative_proxy_invocations()); |
| EXPECT_FALSE(test_task_runner->HasPendingTask()); |
| } |
| |
| // Verifies that the alternative proxy server job fails immediately, and the |
| // main job is not blocked. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, FailAlternativeProxy) { |
| base::ScopedMockTimeMessageLoopTaskRunner test_task_runner; |
| // Using failing resolver will cause the alternative job to fail. |
| FailingHostResolver* resolver = new FailingHostResolver(); |
| session_deps_.host_resolver.reset(resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| Initialize(request_info, true, false); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromMicroseconds(300 * 1000); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("https://myproxy.org")), stats1); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| EXPECT_EQ(1u, test_task_runner->GetPendingTaskCount()); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, _)).Times(0); |
| |
| // Since the alternative proxy server job is started in the next message loop, |
| // the main job would remain blocked until the alternative proxy starts, and |
| // fails. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| |
| // Run tasks with no remaining delay. |
| test_task_runner->RunUntilIdle(); |
| |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| // Since the main job did not complete successfully, the alternative proxy |
| // server should not be marked as bad. |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid()); |
| EXPECT_EQ(1, test_proxy_delegate()->get_alternative_proxy_invocations()); |
| EXPECT_FALSE(test_task_runner->HasPendingTask()); |
| } |
| |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| AlternativeProxyServerJobFailsAfterMainJobSucceeds) { |
| base::HistogramTester histogram_tester; |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| Initialize(request_info, true, false); |
| |
| url::SchemeHostPort server(request_info.url); |
| |
| request_.reset( |
| job_controller_->Start(request_info, &request_delegate_, nullptr, |
| NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY, SSLConfig(), SSLConfig())); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Main job succeeds, starts serving Request and it should report status |
| // to Request. The alternative job will mark the main job complete and gets |
| // orphaned. |
| HttpStream* http_stream = |
| new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false); |
| job_factory_.main_job()->SetStream(http_stream); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream)) |
| .WillOnce(Invoke(DeleteHttpStreamPointer)); |
| job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig()); |
| |
| // JobController shouldn't report the status of alternative server job as |
| // request is already successfully served. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED, |
| SSLConfig()); |
| |
| // Reset the request as it's been successfully served. |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| |
| histogram_tester.ExpectUniqueSample("Net.QuicAlternativeProxy.Usage", |
| 2 /* ALTERNATIVE_PROXY_USAGE_LOST_RACE */, |
| 1); |
| } |
| |
| // When preconnect to a H2 supported server, only 1 connection is opened. |
| TEST_F(HttpStreamFactoryImplJobControllerTest, |
| PreconnectMultipleStreamsToH2Server) { |
| MockRead reads[] = {MockRead(ASYNC, OK)}; |
| SequencedSocketData data(reads, arraysize(reads), nullptr, 0); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| Initialize(request_info, false, /*is_preconnect=*/true); |
| |
| url::SchemeHostPort server(request_info.url); |
| |
| // Sets server support Http/2. |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| job_controller_->Preconnect(/*num_streams=*/5, request_info, SSLConfig(), |
| SSLConfig()); |
| // Only one job is started. |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| // There is only 1 connect even though multiple streams were requested. |
| EXPECT_EQ(1, HttpStreamFactoryImplJobPeer::GetNumStreams( |
| job_controller_->main_job())); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| class HttpStreamFactoryImplJobControllerPreconnectTest |
| : public HttpStreamFactoryImplJobControllerTest, |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| void SetUp() override { |
| if (GetParam()) { |
| scoped_feature_list_.InitFromCommandLine("LimitEarlyPreconnects", |
| std::string()); |
| } |
| } |
| |
| void Initialize() { |
| session_deps_.http_server_properties = |
| base::MakeUnique<MockHttpServerProperties>(); |
| session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| factory_ = |
| static_cast<HttpStreamFactoryImpl*>(session_->http_stream_factory()); |
| request_info_.method = "GET"; |
| request_info_.url = GURL("https://www.example.com"); |
| job_controller_ = new HttpStreamFactoryImpl::JobController( |
| factory_, &request_delegate_, session_.get(), &job_factory_, |
| request_info_, true); |
| HttpStreamFactoryImplPeer::AddJobController(factory_, job_controller_); |
| } |
| |
| protected: |
| void Preconnect(int num_streams) { |
| job_controller_->Preconnect(num_streams, request_info_, SSLConfig(), |
| SSLConfig()); |
| // Only one job is started. |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| HttpRequestInfo request_info_; |
| }; |
| |
| INSTANTIATE_TEST_CASE_P( |
| /* no prefix */, |
| HttpStreamFactoryImplJobControllerPreconnectTest, |
| ::testing::Bool()); |
| |
| TEST_P(HttpStreamFactoryImplJobControllerPreconnectTest, |
| LimitEarlyPreconnects) { |
| std::vector<std::unique_ptr<SequencedSocketData>> providers; |
| std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_providers; |
| const int kNumPreconects = 5; |
| MockRead reads[] = {MockRead(ASYNC, OK)}; |
| // If experiment is not enabled, there are 5 socket connects. |
| const size_t actual_num_connects = GetParam() ? 1 : kNumPreconects; |
| for (size_t i = 0; i < actual_num_connects; ++i) { |
| auto data = base::MakeUnique<SequencedSocketData>(reads, arraysize(reads), |
| nullptr, 0); |
| auto ssl_data = base::MakeUnique<SSLSocketDataProvider>(ASYNC, OK); |
| session_deps_.socket_factory->AddSocketDataProvider(data.get()); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(ssl_data.get()); |
| providers.push_back(std::move(data)); |
| ssl_providers.push_back(std::move(ssl_data)); |
| } |
| Initialize(); |
| Preconnect(kNumPreconects); |
| // If experiment is enabled, only 1 stream is requested. |
| EXPECT_EQ( |
| (int)actual_num_connects, |
| HttpStreamFactoryImplJobPeer::GetNumStreams(job_controller_->main_job())); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| } // namespace net |