| // 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_job_controller.h" |
| |
| #include <algorithm> |
| #include <list> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "net/base/completion_once_callback.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_network_session_peer.h" |
| #include "net/http/http_stream_factory.h" |
| #include "net/http/http_stream_factory_job.h" |
| #include "net/http/http_stream_factory_test_util.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_entry.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/proxy_resolution/mock_proxy_resolver.h" |
| #include "net/proxy_resolution/proxy_config_service_fixed.h" |
| #include "net/proxy_resolution/proxy_info.h" |
| #include "net/proxy_resolution/proxy_resolution_service.h" |
| #include "net/quic/mock_crypto_client_stream_factory.h" |
| #include "net/quic/mock_quic_data.h" |
| #include "net/quic/quic_stream_factory.h" |
| #include "net/quic/quic_stream_factory_peer.h" |
| #include "net/quic/quic_test_packet_maker.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "net/test/test_with_scoped_task_environment.h" |
| #include "net/third_party/quic/core/quic_utils.h" |
| #include "net/third_party/quic/test_tools/mock_random.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gmock_mutant.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Contains; |
| using ::testing::ElementsAre; |
| using ::testing::Invoke; |
| using ::testing::IsEmpty; |
| using ::testing::Key; |
| using ::testing::SizeIs; |
| |
| namespace net { |
| |
| namespace test { |
| #if !defined(QUIC_DISABLED_FOR_STARBOARD) |
| |
| namespace { |
| |
| const char kServerHostname[] = "www.example.com"; |
| |
| // List of errors for which fallback is expected on an HTTPS proxy. |
| const int proxy_test_mock_errors[] = { |
| ERR_PROXY_CONNECTION_FAILED, |
| ERR_NAME_NOT_RESOLVED, |
| ERR_ADDRESS_UNREACHABLE, |
| ERR_CONNECTION_CLOSED, |
| ERR_CONNECTION_TIMED_OUT, |
| ERR_CONNECTION_RESET, |
| ERR_CONNECTION_REFUSED, |
| ERR_CONNECTION_ABORTED, |
| ERR_TIMED_OUT, |
| ERR_SOCKS_CONNECTION_FAILED, |
| ERR_PROXY_CERTIFICATE_INVALID, |
| ERR_SSL_PROTOCOL_ERROR, |
| }; |
| |
| class FailingProxyResolverFactory : public ProxyResolverFactory { |
| public: |
| FailingProxyResolverFactory() : ProxyResolverFactory(false) {} |
| |
| // ProxyResolverFactory override. |
| int CreateProxyResolver(const scoped_refptr<PacFileData>& script_data, |
| std::unique_ptr<ProxyResolver>* result, |
| CompletionOnceCallback callback, |
| std::unique_ptr<Request>* request) override { |
| return ERR_PAC_SCRIPT_FAILED; |
| } |
| }; |
| |
| class FailingHostResolver : public MockHostResolverBase { |
| public: |
| FailingHostResolver() : MockHostResolverBase(false /*use_caching*/) {} |
| ~FailingHostResolver() override = default; |
| |
| int Resolve(const RequestInfo& info, |
| RequestPriority priority, |
| AddressList* addresses, |
| CompletionOnceCallback callback, |
| std::unique_ptr<Request>* out_req, |
| const NetLogWithSource& net_log) override { |
| return ERR_NAME_NOT_RESOLVED; |
| } |
| }; |
| |
| // TODO(xunjieli): This should just use HangingHostResolver from |
| // mock_host_resolver.h |
| class HangingResolver : public MockHostResolverBase { |
| public: |
| HangingResolver() : MockHostResolverBase(false /*use_caching*/) {} |
| ~HangingResolver() override = default; |
| |
| int Resolve(const RequestInfo& info, |
| RequestPriority priority, |
| AddressList* addresses, |
| CompletionOnceCallback 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() = default; |
| ~MockHttpServerProperties() override = default; |
| bool IsInitialized() const override { return false; } |
| }; |
| |
| } // anonymous namespace |
| |
| class HttpStreamFactoryJobPeer { |
| public: |
| static void Start(HttpStreamFactory::Job* job, |
| HttpStreamRequest::StreamType stream_type) { |
| // Start() is mocked for MockHttpStreamFactoryJob. |
| // 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 HttpStreamFactory::Job* job) { |
| return job->num_streams_; |
| } |
| |
| // Return SpdySessionKey of |job|. |
| static const SpdySessionKey GetSpdySessionKey( |
| const HttpStreamFactory::Job* job) { |
| return job->spdy_session_key_; |
| } |
| |
| static void SetShouldReconsiderProxy(HttpStreamFactory::Job* job) { |
| job->should_reconsider_proxy_ = true; |
| } |
| |
| static void SetStream(HttpStreamFactory::Job* job, |
| std::unique_ptr<HttpStream> http_stream) { |
| job->stream_ = std::move(http_stream); |
| } |
| |
| #if !defined(QUIC_DISABLED_FOR_STARBOARD) |
| static void SetQuicConnectionFailedOnDefaultNetwork( |
| HttpStreamFactory::Job* job) { |
| job->quic_request_.OnConnectionFailedOnDefaultNetwork(); |
| } |
| #endif |
| }; |
| |
| class JobControllerPeer { |
| public: |
| static bool main_job_is_blocked( |
| HttpStreamFactory::JobController* job_controller) { |
| return job_controller->main_job_is_blocked_; |
| } |
| |
| static bool main_job_is_resumed( |
| HttpStreamFactory::JobController* job_controller) { |
| return job_controller->main_job_is_resumed_; |
| } |
| |
| static AlternativeServiceInfo GetAlternativeServiceInfoFor( |
| HttpStreamFactory::JobController* job_controller, |
| const HttpRequestInfo& request_info, |
| HttpStreamRequest::Delegate* delegate, |
| HttpStreamRequest::StreamType stream_type) { |
| return job_controller->GetAlternativeServiceInfoFor(request_info, delegate, |
| stream_type); |
| } |
| |
| static void SetAltJobFailedOnDefaultNetwork( |
| HttpStreamFactory::JobController* job_controller) { |
| DCHECK(job_controller->alternative_job() != nullptr); |
| #if !defined(QUIC_DISABLED_FOR_STARBOARD) |
| HttpStreamFactoryJobPeer::SetQuicConnectionFailedOnDefaultNetwork( |
| job_controller->alternative_job_.get()); |
| #endif |
| } |
| }; |
| |
| class HttpStreamFactoryJobControllerTest |
| : public TestWithScopedTaskEnvironment { |
| public: |
| HttpStreamFactoryJobControllerTest() |
| : TestWithScopedTaskEnvironment( |
| base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME) { |
| session_deps_.enable_quic = true; |
| session_deps_.host_resolver->set_synchronous_mode(true); |
| } |
| |
| void UseAlternativeProxy() { |
| ASSERT_FALSE(test_proxy_delegate_); |
| use_alternative_proxy_ = true; |
| } |
| |
| void SetPreconnect() { |
| ASSERT_FALSE(test_proxy_delegate_); |
| is_preconnect_ = true; |
| } |
| |
| void DisableIPBasedPooling() { |
| ASSERT_FALSE(test_proxy_delegate_); |
| enable_ip_based_pooling_ = false; |
| } |
| |
| void DisableAlternativeServices() { |
| ASSERT_FALSE(test_proxy_delegate_); |
| enable_alternative_services_ = false; |
| } |
| |
| void SkipCreatingJobController() { |
| ASSERT_FALSE(job_controller_); |
| create_job_controller_ = false; |
| } |
| |
| void Initialize(const HttpRequestInfo& request_info) { |
| ASSERT_FALSE(test_proxy_delegate_); |
| test_proxy_delegate_ = std::make_unique<TestProxyDelegate>(); |
| test_proxy_delegate_->set_alternative_proxy_server( |
| ProxyServer::FromPacString("QUIC myproxy.org:443")); |
| EXPECT_TRUE(test_proxy_delegate_->alternative_proxy_server().is_quic()); |
| |
| if (quic_data_) |
| quic_data_->AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| if (tcp_data_) |
| session_deps_.socket_factory->AddSocketDataProvider(tcp_data_.get()); |
| |
| if (use_alternative_proxy_) { |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "HTTPS myproxy.org:443", TRAFFIC_ANNOTATION_FOR_TESTS); |
| session_deps_.proxy_resolution_service = |
| std::move(proxy_resolution_service); |
| } |
| |
| session_deps_.proxy_resolution_service->SetProxyDelegate( |
| test_proxy_delegate_.get()); |
| |
| session_deps_.net_log = net_log_.bound().net_log(); |
| HttpNetworkSession::Params params = |
| SpdySessionDependencies::CreateSessionParams(&session_deps_); |
| HttpNetworkSession::Context session_context = |
| SpdySessionDependencies::CreateSessionContext(&session_deps_); |
| |
| session_context.quic_crypto_client_stream_factory = |
| &crypto_client_stream_factory_; |
| session_context.quic_random = &random_generator_; |
| session_ = std::make_unique<HttpNetworkSession>(params, session_context); |
| factory_ = static_cast<HttpStreamFactory*>(session_->http_stream_factory()); |
| if (create_job_controller_) { |
| job_controller_ = new HttpStreamFactory::JobController( |
| factory_, &request_delegate_, session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); |
| } |
| } |
| |
| TestProxyDelegate* test_proxy_delegate() const { |
| return test_proxy_delegate_.get(); |
| } |
| |
| ~HttpStreamFactoryJobControllerTest() override { |
| if (quic_data_) { |
| EXPECT_TRUE(quic_data_->AllReadDataConsumed()); |
| EXPECT_TRUE(quic_data_->AllWriteDataConsumed()); |
| } |
| if (tcp_data_) { |
| EXPECT_TRUE(tcp_data_->AllReadDataConsumed()); |
| EXPECT_TRUE(tcp_data_->AllWriteDataConsumed()); |
| } |
| } |
| |
| void SetAlternativeService(const HttpRequestInfo& request_info, |
| AlternativeService alternative_service) { |
| url::SchemeHostPort server(request_info.url); |
| base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); |
| if (alternative_service.protocol == kProtoQUIC) { |
| session_->http_server_properties()->SetQuicAlternativeService( |
| server, alternative_service, expiration, |
| session_->params().quic_supported_versions); |
| } else { |
| session_->http_server_properties()->SetHttp2AlternativeService( |
| server, alternative_service, expiration); |
| } |
| } |
| |
| void VerifyBrokenAlternateProtocolMapping(const HttpRequestInfo& request_info, |
| bool should_mark_broken) { |
| const url::SchemeHostPort server(request_info.url); |
| const AlternativeServiceInfoVector alternative_service_info_vector = |
| session_->http_server_properties()->GetAlternativeServiceInfos(server); |
| EXPECT_EQ(1u, alternative_service_info_vector.size()); |
| EXPECT_EQ(should_mark_broken, |
| session_->http_server_properties()->IsAlternativeServiceBroken( |
| alternative_service_info_vector[0].alternative_service())); |
| } |
| |
| void TestAltJobSucceedsAfterMainJobFailed( |
| bool alt_job_retried_on_non_default_network); |
| void TestMainJobSucceedsAfterAltJobFailed( |
| bool alt_job_retried_on_non_default_network); |
| void TestAltJobSucceedsAfterMainJobSucceeded( |
| bool alt_job_retried_on_non_default_network); |
| void TestOnStreamFailedForBothJobs( |
| bool alt_job_retried_on_non_default_network); |
| void TestAltJobFailsAfterMainJobSucceeded( |
| bool alt_job_retried_on_non_default_network); |
| void TestMainJobSucceedsAfterAltJobSucceeded( |
| bool alt_job_retried_on_non_default_network); |
| void TestMainJobFailsAfterAltJobSucceeded( |
| bool alt_job_retried_on_non_default_network); |
| |
| TestJobFactory job_factory_; |
| MockHttpStreamRequestDelegate request_delegate_; |
| SpdySessionDependencies session_deps_{ProxyResolutionService::CreateDirect()}; |
| std::unique_ptr<HttpNetworkSession> session_; |
| HttpStreamFactory* factory_ = nullptr; |
| HttpStreamFactory::JobController* job_controller_ = nullptr; |
| std::unique_ptr<HttpStreamRequest> request_; |
| std::unique_ptr<SequencedSocketData> tcp_data_; |
| std::unique_ptr<MockQuicData> quic_data_; |
| MockCryptoClientStreamFactory crypto_client_stream_factory_; |
| quic::MockClock clock_; |
| quic::test::MockRandom random_generator_{0}; |
| QuicTestPacketMaker client_maker_{ |
| HttpNetworkSession::Params().quic_supported_versions[0], |
| #if defined(COBALT_QUIC46) |
| quic::QuicUtils::CreateRandomConnectionId(&random_generator_), |
| #else |
| 0, |
| #endif |
| &clock_, |
| kServerHostname, |
| quic::Perspective::IS_CLIENT, |
| false}; |
| |
| protected: |
| BoundTestNetLog net_log_; |
| bool use_alternative_proxy_ = false; |
| bool is_preconnect_ = false; |
| bool enable_ip_based_pooling_ = true; |
| bool enable_alternative_services_ = true; |
| |
| private: |
| std::unique_ptr<TestProxyDelegate> test_proxy_delegate_; |
| bool create_job_controller_ = true; |
| |
| DISALLOW_COPY_AND_ASSIGN(HttpStreamFactoryJobControllerTest); |
| }; |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsSync) { |
| ProxyConfig proxy_config; |
| proxy_config.set_pac_url(GURL("http://fooproxyurl")); |
| proxy_config.set_pac_mandatory(true); |
| session_deps_.proxy_resolution_service.reset(new ProxyResolutionService( |
| std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation( |
| proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)), |
| std::make_unique<FailingProxyResolverFactory>(), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info); |
| |
| EXPECT_CALL(request_delegate_, |
| OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _)) |
| .Times(1); |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| // Make sure calling GetLoadState() when before job creation does not crash. |
| // Regression test for crbug.com/723920. |
| EXPECT_EQ(LOAD_STATE_IDLE, job_controller_->GetLoadState()); |
| |
| base::RunLoop().RunUntilIdle(); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsAsync) { |
| ProxyConfig proxy_config; |
| proxy_config.set_pac_url(GURL("http://fooproxyurl")); |
| proxy_config.set_pac_mandatory(true); |
| MockAsyncProxyResolverFactory* proxy_resolver_factory = |
| new MockAsyncProxyResolverFactory(false); |
| MockAsyncProxyResolver resolver; |
| session_deps_.proxy_resolution_service.reset(new ProxyResolutionService( |
| std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation( |
| proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)), |
| base::WrapUnique(proxy_resolver_factory), nullptr)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, |
| job_controller_->GetLoadState()); |
| |
| EXPECT_CALL(request_delegate_, |
| OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _)) |
| .Times(1); |
| proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder( |
| ERR_FAILED, &resolver); |
| base::RunLoop().RunUntilIdle(); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, NoSupportedProxies) { |
| session_deps_.proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "QUIC myproxy.org:443", TRAFFIC_ANNOTATION_FOR_TESTS); |
| session_deps_.enable_quic = false; |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info); |
| |
| EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_NO_SUPPORTED_PROXIES, _, _)) |
| .Times(1); |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| base::RunLoop().RunUntilIdle(); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| class JobControllerReconsiderProxyAfterErrorTest |
| : public HttpStreamFactoryJobControllerTest, |
| public ::testing::WithParamInterface<::testing::tuple<bool, int>> { |
| public: |
| void Initialize( |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service) { |
| session_deps_.proxy_resolution_service = |
| std::move(proxy_resolution_service); |
| session_ = std::make_unique<HttpNetworkSession>( |
| SpdySessionDependencies::CreateSessionParams(&session_deps_), |
| SpdySessionDependencies::CreateSessionContext(&session_deps_)); |
| factory_ = session_->http_stream_factory(); |
| } |
| |
| std::unique_ptr<HttpStreamRequest> CreateJobController( |
| const HttpRequestInfo& request_info) { |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, &request_delegate_, session_.get(), &default_job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| return job_controller->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, |
| DEFAULT_PRIORITY); |
| } |
| |
| private: |
| // Use real Jobs so that Job::Resume() is not mocked out. When main job is |
| // resumed it will use mock socket data. |
| HttpStreamFactory::JobFactory default_job_factory_; |
| }; |
| |
| INSTANTIATE_TEST_CASE_P( |
| /* no prefix */, |
| JobControllerReconsiderProxyAfterErrorTest, |
| ::testing::Combine(::testing::Bool(), |
| testing::ValuesIn(proxy_test_mock_errors))); |
| |
| // TODO(eroman): The testing should be expanded to test cases where proxy |
| // fallback is NOT supposed to occur, and also vary across all of |
| // the proxy types. |
| TEST_P(JobControllerReconsiderProxyAfterErrorTest, ReconsiderProxyAfterError) { |
| // Use mock proxy client sockets to test the fallback behavior of error codes |
| // returned by HttpProxyClientSocketWrapper. Errors returned by transport |
| // sockets usually get re-written by the wrapper class. crbug.com/826570. |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| |
| const bool set_alternative_proxy_server = ::testing::get<0>(GetParam()); |
| const int mock_error = ::testing::get<1>(GetParam()); |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "HTTPS badproxy:99; HTTPS badfallbackproxy:98; DIRECT", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| auto test_proxy_delegate = std::make_unique<TestProxyDelegate>(); |
| TestProxyDelegate* test_proxy_delegate_raw = test_proxy_delegate.get(); |
| |
| // Before starting the test, verify that there are no proxies marked as bad. |
| ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()) |
| << mock_error; |
| |
| // Alternative Proxy job is given preference over the main job, so populate |
| // the socket provider first. |
| StaticSocketDataProvider socket_data_proxy_alternate_job; |
| if (set_alternative_proxy_server) { |
| // Mock data for QUIC proxy socket. |
| socket_data_proxy_alternate_job.set_connect_data( |
| MockConnect(ASYNC, mock_error)); |
| session_deps_.socket_factory->AddSocketDataProvider( |
| &socket_data_proxy_alternate_job); |
| test_proxy_delegate->set_alternative_proxy_server( |
| ProxyServer::FromPacString("QUIC badproxy:99")); |
| } |
| |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ProxyClientSocketDataProvider proxy_data(ASYNC, mock_error); |
| |
| StaticSocketDataProvider socket_data_proxy_main_job; |
| socket_data_proxy_main_job.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider( |
| &socket_data_proxy_main_job); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| // When retrying the job using the second proxy (badfallback:98), |
| // alternative job must not be created. So, socket data for only the |
| // main job is needed. |
| StaticSocketDataProvider socket_data_proxy_main_job_2; |
| socket_data_proxy_main_job_2.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider( |
| &socket_data_proxy_main_job_2); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| // First request would use DIRECT, and succeed. |
| StaticSocketDataProvider socket_data_direct_first_request; |
| socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider( |
| &socket_data_direct_first_request); |
| |
| // Second request would use DIRECT, and succeed. |
| StaticSocketDataProvider socket_data_direct_second_request; |
| socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider( |
| &socket_data_direct_second_request); |
| |
| // Now request a stream. It should succeed using the DIRECT. |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| |
| proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get()); |
| Initialize(std::move(proxy_resolution_service)); |
| EXPECT_EQ(set_alternative_proxy_server, |
| test_proxy_delegate_raw->alternative_proxy_server().is_quic()); |
| |
| // Start two requests. The first request should consume data from |
| // |socket_data_proxy_main_job|, |
| // |socket_data_proxy_alternate_job| and |
| // |socket_data_direct_first_request|. The second request should consume |
| // data from |socket_data_direct_second_request|. |
| |
| for (size_t i = 0; i < 2; ++i) { |
| ProxyInfo used_proxy_info; |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)) |
| .Times(1) |
| .WillOnce(::testing::SaveArg<1>(&used_proxy_info)); |
| |
| std::unique_ptr<HttpStreamRequest> request = |
| CreateJobController(request_info); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that request was fetched without proxy. |
| EXPECT_TRUE(used_proxy_info.is_direct()); |
| |
| // The proxies that failed should now be known to the proxy service as |
| // bad. |
| const ProxyRetryInfoMap& retry_info = |
| session_->proxy_resolution_service()->proxy_retry_info(); |
| EXPECT_THAT(retry_info, SizeIs(set_alternative_proxy_server ? 3 : 2)); |
| EXPECT_THAT(retry_info, Contains(Key("https://badproxy:99"))); |
| EXPECT_THAT(retry_info, Contains(Key("https://badfallbackproxy:98"))); |
| |
| if (set_alternative_proxy_server) |
| EXPECT_THAT(retry_info, Contains(Key("quic://badproxy:99"))); |
| } |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Tests that ERR_MSG_TOO_BIG is retryable for QUIC proxy. |
| TEST_F(JobControllerReconsiderProxyAfterErrorTest, ReconsiderErrMsgTooBig) { |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "QUIC badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| // Before starting the test, verify that there are no proxies marked as bad. |
| ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()); |
| |
| // Mock data for the QUIC proxy socket. |
| StaticSocketDataProvider quic_proxy_socket; |
| quic_proxy_socket.set_connect_data(MockConnect(ASYNC, ERR_MSG_TOO_BIG)); |
| session_deps_.socket_factory->AddSocketDataProvider(&quic_proxy_socket); |
| |
| // Mock data for DIRECT. |
| StaticSocketDataProvider socket_data_direct; |
| socket_data_direct.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&socket_data_direct); |
| |
| // Now request a stream. It should fallback to DIRECT on ERR_MSG_TOO_BIG. |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| |
| Initialize(std::move(proxy_resolution_service)); |
| |
| ProxyInfo used_proxy_info; |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)) |
| .Times(1) |
| .WillOnce(::testing::SaveArg<1>(&used_proxy_info)); |
| |
| std::unique_ptr<HttpStreamRequest> request = |
| CreateJobController(request_info); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(used_proxy_info.is_direct()); |
| const ProxyRetryInfoMap& retry_info = |
| session_->proxy_resolution_service()->proxy_retry_info(); |
| EXPECT_THAT(retry_info, SizeIs(1)); |
| EXPECT_THAT(retry_info, Contains(Key("quic://badproxy:99"))); |
| |
| request.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Same as test above except that this is testing the retry behavior for |
| // non-QUIC proxy on ERR_MSG_TOO_BIG. |
| TEST_F(JobControllerReconsiderProxyAfterErrorTest, |
| DoNotReconsiderErrMsgTooBig) { |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "HTTPS badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| // Before starting the test, verify that there are no proxies marked as bad. |
| ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()); |
| |
| // Mock data for the HTTPS proxy socket. |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ProxyClientSocketDataProvider proxy_data(ASYNC, ERR_MSG_TOO_BIG); |
| StaticSocketDataProvider https_proxy_socket; |
| https_proxy_socket.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&https_proxy_socket); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| // Now request a stream. It should not fallback to DIRECT on ERR_MSG_TOO_BIG. |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| |
| Initialize(std::move(proxy_resolution_service)); |
| |
| ProxyInfo used_proxy_info; |
| EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_MSG_TOO_BIG, _, _)) |
| .Times(1); |
| |
| std::unique_ptr<HttpStreamRequest> request = |
| CreateJobController(request_info); |
| base::RunLoop().RunUntilIdle(); |
| |
| const ProxyRetryInfoMap& retry_info = |
| session_->proxy_resolution_service()->proxy_retry_info(); |
| EXPECT_THAT(retry_info, SizeIs(0)); |
| |
| request.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Tests that the main (HTTP) job is started after the alternative |
| // proxy server job has failed. There are 3 jobs in total that are run |
| // in the following sequence: alternative proxy server job, |
| // delayed HTTP job with the first proxy server, HTTP job with |
| // the second proxy configuration. The result of the last job (OK) |
| // should be returned to the delegate. |
| TEST_F(JobControllerReconsiderProxyAfterErrorTest, |
| SecondMainJobIsStartedAfterAltProxyServerJobFailed) { |
| // Configure the proxies and initialize the test. |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateFixedFromPacResult( |
| "HTTPS myproxy.org:443; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| auto test_proxy_delegate = std::make_unique<TestProxyDelegate>(); |
| test_proxy_delegate->set_alternative_proxy_server( |
| ProxyServer::FromPacString("QUIC myproxy.org:443")); |
| |
| proxy_resolution_service->SetProxyDelegate(test_proxy_delegate.get()); |
| Initialize(std::move(proxy_resolution_service)); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| quic_stream_factory->set_require_confirmation(false); |
| ServerNetworkStats stats1; |
| stats1.srtt = base::TimeDelta::FromSeconds(100); |
| session_->http_server_properties()->SetServerNetworkStats( |
| url::SchemeHostPort(GURL("http://www.example.com")), stats1); |
| |
| // Prepare the mocked data. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_QUIC_PROTOCOL_ERROR); |
| quic_data.AddWrite(ASYNC, OK); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| StaticSocketDataProvider tcp_data_1; |
| tcp_data_1.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); |
| session_deps_.socket_factory->AddSocketDataProvider(&tcp_data_1); |
| |
| StaticSocketDataProvider tcp_data_2; |
| tcp_data_2.set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&tcp_data_2); |
| SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| // Create a request. |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| AlternativeService alternative_service(kProtoQUIC, "www.example.com", 80); |
| SetAlternativeService(request_info, alternative_service); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)).Times(1); |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); |
| |
| // Create the job controller. |
| std::unique_ptr<HttpStreamRequest> request = |
| CreateJobController(request_info); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(quic_data.AllReadDataConsumed()); |
| EXPECT_TRUE(quic_data.AllWriteDataConsumed()); |
| EXPECT_TRUE(tcp_data_1.AllReadDataConsumed()); |
| EXPECT_TRUE(tcp_data_1.AllWriteDataConsumed()); |
| EXPECT_TRUE(tcp_data_2.AllReadDataConsumed()); |
| EXPECT_TRUE(tcp_data_2.AllWriteDataConsumed()); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, OnStreamFailedWithNoAlternativeJob) { |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_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); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, OnStreamReadyWithNoAlternativeJob) { |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| |
| Initialize(request_info); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| // There's no other alternative job. Thus when a stream is ready, it should |
| // notify Request. |
| EXPECT_TRUE(job_controller_->main_job()); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Test we cancel Jobs correctly when the Request is explicitly canceled |
| // before any Job is bound to Request. |
| TEST_F(HttpStreamFactoryJobControllerTest, CancelJobsBeforeBinding) { |
| // Use COLD_START to make the alt job pending. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, OK); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Test that the controller does not create alternative job when the advertised |
| // versions in AlternativeServiceInfo do not contain any version that is |
| // supported. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| DoNotCreateAltJobIfQuicVersionsUnsupported) { |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); |
| session_->http_server_properties()->SetQuicAlternativeService( |
| server, alternative_service, expiration, |
| {quic::QUIC_VERSION_UNSUPPORTED}); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| request_.reset(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| void HttpStreamFactoryJobControllerTest::TestOnStreamFailedForBothJobs( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(ASYNC, ERR_FAILED); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| |
| // 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); |
| base::RunLoop().RunUntilIdle(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // This test verifies that the alternative service is not marked broken if both |
| // jobs fail, and the alternative job is not retried on the alternate network. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| OnStreamFailedForBothJobsWithoutQuicRetry) { |
| TestOnStreamFailedForBothJobs(false); |
| } |
| |
| // This test verifies that the alternative service is not marked broken if both |
| // jobs fail, and the alternative job is retried on the alternate network. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| OnStreamFailedForBothJobsWithQuicRetriedOnAlternateNetwork) { |
| TestOnStreamFailedForBothJobs(true); |
| } |
| |
| void HttpStreamFactoryJobControllerTest::TestAltJobFailsAfterMainJobSucceeded( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(ASYNC, ERR_FAILED); |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| |
| // 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. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| // JobController shouldn't report the status of second job as request |
| // is already successfully served. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Reset the request as it's been successfully served. |
| request_.reset(); |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| |
| // Verify the brokenness is not cleared when the default network changes. |
| session_->http_server_properties()->OnDefaultNetworkChanged(); |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| } |
| |
| // This test verifies that the alternatvie service is marked broken when the |
| // alternative job fails on default after the main job succeeded. The |
| // brokenness should not be cleared when the default network changes. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobFailsOnDefaultNetworkAfterMainJobSucceeded) { |
| TestAltJobFailsAfterMainJobSucceeded(false); |
| } |
| |
| // This test verifies that the alternatvie service is marked broken when the |
| // alternative job fails on both networks after the main job succeeded. The |
| // brokenness should not be cleared when the default network changes. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobFailsOnBothNetworksAfterMainJobSucceeded) { |
| TestAltJobFailsAfterMainJobSucceeded(true); |
| } |
| |
| // Tests that when alt job succeeds, main job is destroyed. |
| TEST_F(HttpStreamFactoryJobControllerTest, AltJobSucceedsMainJobDestroyed) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| request_.reset(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| EXPECT_TRUE(HttpStreamFactoryPeer::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(HttpStreamFactoryJobControllerTest, |
| AltJobSucceedsMainJobBlockedControllerDestroyed) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddWrite(SYNCHRONOUS, |
| client_maker_.MakeInitialSettingsPacket(1, nullptr)); |
| quic_data_->AddRead(ASYNC, OK); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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_delegate_|. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| 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(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| SpdySessionKeyHasOriginHostPortPair) { |
| session_deps_.enable_http2_alternative_service = true; |
| |
| const char origin_host[] = "www.example.org"; |
| const uint16_t origin_port = 443; |
| const char alternative_host[] = "mail.example.org"; |
| const uint16_t alternative_port = 123; |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = |
| GURL(base::StringPrintf("https://%s:%u", origin_host, origin_port)); |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoHTTP2, alternative_host, |
| alternative_port); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| HostPortPair main_host_port_pair = |
| HttpStreamFactoryJobPeer::GetSpdySessionKey(job_controller_->main_job()) |
| .host_port_pair(); |
| EXPECT_EQ(origin_host, main_host_port_pair.host()); |
| EXPECT_EQ(origin_port, main_host_port_pair.port()); |
| |
| HostPortPair alternative_host_port_pair = |
| HttpStreamFactoryJobPeer::GetSpdySessionKey( |
| job_controller_->alternative_job()) |
| .host_port_pair(); |
| EXPECT_EQ(origin_host, alternative_host_port_pair.host()); |
| EXPECT_EQ(origin_port, alternative_host_port_pair.port()); |
| } |
| |
| // Tests that if an orphaned job completes after |request_| is gone, |
| // JobController will be cleaned up. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| OrphanedJobCompletesControllerDestroyed) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| // main job should not be blocked because alt job returned ERR_IO_PENDING. |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| // Complete main job now. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Invoke OnRequestComplete() which should not delete |job_controller_| from |
| // |factory_| because alt job is yet to finish. |
| request_.reset(); |
| ASSERT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| EXPECT_FALSE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| // This should not call request_delegate_::OnStreamReady. |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| // Make sure that controller does not leak. |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| void HttpStreamFactoryJobControllerTest::TestAltJobSucceedsAfterMainJobFailed( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| // One failed TCP connect. |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_FAILED)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // |main_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| base::RunLoop().RunUntilIdle(); |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| // |alternative_job| succeeds and should report status to Request. |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // This test verifies that the alternative service is not mark broken if the |
| // alternative job succeeds on the default network after the main job failed. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobSucceedsOnDefaultNetworkAfterMainJobFailed) { |
| TestAltJobSucceedsAfterMainJobFailed(false); |
| } |
| |
| // This test verifies that the alternative service is not mark broken if the |
| // alternative job succeeds on the alternate network after the main job failed. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobSucceedsOnAlternateNetwrokAfterMainJobFailed) { |
| TestAltJobSucceedsAfterMainJobFailed(true); |
| } |
| |
| void HttpStreamFactoryJobControllerTest:: |
| TestAltJobSucceedsAfterMainJobSucceeded( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // |main_job| fails but should not report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Run the message loop to make |main_job| succeed and status will be |
| // reported to Request. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| base::RunLoop().RunUntilIdle(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| request_.reset(); |
| // If alt job was retried on the alternate network, the alternative service |
| // should be marked broken until the default network changes. |
| VerifyBrokenAlternateProtocolMapping(request_info, |
| alt_job_retried_on_non_default_network); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| if (alt_job_retried_on_non_default_network) { |
| // Verify the brokenness is cleared when the default network changes. |
| session_->http_server_properties()->OnDefaultNetworkChanged(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| } |
| } |
| |
| // This test verifies that the alternative service is not marked broken if the |
| // alternative job succeeds on the default network after the main job succeeded. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobSucceedsOnDefaultNetworkAfterMainJobSucceeded) { |
| TestAltJobSucceedsAfterMainJobSucceeded(false); |
| } |
| |
| // This test verifies that the alternative service is marked broken until the |
| // default network changes if the alternative job succeeds on the non-default |
| // network, which failed on the default network previously, after the main job |
| // succeeded. The brokenness should be cleared when the default network |
| // changes. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AltJobSucceedsOnAlternateNetworkAfterMainJobSucceeded) { |
| TestAltJobSucceedsAfterMainJobSucceeded(true); |
| } |
| |
| void HttpStreamFactoryJobControllerTest:: |
| TestMainJobSucceedsAfterAltJobSucceeded( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| // Run message loop to make the main job succeed. |
| base::RunLoop().RunUntilIdle(); |
| // If alt job was retried on the alternate network, the alternative service |
| // should be marked broken until the default network changes. |
| VerifyBrokenAlternateProtocolMapping(request_info, |
| alt_job_retried_on_non_default_network); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| if (alt_job_retried_on_non_default_network) { |
| // Verify the brokenness is cleared when the default network changes. |
| session_->http_server_properties()->OnDefaultNetworkChanged(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| } |
| } |
| |
| // This test verifies that the alternative service is not marked broken if the |
| // main job succeeds after the alternative job succeeded on the default network. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobSucceedsAfterAltJobSucceededOnDefaultNetwork) { |
| TestMainJobSucceedsAfterAltJobSucceeded(false); |
| } |
| |
| // This test verifies that the alternative service is marked broken until the |
| // default network changes if the main job succeeds after the alternative job |
| // succeeded on the non-default network, i.e., failed on the default network |
| // previously. The brokenness should be cleared when the default network |
| // changes. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobSucceedsAfterAltJobSucceededOnAlternateNetwork) { |
| TestAltJobSucceedsAfterMainJobSucceeded(true); |
| } |
| |
| void HttpStreamFactoryJobControllerTest::TestMainJobFailsAfterAltJobSucceeded( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| // Use cold start and complete alt job manually. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| // Make |alternative_job| succeed. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| // Run message loop to make the main job fail. |
| base::RunLoop().RunUntilIdle(); |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // This test verifies that the alternative service is not marked broken if the |
| // main job fails after the alternative job succeeded on the default network. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobFailsAfterAltJobSucceededOnDefaultNetwork) { |
| TestMainJobFailsAfterAltJobSucceeded(false); |
| } |
| |
| // This test verifies that the alternative service is not marked broken if the |
| // main job fails after the alternative job succeeded on the non-default |
| // network, i.e., failed on the default network previously. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobFailsAfterAltJobSucceededOnAlternateNetwork) { |
| TestMainJobFailsAfterAltJobSucceeded(true); |
| } |
| |
| void HttpStreamFactoryJobControllerTest::TestMainJobSucceedsAfterAltJobFailed( |
| bool alt_job_retried_on_non_default_network) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(SYNCHRONOUS, ERR_FAILED); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| base::HistogramTester histogram_tester; |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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); |
| // |main_job| succeeds and should report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| if (alt_job_retried_on_non_default_network) { |
| // Set the alt job as if it failed on the default network and is retired on |
| // the alternate network. |
| JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| request_.reset(); |
| // Verify that the alternate protocol is marked as broken. |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", -ERR_FAILED, |
| 1); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| // Verify the brokenness is not cleared when the default network changes. |
| session_->http_server_properties()->OnDefaultNetworkChanged(); |
| VerifyBrokenAlternateProtocolMapping(request_info, true); |
| } |
| |
| // This test verifies that the alternative service will be marked broken when |
| // the alternative job fails on the default network and main job succeeds later. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobSucceedsAfterAltJobFailedOnDefaultNetwork) { |
| TestMainJobSucceedsAfterAltJobFailed(false); |
| } |
| |
| // This test verifies that the alternative service will be marked broken when |
| // the alternative job fails on both default and alternate networks and main job |
| // succeeds later. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobSucceedsAfterAltJobFailedOnBothNetworks) { |
| TestMainJobSucceedsAfterAltJobFailed(true); |
| } |
| |
| // Verifies that if the alternative job fails due to a connection change event, |
| // then the alternative service is not marked as broken. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| MainJobSucceedsAfterConnectionChanged) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(SYNCHRONOUS, ERR_NETWORK_CHANGED); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| base::HistogramTester histogram_tester; |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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); |
| // |main_job| succeeds and should report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| base::RunLoop().RunUntilIdle(); |
| request_.reset(); |
| |
| // Verify that the alternate protocol is not marked as broken. |
| VerifyBrokenAlternateProtocolMapping(request_info, false); |
| histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", |
| -ERR_NETWORK_CHANGED, 1); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Regression test for crbug/621069. |
| // Get load state after main job fails and before alternative job succeeds. |
| TEST_F(HttpStreamFactoryJobControllerTest, GetLoadStateAfterMainJobFailed) { |
| // Use COLD_START to complete alt job manually. |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // Controller should use alternative job to get load state. |
| job_controller_->GetLoadState(); |
| |
| // |alternative_job| succeeds and should report status to Request. |
| auto http_stream = std::make_unique<HttpBasicStream>( |
| std::make_unique<ClientSocketHandle>(), false, false); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); |
| |
| HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), |
| std::move(http_stream)); |
| job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); |
| |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobWhenAltJobStalls) { |
| // Use COLD_START to stall alt job. |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // Alt job is stalled and main job should complete successfully. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 101); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| 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(); |
| } |
| |
| // Verifies that the main job is not resumed until after the alt job completes |
| // host resolution. |
| TEST_F(HttpStreamFactoryJobControllerTest, HostResolutionHang) { |
| auto hanging_resolver = std::make_unique<MockHostResolver>(); |
| hanging_resolver->set_ondemand_mode(true); |
| session_deps_.host_resolver = std::move(hanging_resolver); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| // handshake will fail asynchronously after mock data is unpaused. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause |
| quic_data.AddRead(ASYNC, ERR_FAILED); |
| quic_data.AddWrite(ASYNC, ERR_FAILED); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // This prevents handshake from immediately succeeding. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // Since the alt job has not finished host resolution, there should be no |
| // delayed task posted to resume the main job. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(50)); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| |
| // Allow alt job host resolution to complete. |
| session_deps_.host_resolver->ResolveAllPending(); |
| |
| // Task to resume main job in 15 microseconds should be posted. |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(14)); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(1)); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Unpause mock quic data. |
| // Will cause |alternative_job| to fail, but its failure should not be |
| // reported to Request. |
| EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); |
| // 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); |
| quic_data.GetSequencedSocketData()->Resume(); |
| FastForwardUntilNoTasksRemain(); |
| // Alt job should be cleaned up |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCP) { |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| // Handshake will fail asynchronously after mock data is unpaused. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause |
| quic_data.AddRead(ASYNC, ERR_FAILED); |
| quic_data.AddWrite(ASYNC, ERR_FAILED); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // This prevents handshake from immediately succeeding. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| // Main job is not blocked but hasn't resumed yet; it should resume in 15us. |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Task to resume main job in 15us should be posted. |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(14)); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(1)); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Unpause mock quic data and run all remaining tasks. Alt-job should fail |
| // and be cleaned up. |
| quic_data.GetSequencedSocketData()->Resume(); |
| FastForwardUntilNoTasksRemain(); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| // Regression test for crbug.com/789560. |
| TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobLaterCanceled) { |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service = |
| ProxyResolutionService::CreateDirect(); |
| ProxyResolutionService* proxy_resolution_service_raw = |
| proxy_resolution_service.get(); |
| session_deps_.proxy_resolution_service = std::move(proxy_resolution_service); |
| |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| session_deps_.alternate_host_resolver = |
| std::make_unique<HangingHostResolver>(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| |
| base::RunLoop run_loop; |
| // The main job should be resumed without delay when alt job fails. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()) |
| .Times(1) |
| .WillOnce(Invoke([&run_loop]() { run_loop.Quit(); })); |
| job_controller_->OnStreamFailed(job_factory_.alternative_job(), |
| ERR_QUIC_PROTOCOL_ERROR, SSLConfig()); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(0)); |
| run_loop.Run(); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| // Calling ForceReloadProxyConfig will cause the proxy configuration to |
| // change. It will still be the direct connection but the configuration |
| // version will be bumped. That is enough for the job controller to restart |
| // the jobs. |
| proxy_resolution_service_raw->ForceReloadProxyConfig(); |
| HttpStreamFactoryJobPeer::SetShouldReconsiderProxy(job_factory_.main_job()); |
| // Now the alt service is marked as broken (e.g. through a different request), |
| // so only non-alt job is restarted. |
| session_->http_server_properties()->MarkAlternativeServiceBroken( |
| alternative_service); |
| |
| job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, |
| SSLConfig()); |
| // Jobs are restarted. |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| // There shouldn't be any ResumeMainJobLater() delayed tasks. |
| // This EXPECT_CALL will fail before crbug.com/789560 fix. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(15)); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| request_.reset(); |
| } |
| |
| // 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(HttpStreamFactoryJobControllerTest, DelayedTCPWithLargeSrtt) { |
| // The max delay time should be in sync with .cc file. |
| base::TimeDelta kMaxDelayTimeForMainJob = base::TimeDelta::FromSeconds(3); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| // handshake will fail asynchronously after mock data is unpaused. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause |
| quic_data.AddRead(ASYNC, ERR_FAILED); |
| quic_data.AddWrite(ASYNC, ERR_FAILED); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // This prevents handshake from immediately succeeding. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| // Main job is not blocked but hasn't resumed yet; it should resume in 3s. |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Task to resume main job in 3 seconds should be posted. |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(kMaxDelayTimeForMainJob - base::TimeDelta::FromMicroseconds(1)); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(1)); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Unpause mock quic data and run all remaining tasks. Alt-job should fail |
| // and be cleaned up. |
| quic_data.GetSequencedSocketData()->Resume(); |
| FastForwardUntilNoTasksRemain(); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| ResumeMainJobImmediatelyOnStreamFailed) { |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| |
| // handshake will fail asynchronously after mock data is unpaused. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause |
| quic_data.AddRead(ASYNC, ERR_FAILED); |
| quic_data.AddWrite(ASYNC, ERR_FAILED); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // This prevents handshake from immediately succeeding. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| // Main job is not blocked but hasn't resumed yet; it's scheduled to resume |
| // in 15us. |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Task to resume main job in 15us should be posted. |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(1)); |
| |
| // Now unpause the mock quic data to fail the alt job. This should immediately |
| // resume the main job. |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| quic_data.GetSequencedSocketData()->Resume(); |
| FastForwardBy(base::TimeDelta()); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // 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(MainThreadHasPendingTask()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(15)); |
| |
| FastForwardUntilNoTasksRemain(); |
| } |
| |
| // Verifies that the alternative proxy server job is not created if the URL |
| // scheme is HTTPS. |
| TEST_F(HttpStreamFactoryJobControllerTest, HttpsURL) { |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| session_deps_.host_resolver = std::make_unique<HangingResolver>(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://mail.example.org/"); |
| Initialize(request_info); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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(); |
| } |
| |
| // Verifies that the alternative proxy server job is not created if the main job |
| // does not fetch the resource through a proxy. |
| TEST_F(HttpStreamFactoryJobControllerTest, HttpURLWithNoProxy) { |
| // Using hanging resolver will cause the alternative job to hang indefinitely. |
| session_deps_.host_resolver = std::make_unique<HangingResolver>(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| |
| Initialize(request_info); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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(); |
| } |
| |
| // Verifies that the main job is resumed properly after a delay when the |
| // alternative proxy server job hangs. |
| TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCPAlternativeProxy) { |
| UseAlternativeProxy(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.mail.example.org/"); |
| |
| Initialize(request_info); |
| |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| |
| // Handshake will fail asynchronously after mock data is unpaused. |
| MockQuicData quic_data; |
| quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause |
| quic_data.AddRead(ASYNC, ERR_FAILED); |
| quic_data.AddWrite(ASYNC, ERR_FAILED); |
| quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // This prevents handshake from immediately succeeding. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()->is_waiting()); |
| // Main job is not blocked but hasn't resumed yet; it should resume in 15us. |
| EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); |
| EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Task to resume main job in 15us should be posted. |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(14)); |
| EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); |
| FastForwardBy(base::TimeDelta::FromMicroseconds(1)); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); |
| |
| // Unpause mock quic data and run all remaining tasks. Alt-job should fail |
| // and be cleaned up. |
| quic_data.GetSequencedSocketData()->Resume(); |
| FastForwardUntilNoTasksRemain(); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| // Verifies that if the alternative proxy server job fails immediately, the |
| // main job is not blocked. |
| TEST_F(HttpStreamFactoryJobControllerTest, FailAlternativeProxy) { |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(SYNCHRONOUS, ERR_FAILED); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| UseAlternativeProxy(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| Initialize(request_info); |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); |
| EXPECT_THAT(session_->proxy_resolution_service()->proxy_retry_info(), |
| IsEmpty()); |
| |
| // Enable delayed TCP and set time delay for waiting job. |
| QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); |
| 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_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()); |
| |
| // The alternative proxy server should be marked as bad. |
| EXPECT_THAT(session_->proxy_resolution_service()->proxy_retry_info(), |
| ElementsAre(Key("quic://myproxy.org:443"))); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Verifies that if the alternative proxy server job fails due to network |
| // disconnection, then the proxy delegate is not notified. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| InternetDisconnectedAlternativeProxy) { |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(SYNCHRONOUS, ERR_INTERNET_DISCONNECTED); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| UseAlternativeProxy(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://mail.example.org/"); |
| Initialize(request_info); |
| 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(); |
| 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_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| EXPECT_TRUE(job_controller_->main_job()); |
| |
| // The alternative proxy server should not be marked as bad. |
| EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid()); |
| request_.reset(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| AlternativeProxyServerJobFailsAfterMainJobSucceeds) { |
| base::HistogramTester histogram_tester; |
| |
| session_deps_.socket_factory->UseMockProxyClientSockets(); |
| ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); |
| |
| // Use COLD_START to make the alt job pending. |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| UseAlternativeProxy(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.google.com"); |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| 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. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| |
| // 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(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| |
| histogram_tester.ExpectUniqueSample("Net.QuicAlternativeProxy.Usage", |
| 2 /* ALTERNATIVE_PROXY_USAGE_LOST_RACE */, |
| 1); |
| } |
| |
| TEST_F(HttpStreamFactoryJobControllerTest, PreconnectToHostWithValidAltSvc) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddWrite(SYNCHRONOUS, |
| client_maker_.MakeInitialSettingsPacket(1, nullptr)); |
| quic_data_->AddRead(ASYNC, OK); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| SetPreconnect(); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| job_controller_->Preconnect(1); |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_EQ(HttpStreamFactory::PRECONNECT, |
| job_controller_->main_job()->job_type()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // When preconnect to a H2 supported server, only 1 connection is opened. |
| TEST_F(HttpStreamFactoryJobControllerTest, |
| PreconnectMultipleStreamsToH2Server) { |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| SetPreconnect(); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("http://www.example.com"); |
| Initialize(request_info); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| job_controller_->Preconnect(/*num_streams=*/5); |
| // Only one job is started. |
| EXPECT_TRUE(job_controller_->main_job()); |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| EXPECT_EQ(HttpStreamFactory::PRECONNECT, |
| job_controller_->main_job()->job_type()); |
| // There is only 1 connect even though multiple streams were requested. |
| EXPECT_EQ( |
| 1, HttpStreamFactoryJobPeer::GetNumStreams(job_controller_->main_job())); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| class JobControllerLimitMultipleH2Requests |
| : public HttpStreamFactoryJobControllerTest { |
| protected: |
| const int kNumRequests = 5; |
| void SetUp() override { SkipCreatingJobController(); } |
| }; |
| |
| TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequests) { |
| // Make sure there is only one socket connect. |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| tcp_data_ = |
| std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>()); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ssl_data.next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| Initialize(request_info); |
| SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); |
| pool_peer.SetEnableSendingInitialData(false); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; |
| std::vector<std::unique_ptr<HttpStreamRequest>> requests; |
| for (int i = 0; i < kNumRequests; ++i) { |
| request_delegates.emplace_back( |
| std::make_unique<MockHttpStreamRequestDelegate>()); |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, request_delegates[i].get(), session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| auto request = job_controller->Start( |
| request_delegates[i].get(), nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_FALSE(job_controller->alternative_job()); |
| requests.push_back(std::move(request)); |
| } |
| |
| for (int i = 0; i < kNumRequests; ++i) { |
| EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); |
| } |
| |
| base::RunLoop().RunUntilIdle(); |
| requests.clear(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| TestNetLogEntry::List entries; |
| size_t log_position = 0; |
| for (int i = 0; i < kNumRequests - 1; ++i) { |
| net_log_.GetEntries(&entries); |
| log_position = ExpectLogContainsSomewhereAfter( |
| entries, log_position, NetLogEventType::HTTP_STREAM_JOB_THROTTLED, |
| NetLogEventPhase::NONE); |
| } |
| } |
| |
| TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequestsFirstRequestHang) { |
| // First socket connect hang. |
| SequencedSocketData hangdata; |
| hangdata.set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); |
| session_deps_.socket_factory->AddSocketDataProvider(&hangdata); |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| std::list<SequencedSocketData> socket_data; |
| std::list<SSLSocketDataProvider> ssl_socket_data; |
| // kNumRequests - 1 will resume themselves after a delay. There will be |
| // kNumRequests - 1 sockets opened. |
| for (int i = 0; i < kNumRequests - 1; i++) { |
| // Only the first one needs a MockRead because subsequent sockets are |
| // not used to establish a SpdySession. |
| if (i == 0) { |
| socket_data.emplace_back(reads, base::span<MockWrite>()); |
| } else { |
| socket_data.emplace_back(); |
| } |
| socket_data.back().set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back()); |
| ssl_socket_data.emplace_back(ASYNC, OK); |
| ssl_socket_data.back().next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider( |
| &ssl_socket_data.back()); |
| } |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| Initialize(request_info); |
| SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); |
| pool_peer.SetEnableSendingInitialData(false); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; |
| std::vector<std::unique_ptr<HttpStreamRequest>> requests; |
| for (int i = 0; i < kNumRequests; ++i) { |
| request_delegates.push_back( |
| std::make_unique<MockHttpStreamRequestDelegate>()); |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, request_delegates[i].get(), session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| auto request = job_controller->Start( |
| request_delegates[i].get(), nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_FALSE(job_controller->alternative_job()); |
| requests.push_back(std::move(request)); |
| } |
| |
| for (int i = 0; i < kNumRequests; ++i) { |
| EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); |
| } |
| |
| EXPECT_TRUE(MainThreadHasPendingTask()); |
| FastForwardBy(base::TimeDelta::FromMilliseconds( |
| HttpStreamFactory::Job::kHTTP2ThrottleMs)); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| requests.clear(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| |
| EXPECT_TRUE(hangdata.AllReadDataConsumed()); |
| for (const auto& data : socket_data) { |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| } |
| |
| TEST_F(JobControllerLimitMultipleH2Requests, |
| MultipleRequestsFirstRequestCanceled) { |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| SequencedSocketData first_socket(reads, base::span<MockWrite>()); |
| first_socket.set_connect_data(MockConnect(ASYNC, OK)); |
| SSLSocketDataProvider first_ssl_data(ASYNC, OK); |
| first_ssl_data.next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSocketDataProvider(&first_socket); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&first_ssl_data); |
| std::list<SequencedSocketData> socket_data; |
| std::list<SSLSocketDataProvider> ssl_socket_data; |
| // kNumRequests - 1 will be resumed when the first request is canceled. |
| for (int i = 0; i < kNumRequests - 1; i++) { |
| socket_data.emplace_back(); |
| socket_data.back().set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back()); |
| ssl_socket_data.emplace_back(ASYNC, OK); |
| ssl_socket_data.back().next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider( |
| &ssl_socket_data.back()); |
| } |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| Initialize(request_info); |
| SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); |
| pool_peer.SetEnableSendingInitialData(false); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; |
| std::vector<std::unique_ptr<HttpStreamRequest>> requests; |
| for (int i = 0; i < kNumRequests; ++i) { |
| request_delegates.emplace_back( |
| std::make_unique<MockHttpStreamRequestDelegate>()); |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, request_delegates[i].get(), session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| auto request = job_controller->Start( |
| request_delegates[i].get(), nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_FALSE(job_controller->alternative_job()); |
| requests.push_back(std::move(request)); |
| } |
| // Cancel the first one. |
| requests[0].reset(); |
| |
| for (int i = 1; i < kNumRequests; ++i) { |
| EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); |
| } |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| requests.clear(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| |
| EXPECT_TRUE(first_socket.AllReadDataConsumed()); |
| for (const auto& data : socket_data) { |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| } |
| |
| TEST_F(JobControllerLimitMultipleH2Requests, MultiplePreconnects) { |
| // Make sure there is only one socket connect. |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ssl_data.next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| SetPreconnect(); |
| Initialize(request_info); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; |
| for (int i = 0; i < kNumRequests; ++i) { |
| request_delegates.emplace_back( |
| std::make_unique<MockHttpStreamRequestDelegate>()); |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, request_delegates[i].get(), session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| job_controller->Preconnect(1); |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_FALSE(job_controller->alternative_job()); |
| } |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| TEST_F(JobControllerLimitMultipleH2Requests, H1NegotiatedForFirstRequest) { |
| // First socket is an HTTP/1.1 socket. |
| SequencedSocketData first_socket; |
| first_socket.set_connect_data(MockConnect(ASYNC, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSocketDataProvider(&first_socket); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| // Second socket is an HTTP/2 socket. |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| SequencedSocketData second_socket(reads, base::span<MockWrite>()); |
| second_socket.set_connect_data(MockConnect(ASYNC, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&second_socket); |
| SSLSocketDataProvider second_ssl_data(ASYNC, OK); |
| second_ssl_data.next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&second_ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.example.com"); |
| Initialize(request_info); |
| SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); |
| pool_peer.SetEnableSendingInitialData(false); |
| |
| // Sets server support HTTP/2. |
| url::SchemeHostPort server(request_info.url); |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; |
| std::vector<std::unique_ptr<HttpStreamRequest>> requests; |
| for (int i = 0; i < 2; ++i) { |
| request_delegates.emplace_back( |
| std::make_unique<MockHttpStreamRequestDelegate>()); |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, request_delegates[i].get(), session_.get(), &job_factory_, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| auto request = job_controller->Start( |
| request_delegates[i].get(), nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_FALSE(job_controller->alternative_job()); |
| requests.push_back(std::move(request)); |
| } |
| |
| for (int i = 0; i < 2; ++i) { |
| EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); |
| } |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| requests.clear(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| |
| EXPECT_TRUE(first_socket.AllReadDataConsumed()); |
| EXPECT_FALSE(second_socket.AllReadDataConsumed()); |
| } |
| |
| // Tests that HTTP/2 throttling logic only applies to non-QUIC jobs. |
| TEST_F(JobControllerLimitMultipleH2Requests, QuicJobNotThrottled) { |
| crypto_client_stream_factory_.set_handshake_mode( |
| MockCryptoClientStream::COLD_START); |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| tcp_data_ = |
| std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>()); |
| |
| tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| ssl_data.next_proto = kProtoHTTP2; |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); |
| pool_peer.SetEnableSendingInitialData(false); |
| |
| url::SchemeHostPort server(request_info.url); |
| // Sets server supports QUIC. |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| // Sets server support HTTP/2. |
| session_->http_server_properties()->SetSupportsSpdy(server, true); |
| |
| // Use default job factory so that Resume() is not mocked out. |
| HttpStreamFactory::JobFactory default_job_factory; |
| HttpStreamFactory::JobController* job_controller = |
| new HttpStreamFactory::JobController( |
| factory_, &request_delegate_, session_.get(), &default_job_factory, |
| request_info, is_preconnect_, false /* is_websocket */, |
| enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), |
| SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller); |
| request_ = |
| job_controller->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| |
| EXPECT_TRUE(job_controller->main_job()); |
| EXPECT_TRUE(job_controller->alternative_job()); |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| base::RunLoop().RunUntilIdle(); |
| TestNetLogEntry::List entries; |
| net_log_.GetEntries(&entries); |
| for (auto entry : entries) { |
| ASSERT_NE(NetLogEventType::HTTP_STREAM_JOB_THROTTLED, entry.type); |
| } |
| } |
| |
| class HttpStreamFactoryJobControllerMisdirectedRequestRetry |
| : public HttpStreamFactoryJobControllerTest, |
| public ::testing::WithParamInterface<::testing::tuple<bool, bool>> {}; |
| |
| INSTANTIATE_TEST_CASE_P( |
| /* no prefix */, |
| HttpStreamFactoryJobControllerMisdirectedRequestRetry, |
| ::testing::Combine(::testing::Bool(), ::testing::Bool())); |
| |
| TEST_P(HttpStreamFactoryJobControllerMisdirectedRequestRetry, |
| DisableIPBasedPoolingAndAlternativeServices) { |
| const bool enable_ip_based_pooling = ::testing::get<0>(GetParam()); |
| const bool enable_alternative_services = ::testing::get<1>(GetParam()); |
| if (enable_alternative_services) { |
| quic_data_ = std::make_unique<MockQuicData>(); |
| quic_data_->AddConnect(SYNCHRONOUS, OK); |
| quic_data_->AddWrite(SYNCHRONOUS, |
| client_maker_.MakeInitialSettingsPacket(1, nullptr)); |
| quic_data_->AddRead(ASYNC, OK); |
| } |
| tcp_data_ = std::make_unique<SequencedSocketData>(); |
| tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); |
| |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| if (!enable_ip_based_pooling) |
| DisableIPBasedPooling(); |
| if (!enable_alternative_services) |
| DisableAlternativeServices(); |
| |
| Initialize(request_info); |
| |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| SetAlternativeService(request_info, alternative_service); |
| |
| request_ = |
| job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), |
| HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); |
| EXPECT_TRUE(job_controller_->main_job()); |
| if (enable_alternative_services) { |
| EXPECT_TRUE(job_controller_->alternative_job()); |
| } else { |
| EXPECT_FALSE(job_controller_->alternative_job()); |
| } |
| |
| // |main_job| succeeds and should report status to Request. |
| EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| class HttpStreamFactoryJobControllerPreconnectTest |
| : public HttpStreamFactoryJobControllerTest, |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| void SetUp() override { |
| if (!GetParam()) { |
| scoped_feature_list_.InitFromCommandLine(std::string(), |
| "LimitEarlyPreconnects"); |
| } |
| } |
| |
| void Initialize() { |
| session_deps_.http_server_properties = |
| std::make_unique<MockHttpServerProperties>(); |
| session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| factory_ = session_->http_stream_factory(); |
| request_info_.method = "GET"; |
| request_info_.url = GURL("https://www.example.com"); |
| job_controller_ = new HttpStreamFactory::JobController( |
| factory_, &request_delegate_, session_.get(), &job_factory_, |
| request_info_, /* is_preconnect = */ true, |
| /* is_websocket = */ false, |
| /* enable_ip_based_pooling = */ true, |
| /* enable_alternative_services = */ true, SSLConfig(), SSLConfig()); |
| HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); |
| } |
| |
| protected: |
| void Preconnect(int num_streams) { |
| job_controller_->Preconnect(num_streams); |
| // 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 */, |
| HttpStreamFactoryJobControllerPreconnectTest, |
| ::testing::Bool()); |
| |
| TEST_P(HttpStreamFactoryJobControllerPreconnectTest, LimitEarlyPreconnects) { |
| std::list<SequencedSocketData> providers; |
| std::list<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) { |
| providers.emplace_back(reads, base::span<MockWrite>()); |
| session_deps_.socket_factory->AddSocketDataProvider(&providers.back()); |
| ssl_providers.emplace_back(ASYNC, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider( |
| &ssl_providers.back()); |
| } |
| Initialize(); |
| Preconnect(kNumPreconects); |
| // If experiment is enabled, only 1 stream is requested. |
| EXPECT_EQ((int)actual_num_connects, HttpStreamFactoryJobPeer::GetNumStreams( |
| job_controller_->main_job())); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); |
| } |
| |
| // Test that GetAlternativeServiceInfoFor will include a list of advertised |
| // versions, which contains a version that is supported. Returns an empty list |
| // if advertised versions are missing in HttpServerProperties. |
| TEST_F(HttpStreamFactoryJobControllerTest, GetAlternativeServiceInfoFor) { |
| HttpRequestInfo request_info; |
| request_info.method = "GET"; |
| request_info.url = GURL("https://www.google.com"); |
| |
| Initialize(request_info); |
| url::SchemeHostPort server(request_info.url); |
| AlternativeService alternative_service(kProtoQUIC, server.host(), 443); |
| base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); |
| |
| // Set alternative service with no advertised version. |
| session_->http_server_properties()->SetQuicAlternativeService( |
| server, alternative_service, expiration, |
| quic::QuicTransportVersionVector()); |
| |
| AlternativeServiceInfo alt_svc_info = |
| JobControllerPeer::GetAlternativeServiceInfoFor( |
| job_controller_, request_info, &request_delegate_, |
| HttpStreamRequest::HTTP_STREAM); |
| // Verify that JobController get an empty list of supported QUIC versions. |
| EXPECT_TRUE(alt_svc_info.advertised_versions().empty()); |
| |
| // Set alternative service for the same server with the same list of versions |
| // that is supported. |
| quic::QuicTransportVersionVector supported_versions = |
| session_->params().quic_supported_versions; |
| ASSERT_TRUE(session_->http_server_properties()->SetQuicAlternativeService( |
| server, alternative_service, expiration, supported_versions)); |
| |
| alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor( |
| job_controller_, request_info, &request_delegate_, |
| HttpStreamRequest::HTTP_STREAM); |
| std::sort(supported_versions.begin(), supported_versions.end()); |
| EXPECT_EQ(supported_versions, alt_svc_info.advertised_versions()); |
| |
| quic::QuicTransportVersion unsupported_version_1( |
| quic::QUIC_VERSION_UNSUPPORTED); |
| quic::QuicTransportVersion unsupported_version_2( |
| quic::QUIC_VERSION_UNSUPPORTED); |
| for (const quic::QuicTransportVersion& version : |
| quic::AllSupportedTransportVersions()) { |
| if (std::find(supported_versions.begin(), supported_versions.end(), |
| version) != supported_versions.end()) |
| continue; |
| if (unsupported_version_1 == quic::QUIC_VERSION_UNSUPPORTED) { |
| unsupported_version_1 = version; |
|