blob: 5ce1453946fb7ef6c04a2e5fa8c84cfbd5070c70 [file] [log] [blame]
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_stream_factory_impl_job_controller.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/histogram_tester.h"
#include "base/threading/platform_thread.h"
#include "net/base/test_proxy_delegate.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_stream_factory_impl_request.h"
#include "net/http/http_stream_factory_test_util.h"
#include "net/log/net_log_with_source.h"
#include "net/proxy/mock_proxy_resolver.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_info.h"
#include "net/proxy/proxy_service.h"
#include "net/quic/test_tools/quic_stream_factory_peer.h"
#include "net/spdy/spdy_test_util_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gmock_mutant.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
namespace net {
namespace {
void DeleteHttpStreamPointer(const SSLConfig& used_ssl_config,
const ProxyInfo& used_proxy_info,
HttpStream* stream) {
delete stream;
}
class FailingProxyResolverFactory : public ProxyResolverFactory {
public:
FailingProxyResolverFactory() : ProxyResolverFactory(false) {}
// ProxyResolverFactory override.
int CreateProxyResolver(
const scoped_refptr<ProxyResolverScriptData>& script_data,
std::unique_ptr<ProxyResolver>* result,
const CompletionCallback& callback,
std::unique_ptr<Request>* request) override {
return ERR_PAC_SCRIPT_FAILED;
}
};
class FailingHostResolver : public MockHostResolverBase {
public:
FailingHostResolver() : MockHostResolverBase(false /*use_caching*/) {}
~FailingHostResolver() override {}
int Resolve(const RequestInfo& info,
RequestPriority priority,
AddressList* addresses,
const CompletionCallback& callback,
std::unique_ptr<Request>* out_req,
const NetLogWithSource& net_log) override {
return ERR_NAME_NOT_RESOLVED;
}
};
class HangingResolver : public MockHostResolverBase {
public:
HangingResolver() : MockHostResolverBase(false /*use_caching*/) {}
~HangingResolver() override {}
int Resolve(const RequestInfo& info,
RequestPriority priority,
AddressList* addresses,
const CompletionCallback& callback,
std::unique_ptr<Request>* out_req,
const NetLogWithSource& net_log) override {
return ERR_IO_PENDING;
}
};
} // anonymous namespace
class HttpStreamFactoryImplJobPeer {
public:
static void Start(HttpStreamFactoryImpl::Job* job,
HttpStreamRequest::StreamType stream_type) {
// Start() is mocked for MockHttpStreamFactoryImplJob.
// This is the alternative method to invoke real Start() method on Job.
job->stream_type_ = stream_type;
job->StartInternal();
}
};
class JobControllerPeer {
public:
static void VerifyWaitingTimeForMainJob(
HttpStreamFactoryImpl::JobController* job_controller,
const base::TimeDelta& delay) {
EXPECT_EQ(delay, job_controller->main_job_wait_time_);
}
static bool main_job_is_blocked(
HttpStreamFactoryImpl::JobController* job_controller) {
return job_controller->main_job_is_blocked_;
}
};
class HttpStreamFactoryImplJobControllerTest
: public ::testing::Test,
public ::testing::WithParamInterface<NextProto> {
public:
HttpStreamFactoryImplJobControllerTest()
: session_deps_(ProxyService::CreateDirect()) {
session_deps_.enable_quic = true;
}
void Initialize(bool use_alternative_proxy) {
std::unique_ptr<TestProxyDelegate> test_proxy_delegate(
new TestProxyDelegate());
test_proxy_delegate_ = test_proxy_delegate.get();
test_proxy_delegate->set_alternative_proxy_server(
ProxyServer::FromPacString("QUIC myproxy.org:443"));
EXPECT_TRUE(test_proxy_delegate->alternative_proxy_server().is_quic());
session_deps_.proxy_delegate.reset(test_proxy_delegate.release());
if (use_alternative_proxy) {
std::unique_ptr<ProxyService> proxy_service =
ProxyService::CreateFixedFromPacResult("HTTPS myproxy.org:443");
session_deps_.proxy_service.reset(proxy_service.release());
}
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
factory_ =
static_cast<HttpStreamFactoryImpl*>(session_->http_stream_factory());
job_controller_ = new HttpStreamFactoryImpl::JobController(
factory_, &request_delegate_, session_.get(), &job_factory_);
HttpStreamFactoryImplPeer::AddJobController(factory_, job_controller_);
}
TestProxyDelegate* test_proxy_delegate() const {
return test_proxy_delegate_;
}
~HttpStreamFactoryImplJobControllerTest() {}
void SetAlternativeService(const HttpRequestInfo& request_info,
AlternativeService alternative_service) {
HostPortPair host_port_pair = HostPortPair::FromURL(request_info.url);
url::SchemeHostPort server(request_info.url);
base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1);
session_->http_server_properties()->SetAlternativeService(
server, alternative_service, expiration);
}
void VerifyBrokenAlternateProtocolMapping(const HttpRequestInfo& request_info,
bool should_mark_broken) {
const url::SchemeHostPort server(request_info.url);
const AlternativeServiceVector alternative_service_vector =
session_->http_server_properties()->GetAlternativeServices(server);
EXPECT_EQ(1u, alternative_service_vector.size());
EXPECT_EQ(should_mark_broken,
session_->http_server_properties()->IsAlternativeServiceBroken(
alternative_service_vector[0]));
}
// Not owned by |this|.
TestProxyDelegate* test_proxy_delegate_;
TestJobFactory job_factory_;
MockHttpStreamRequestDelegate request_delegate_;
SpdySessionDependencies session_deps_;
std::unique_ptr<HttpNetworkSession> session_;
HttpStreamFactoryImpl* factory_;
HttpStreamFactoryImpl::JobController* job_controller_;
std::unique_ptr<HttpStreamFactoryImpl::Request> request_;
DISALLOW_COPY_AND_ASSIGN(HttpStreamFactoryImplJobControllerTest);
};
TEST_F(HttpStreamFactoryImplJobControllerTest,
OnStreamFailedWithNoAlternativeJob) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
// There's no other alternative job. Thus when stream failed, it should
// notify Request of the stream failure.
EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_FAILED, _)).Times(1);
job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED,
SSLConfig());
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
OnStreamReadyWithNoAlternativeJob) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
// There's no other alternative job. Thus when a stream is ready, it should
// notify Request.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.main_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig(),
ProxyInfo());
}
// Test we cancel Jobs correctly when the Request is explicitly canceled
// before any Job is bound to Request.
TEST_F(HttpStreamFactoryImplJobControllerTest, CancelJobsBeforeBinding) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(new ProxyService(
base::WrapUnique(new ProxyConfigServiceFixed(proxy_config)),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Reset the Request will cancel all the Jobs since there's no Job determined
// to serve Request yet and JobController will notify the factory to delete
// itself upon completion.
request_.reset();
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryImplJobControllerTest, OnStreamFailedForBothJobs) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// We have the main job with unknown status when the alternative job is failed
// thus should not notify Request of the alternative job's failure. But should
// notify the main job to mark the alternative job failed.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED,
SSLConfig());
EXPECT_TRUE(!job_controller_->alternative_job());
EXPECT_TRUE(job_controller_->main_job());
// The failure of second Job should be reported to Request as there's no more
// pending Job to serve the Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(1);
job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED,
SSLConfig());
VerifyBrokenAlternateProtocolMapping(request_info, false);
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
AltJobFailsAfterMainJobSucceeds) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Main job succeeds, starts serving Request and it should report status
// to Request. The alternative job will mark the main job complete and gets
// orphaned.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.main_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig(),
ProxyInfo());
// JobController shouldn't report the status of second job as request
// is already successfully served.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED,
SSLConfig());
VerifyBrokenAlternateProtocolMapping(request_info, true);
// Reset the request as it's been successfully served.
request_.reset();
EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
AltJobSucceedsAfterMainJobFailed) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// |main_job| fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED,
SSLConfig());
// |alternative_job| succeeds and should report status to Request.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.alternative_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig(),
ProxyInfo());
VerifyBrokenAlternateProtocolMapping(request_info, false);
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
MainJobSucceedsAfterAltJobFailed) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// |alternative_job| fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED,
SSLConfig());
// |main_job| succeeds and should report status to Request.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.main_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig(),
ProxyInfo());
VerifyBrokenAlternateProtocolMapping(request_info, true);
}
// Regression test for crbug/621069.
// Get load state after main job fails and before alternative job succeeds.
TEST_F(HttpStreamFactoryImplJobControllerTest, GetLoadStateAfterMainJobFailed) {
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(new ProxyService(
base::WrapUnique(new ProxyConfigServiceFixed(proxy_config)),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// |main_job| fails but should not report status to Request.
// The alternative job will mark the main job complete.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED,
SSLConfig());
// Controller should use alternative job to get load state.
job_controller_->GetLoadState();
// |alternative_job| succeeds and should report status to Request.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.alternative_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig(),
ProxyInfo());
}
TEST_F(HttpStreamFactoryImplJobControllerTest, DoNotResumeMainJobBeforeWait) {
// Use failing ProxyResolverFactory which is unable to create ProxyResolver
// to stall the alternative job and report to controller to maybe resume the
// main job.
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
proxy_config.set_pac_mandatory(true);
session_deps_.proxy_service.reset(new ProxyService(
base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(new FailingProxyResolverFactory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Wait until OnStreamFailedCallback is executed on the alternative job.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(1);
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryImplJobControllerTest, InvalidPortForQuic) {
// 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(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 101);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_factory_.main_job()->is_waiting());
// Wait until OnStreamFailedCallback is executed on the alternative job.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
NoAvailableSpdySessionToResumeMainJob) {
// Test the alternative job is not resumed when the alternative job is
// IO_PENDING for proxy resolution. Once all the proxy resolution succeeds,
// the latter part of this test tests controller resumes the main job
// when there's no SPDY session for the alternative job.
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
HangingResolver* host_resolver = new HangingResolver();
session_deps_.host_resolver.reset(host_resolver);
session_deps_.host_resolver->set_synchronous_mode(false);
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
// Set a SPDY alternative service for the server.
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
// Hack to use different URL for the main job to help differentiate the proxy
// requests.
job_factory_.UseDifferentURLForMainJob(GURL("http://www.google.com"));
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
// Both jobs should be created but stalled as proxy resolution not completed.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
MockAsyncProxyResolver resolver;
proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder(
net::OK, &resolver);
// Resolve proxy for the main job which then proceed to wait for the
// alternative job which is IO_PENDING.
int main_job_request_id =
resolver.pending_jobs()[0]->url().SchemeIs("http") ? 0 : 1;
resolver.pending_jobs()[main_job_request_id]->results()->UseNamedProxy(
"result1:80");
resolver.pending_jobs()[main_job_request_id]->CompleteNow(net::OK);
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Resolve proxy for the alternative job to proceed to create a connection.
// Use hanging HostResolver to fail creation of a SPDY session for the
// alternative job. The alternative job will be IO_PENDING thus should resume
// the main job.
resolver.pending_jobs()[0]->CompleteNow(net::OK);
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
NoAvailableQuicSessionToResumeMainJob) {
// Use failing HostResolver which is unable to resolve the host name for QUIC.
// No QUIC session is created and thus should resume the main job.
FailingHostResolver* host_resolver = new FailingHostResolver();
session_deps_.host_resolver.reset(host_resolver);
ProxyConfig proxy_config;
proxy_config.set_auto_detect(true);
// Use asynchronous proxy resolver.
MockAsyncProxyResolverFactory* proxy_resolver_factory =
new MockAsyncProxyResolverFactory(false);
session_deps_.proxy_service.reset(
new ProxyService(base::MakeUnique<ProxyConfigServiceFixed>(proxy_config),
base::WrapUnique(proxy_resolver_factory), nullptr));
Initialize(false);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
// Hack to use different URL for the main job to help differentiate the proxy
// requests.
job_factory_.UseDifferentURLForMainJob(GURL("http://www.google.com"));
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
MockAsyncProxyResolver resolver;
proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder(
net::OK, &resolver);
// Resolve proxy for the main job which then proceed to wait for the
// alternative job which is IO_PENDING.
int main_job_request_id =
resolver.pending_jobs()[0]->url().SchemeIs("http") ? 0 : 1;
resolver.pending_jobs()[main_job_request_id]->results()->UseNamedProxy(
"result1:80");
resolver.pending_jobs()[main_job_request_id]->CompleteNow(net::OK);
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Resolve proxy for the alternative job to proceed to create a connection.
// Use failing HostResolver to fail creation of a QUIC session for the
// alternative job. The alternative job will thus resume the main job.
resolver.pending_jobs()[0]->results()->UseNamedProxy("result1:80");
resolver.pending_jobs()[0]->CompleteNow(net::OK);
// Wait until OnStreamFailedCallback is executed on the alternative job.
// Request shouldn't be notified as the main job is still pending status.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryImplJobControllerTest, DelayedTCP) {
HangingResolver* resolver = new HangingResolver();
session_deps_.host_resolver.reset(resolver);
Initialize(false);
// Enable delayed TCP and set time delay for waiting job.
QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory();
test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true);
quic_stream_factory->set_require_confirmation(false);
ServerNetworkStats stats1;
stats1.srtt = base::TimeDelta::FromMicroseconds(10);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")), stats1);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
// Set a SPDY alternative service for the server.
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(QUIC, server.host(), 443);
SetAlternativeService(request_info, alternative_service);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// The alternative job stalls as host resolution hangs when creating the QUIC
// request and controller should resume the main job after delay.
// Verify the waiting time for delayed main job.
EXPECT_CALL(*job_factory_.main_job(), Resume())
.WillOnce(Invoke(testing::CreateFunctor(
&JobControllerPeer::VerifyWaitingTimeForMainJob, job_controller_,
base::TimeDelta::FromMicroseconds(15))));
base::RunLoop().RunUntilIdle();
}
// Verifies that the alternative proxy server job is not created if the URL
// scheme is HTTPS.
TEST_F(HttpStreamFactoryImplJobControllerTest, HttpsURL) {
// Using hanging resolver will cause the alternative job to hang indefinitely.
HangingResolver* resolver = new HangingResolver();
session_deps_.host_resolver.reset(resolver);
Initialize(true);
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic());
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://mail.example.org/");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_proxy_delegate()->get_alternative_proxy_invocations());
}
// Verifies that the alternative proxy server job is not created if the main job
// does not fetch the resource through a proxy.
TEST_F(HttpStreamFactoryImplJobControllerTest, HttpURLWithNoProxy) {
// Using hanging resolver will cause the alternative job to hang indefinitely.
HangingResolver* resolver = new HangingResolver();
session_deps_.host_resolver.reset(resolver);
Initialize(false);
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic());
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://mail.example.org/");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, test_proxy_delegate()->get_alternative_proxy_invocations());
}
// Verifies that the main job is resumed properly after a delay when the
// alternative proxy server job hangs.
TEST_F(HttpStreamFactoryImplJobControllerTest, DelayedTCPAlternativeProxy) {
// Using hanging resolver will cause the alternative job to hang indefinitely.
HangingResolver* resolver = new HangingResolver();
session_deps_.host_resolver.reset(resolver);
Initialize(true);
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic());
// Enable delayed TCP and set time delay for waiting job.
QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory();
test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true);
quic_stream_factory->set_require_confirmation(false);
ServerNetworkStats stats1;
stats1.srtt = base::TimeDelta::FromMicroseconds(10);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://myproxy.org")), stats1);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://mail.example.org/");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
// The alternative proxy server job stalls when connecting to the alternative
// proxy server, and controller should resume the main job after delay.
// Verify the waiting time for delayed main job.
EXPECT_CALL(*job_factory_.main_job(), Resume())
.WillOnce(Invoke(testing::CreateFunctor(
&JobControllerPeer::VerifyWaitingTimeForMainJob, job_controller_,
base::TimeDelta::FromMicroseconds(15))));
// This message loop should cause the alternative proxy server job to start,
// and schedule resumption of the main job.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
// Wait for the Resume to post.
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
// This message loop should cause the main job to resume.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
JobControllerPeer::VerifyWaitingTimeForMainJob(
job_controller_, base::TimeDelta::FromMicroseconds(0));
// Since the main job did not complete successfully, the alternative proxy
// server should not be marked as bad.
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid());
EXPECT_EQ(1, test_proxy_delegate()->get_alternative_proxy_invocations());
}
// Verifies that the alternative proxy server job fails immediately, and the
// main job is not blocked.
TEST_F(HttpStreamFactoryImplJobControllerTest, FailAlternativeProxy) {
// Using failing resolver will cause the alternative job to fail.
FailingHostResolver* resolver = new FailingHostResolver();
session_deps_.host_resolver.reset(resolver);
Initialize(true);
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic());
// Enable delayed TCP and set time delay for waiting job.
QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory();
test::QuicStreamFactoryPeer::SetDelayTcpRace(quic_stream_factory, true);
quic_stream_factory->set_require_confirmation(false);
ServerNetworkStats stats1;
stats1.srtt = base::TimeDelta::FromMicroseconds(300 * 1000);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://myproxy.org")), stats1);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://mail.example.org/");
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, _)).Times(0);
// Since the alternative proxy server job is started in the next message loop,
// the main job would remain blocked until the alternative proxy starts, and
// fails.
EXPECT_CALL(*job_factory_.main_job(), Resume())
.WillOnce(Invoke(testing::CreateFunctor(
&JobControllerPeer::VerifyWaitingTimeForMainJob, job_controller_,
base::TimeDelta::FromMicroseconds(0))));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Since the main job did not complete successfully, the alternative proxy
// server should not be marked as bad.
EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid());
EXPECT_EQ(1, test_proxy_delegate()->get_alternative_proxy_invocations());
}
TEST_F(HttpStreamFactoryImplJobControllerTest,
AlternativeProxyServerJobFailsAfterMainJobSucceeds) {
base::HistogramTester histogram_tester;
Initialize(true);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
url::SchemeHostPort server(request_info.url);
request_.reset(
job_controller_->Start(request_info, &request_delegate_, nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY, SSLConfig(), SSLConfig()));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Main job succeeds, starts serving Request and it should report status
// to Request. The alternative job will mark the main job complete and gets
// orphaned.
HttpStream* http_stream =
new HttpBasicStream(base::MakeUnique<ClientSocketHandle>(), false, false);
job_factory_.main_job()->SetStream(http_stream);
EXPECT_CALL(request_delegate_, OnStreamReady(_, _, http_stream))
.WillOnce(Invoke(DeleteHttpStreamPointer));
job_controller_->OnStreamReady(job_factory_.main_job(), SSLConfig(),
ProxyInfo());
// JobController shouldn't report the status of alternative server job as
// request is already successfully served.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _)).Times(0);
job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED,
SSLConfig());
// Reset the request as it's been successfully served.
request_.reset();
EXPECT_TRUE(HttpStreamFactoryImplPeer::IsJobControllerDeleted(factory_));
histogram_tester.ExpectUniqueSample("Net.QuicAlternativeProxy.Usage",
2 /* ALTERNATIVE_PROXY_USAGE_LOST_RACE */,
1);
}
} // namespace net