// Copyright (c) 2012 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 #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "net/base/address_list.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/request_priority.h" #include "net/dns/host_cache.h" #include "net/dns/mock_host_resolver.h" #include "net/http/http_auth_challenge_tokenizer.h" #include "net/http/http_auth_handler_mock.h" #include "net/http/http_network_session.h" #include "net/http/http_network_transaction.h" #include "net/http/http_request_info.h" #include "net/http/http_server_properties_impl.h" #include "net/proxy/proxy_config_service.h" #include "net/proxy/proxy_service.h" #include "net/socket/client_socket_handle.h" #include "net/socket/client_socket_pool_histograms.h" #include "net/socket/client_socket_pool_manager.h" #include "net/socket/socket_test_util.h" #include "net/ssl/ssl_config_service_defaults.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using testing::StrEq; namespace net { namespace { class SimpleProxyConfigService : public ProxyConfigService { public: virtual void AddObserver(Observer* observer) OVERRIDE { observer_ = observer; } virtual void RemoveObserver(Observer* observer) OVERRIDE { if (observer_ == observer) { observer_ = NULL; } } virtual ConfigAvailability GetLatestProxyConfig( ProxyConfig* config) OVERRIDE { *config = config_; return CONFIG_VALID; } void IncrementConfigId() { config_.set_id(config_.id() + 1); observer_->OnProxyConfigChanged(config_, ProxyConfigService::CONFIG_VALID); } private: ProxyConfig config_; Observer* observer_; }; class HttpPipelinedNetworkTransactionTest : public testing::Test { public: HttpPipelinedNetworkTransactionTest() : histograms_("a"), pool_(1, 1, &histograms_, &factory_) { } void Initialize(bool force_http_pipelining) { // Normally, this code could just go in SetUp(). For a few of these tests, // we change the default number of sockets per group. That needs to be done // before we construct the HttpNetworkSession. proxy_config_service_ = new SimpleProxyConfigService(); proxy_service_.reset(new ProxyService(proxy_config_service_, NULL, NULL)); ssl_config_ = new SSLConfigServiceDefaults; auth_handler_factory_.reset(new HttpAuthHandlerMock::Factory()); HttpNetworkSession::Params session_params; session_params.client_socket_factory = &factory_; session_params.proxy_service = proxy_service_.get(); session_params.host_resolver = &mock_resolver_; session_params.ssl_config_service = ssl_config_.get(); session_params.http_auth_handler_factory = auth_handler_factory_.get(); session_params.http_server_properties = http_server_properties_.GetWeakPtr(); session_params.force_http_pipelining = force_http_pipelining; session_params.http_pipelining_enabled = true; session_ = new HttpNetworkSession(session_params); } void AddExpectedConnection(MockRead* reads, size_t reads_count, MockWrite* writes, size_t writes_count) { DeterministicSocketData* data = new DeterministicSocketData( reads, reads_count, writes, writes_count); data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); if (reads_count || writes_count) { data->StopAfter(reads_count + writes_count); } factory_.AddSocketDataProvider(data); data_vector_.push_back(data); } enum RequestInfoOptions { REQUEST_DEFAULT, REQUEST_MAIN_RESOURCE, }; HttpRequestInfo* GetRequestInfo( const char* filename, RequestInfoOptions options = REQUEST_DEFAULT) { std::string url = base::StringPrintf("http://localhost/%s", filename); HttpRequestInfo* request_info = new HttpRequestInfo; request_info->url = GURL(url); request_info->method = "GET"; if (options == REQUEST_MAIN_RESOURCE) { request_info->load_flags = LOAD_MAIN_FRAME; } request_info_vector_.push_back(request_info); return request_info; } void ExpectResponse(const std::string& expected, HttpNetworkTransaction& transaction, IoMode io_mode) { scoped_refptr buffer(new IOBuffer(expected.size())); if (io_mode == ASYNC) { EXPECT_EQ(ERR_IO_PENDING, transaction.Read(buffer.get(), expected.size(), callback_.callback())); data_vector_[0]->RunFor(1); EXPECT_EQ(static_cast(expected.length()), callback_.WaitForResult()); } else { EXPECT_EQ(static_cast(expected.size()), transaction.Read(buffer.get(), expected.size(), callback_.callback())); } std::string actual(buffer->data(), expected.size()); EXPECT_THAT(actual, StrEq(expected)); EXPECT_EQ(OK, transaction.Read(buffer.get(), expected.size(), callback_.callback())); } void CompleteTwoRequests(int data_index, int stop_at_step) { scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); TestCompletionCallback one_read_callback; scoped_refptr buffer(new IOBuffer(8)); EXPECT_EQ(ERR_IO_PENDING, one_transaction->Read(buffer.get(), 8, one_read_callback.callback())); data_vector_[data_index]->SetStop(stop_at_step); data_vector_[data_index]->Run(); EXPECT_EQ(8, one_read_callback.WaitForResult()); data_vector_[data_index]->SetStop(10); std::string actual(buffer->data(), 8); EXPECT_THAT(actual, StrEq("one.html")); EXPECT_EQ(OK, one_transaction->Read(buffer.get(), 8, one_read_callback.callback())); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } void CompleteFourRequests(RequestInfoOptions options) { scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html", options), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html", options), two_callback.callback(), BoundNetLog())); HttpNetworkTransaction three_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback three_callback; EXPECT_EQ(ERR_IO_PENDING, three_transaction.Start(GetRequestInfo("three.html", options), three_callback.callback(), BoundNetLog())); HttpNetworkTransaction four_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback four_callback; EXPECT_EQ(ERR_IO_PENDING, four_transaction.Start(GetRequestInfo("four.html", options), four_callback.callback(), BoundNetLog())); ExpectResponse("one.html", *one_transaction.get(), SYNCHRONOUS); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); EXPECT_EQ(OK, three_callback.WaitForResult()); ExpectResponse("three.html", three_transaction, SYNCHRONOUS); one_transaction.reset(); EXPECT_EQ(OK, four_callback.WaitForResult()); ExpectResponse("four.html", four_transaction, SYNCHRONOUS); } DeterministicMockClientSocketFactory factory_; ClientSocketPoolHistograms histograms_; MockTransportClientSocketPool pool_; ScopedVector data_vector_; TestCompletionCallback callback_; ScopedVector request_info_vector_; SimpleProxyConfigService* proxy_config_service_; scoped_ptr proxy_service_; MockHostResolver mock_resolver_; scoped_refptr ssl_config_; scoped_ptr auth_handler_factory_; HttpServerPropertiesImpl http_server_properties_; scoped_refptr session_; }; TEST_F(HttpPipelinedNetworkTransactionTest, OneRequest) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /test.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 9\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "test.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get()); EXPECT_EQ(ERR_IO_PENDING, transaction.Start(GetRequestInfo("test.html"), callback_.callback(), BoundNetLog())); EXPECT_EQ(OK, callback_.WaitForResult()); ExpectResponse("test.html", transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, ReusePipeline) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 4, "one.html"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 7, "two.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); CompleteTwoRequests(0, 5); } TEST_F(HttpPipelinedNetworkTransactionTest, ReusesOnSpaceAvailable) { int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, 1); Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 7, "GET /three.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "one.html"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 8, "two.html"), MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"), MockRead(SYNCHRONOUS, 11, "three.html"), MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"), MockRead(SYNCHRONOUS, 15, "four.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); CompleteFourRequests(REQUEST_DEFAULT); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets); } TEST_F(HttpPipelinedNetworkTransactionTest, WontPipelineMainResource) { int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, 1); Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 8, "GET /three.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "one.html"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 7, "two.html"), MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"), MockRead(SYNCHRONOUS, 11, "three.html"), MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"), MockRead(SYNCHRONOUS, 15, "four.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); CompleteFourRequests(REQUEST_MAIN_RESOURCE); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets); } TEST_F(HttpPipelinedNetworkTransactionTest, UnknownSizeEvictsToNewPipeline) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n\r\n"), MockRead(ASYNC, 2, "one.html"), MockRead(SYNCHRONOUS, OK, 3), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); MockWrite writes2[] = { MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads2[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "two.html"), }; AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2)); CompleteTwoRequests(0, 3); } TEST_F(HttpPipelinedNetworkTransactionTest, ConnectionCloseEvictToNewPipeline) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 4, "one.html"), MockRead(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED, 5), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); MockWrite writes2[] = { MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads2[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "two.html"), }; AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2)); CompleteTwoRequests(0, 5); } TEST_F(HttpPipelinedNetworkTransactionTest, ErrorEvictsToNewPipeline) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n\r\n"), MockRead(SYNCHRONOUS, ERR_FAILED, 2), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); MockWrite writes2[] = { MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads2[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "two.html"), }; AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2)); HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction.Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); scoped_refptr buffer(new IOBuffer(1)); EXPECT_EQ(ERR_FAILED, one_transaction.Read(buffer.get(), 1, callback_.callback())); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, SendErrorEvictsToNewPipeline) { Initialize(false); MockWrite writes[] = { MockWrite(ASYNC, ERR_FAILED, 0), }; AddExpectedConnection(NULL, 0, writes, arraysize(writes)); MockWrite writes2[] = { MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads2[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "two.html"), }; AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2)); HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction.Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); data_vector_[0]->RunFor(1); EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, RedirectDrained) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /redirect.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 3, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 302 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 4, "redirect"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 7, "two.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("redirect.html"), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); one_transaction.reset(); data_vector_[0]->RunFor(2); data_vector_[0]->SetStop(10); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, BasicHttpAuthentication) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 5, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n" "Authorization: auth_token\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 401 Authentication Required\r\n"), MockRead(SYNCHRONOUS, 2, "WWW-Authenticate: Basic realm=\"Secure Area\"\r\n"), MockRead(SYNCHRONOUS, 3, "Content-Length: 20\r\n\r\n"), MockRead(SYNCHRONOUS, 4, "needs authentication"), MockRead(SYNCHRONOUS, 6, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 7, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 8, "one.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); HttpAuthHandlerMock* mock_auth = new HttpAuthHandlerMock; std::string challenge_text = "Basic"; HttpAuthChallengeTokenizer challenge(challenge_text.begin(), challenge_text.end()); GURL origin("localhost"); EXPECT_TRUE(mock_auth->InitFromChallenge(&challenge, HttpAuth::AUTH_SERVER, origin, BoundNetLog())); auth_handler_factory_->AddMockHandler(mock_auth, HttpAuth::AUTH_SERVER); HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get()); EXPECT_EQ(ERR_IO_PENDING, transaction.Start(GetRequestInfo("one.html"), callback_.callback(), BoundNetLog())); EXPECT_EQ(OK, callback_.WaitForResult()); AuthCredentials credentials(base::ASCIIToUTF16("user"), base::ASCIIToUTF16("pass")); EXPECT_EQ(OK, transaction.RestartWithAuth(credentials, callback_.callback())); ExpectResponse("one.html", transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, OldVersionDisablesPipelining) { Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /pipelined.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.0 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 14\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "pipelined.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); MockWrite writes2[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads2[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 3, "one.html"), MockRead(SYNCHRONOUS, OK, 4), }; AddExpectedConnection(reads2, arraysize(reads2), writes2, arraysize(writes2)); MockWrite writes3[] = { MockWrite(SYNCHRONOUS, 0, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads3[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "two.html"), MockRead(SYNCHRONOUS, OK, 4), }; AddExpectedConnection(reads3, arraysize(reads3), writes3, arraysize(writes3)); HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction.Start(GetRequestInfo("pipelined.html"), one_callback.callback(), BoundNetLog())); EXPECT_EQ(OK, one_callback.WaitForResult()); ExpectResponse("pipelined.html", one_transaction, SYNCHRONOUS); CompleteTwoRequests(1, 4); } TEST_F(HttpPipelinedNetworkTransactionTest, PipelinesImmediatelyIfKnownGood) { // The first request gets us an HTTP/1.1. The next 3 test pipelining. When the // 3rd request completes, we know pipelining is safe. After the first 4 // complete, the 5th and 6th should then be immediately sent pipelined on a // new HttpPipelinedConnection. int old_max_sockets = ClientSocketPoolManager::max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, 1); Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 4, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 7, "GET /three.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 12, "GET /four.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 16, "GET /second-pipeline-one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(SYNCHRONOUS, 17, "GET /second-pipeline-two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 3, "one.html"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 8, "two.html"), MockRead(SYNCHRONOUS, 9, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 10, "Content-Length: 10\r\n\r\n"), MockRead(SYNCHRONOUS, 11, "three.html"), MockRead(SYNCHRONOUS, 13, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 14, "Content-Length: 9\r\n\r\n"), MockRead(SYNCHRONOUS, 15, "four.html"), MockRead(ASYNC, 18, "HTTP/1.1 200 OK\r\n"), MockRead(ASYNC, 19, "Content-Length: 24\r\n\r\n"), MockRead(SYNCHRONOUS, 20, "second-pipeline-one.html"), MockRead(SYNCHRONOUS, 21, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 22, "Content-Length: 24\r\n\r\n"), MockRead(SYNCHRONOUS, 23, "second-pipeline-two.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); CompleteFourRequests(REQUEST_DEFAULT); HttpNetworkTransaction second_one_transaction( DEFAULT_PRIORITY, session_.get()); TestCompletionCallback second_one_callback; EXPECT_EQ(ERR_IO_PENDING, second_one_transaction.Start( GetRequestInfo("second-pipeline-one.html"), second_one_callback.callback(), BoundNetLog())); base::MessageLoop::current()->RunUntilIdle(); HttpNetworkTransaction second_two_transaction( DEFAULT_PRIORITY, session_.get()); TestCompletionCallback second_two_callback; EXPECT_EQ(ERR_IO_PENDING, second_two_transaction.Start( GetRequestInfo("second-pipeline-two.html"), second_two_callback.callback(), BoundNetLog())); data_vector_[0]->RunFor(3); EXPECT_EQ(OK, second_one_callback.WaitForResult()); data_vector_[0]->StopAfter(100); ExpectResponse("second-pipeline-one.html", second_one_transaction, SYNCHRONOUS); EXPECT_EQ(OK, second_two_callback.WaitForResult()); ExpectResponse("second-pipeline-two.html", second_two_transaction, SYNCHRONOUS); ClientSocketPoolManager::set_max_sockets_per_group( HttpNetworkSession::NORMAL_SOCKET_POOL, old_max_sockets); } class DataRunnerObserver : public base::MessageLoop::TaskObserver { public: DataRunnerObserver(DeterministicSocketData* data, int run_before_task) : data_(data), run_before_task_(run_before_task), current_task_(0) { } virtual void WillProcessTask(const base::PendingTask& pending_task) OVERRIDE { ++current_task_; if (current_task_ == run_before_task_) { data_->Run(); base::MessageLoop::current()->RemoveTaskObserver(this); } } virtual void DidProcessTask(const base::PendingTask& pending_task) OVERRIDE {} private: DeterministicSocketData* data_; int run_before_task_; int current_task_; }; TEST_F(HttpPipelinedNetworkTransactionTest, OpenPipelinesWhileBinding) { // There was a racy crash in the pipelining code. This test recreates that // race. The steps are: // 1. The first request starts a pipeline and requests headers. // 2. HttpStreamFactoryImpl::Job tries to bind a pending request to a new // pipeline and queues a task to do so. // 3. Before that task runs, the first request receives its headers and // determines this host is probably capable of pipelining. // 4. All of the hosts' pipelines are notified they have capacity in a loop. // 5. On the first iteration, the first pipeline is opened up to accept new // requests and steals the request from step #2. // 6. The pipeline from #2 is deleted because it has no streams. // 7. On the second iteration, the host tries to notify the pipeline from step // #2 that it has capacity. This is a use-after-free. Initialize(false); MockWrite writes[] = { MockWrite(SYNCHRONOUS, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), MockWrite(ASYNC, 3, "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 OK\r\n"), MockRead(ASYNC, 2, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 4, "one.html"), MockRead(SYNCHRONOUS, 5, "HTTP/1.1 200 OK\r\n"), MockRead(SYNCHRONOUS, 6, "Content-Length: 8\r\n\r\n"), MockRead(SYNCHRONOUS, 7, "two.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); AddExpectedConnection(NULL, 0, NULL, 0); HttpNetworkTransaction one_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction.Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); data_vector_[0]->SetStop(2); data_vector_[0]->Run(); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); // Posted tasks should be: // 1. MockHostResolverBase::ResolveNow // 2. HttpStreamFactoryImpl::Job::OnStreamReadyCallback for job 1 // 3. HttpStreamFactoryImpl::Job::OnStreamReadyCallback for job 2 // // We need to make sure that the response that triggers OnPipelineFeedback(OK) // is called in between when task #3 is scheduled and when it runs. The // DataRunnerObserver does that. DataRunnerObserver observer(data_vector_[0], 3); base::MessageLoop::current()->AddTaskObserver(&observer); data_vector_[0]->SetStop(4); base::MessageLoop::current()->RunUntilIdle(); data_vector_[0]->SetStop(10); EXPECT_EQ(OK, one_callback.WaitForResult()); ExpectResponse("one.html", one_transaction, SYNCHRONOUS); EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, SYNCHRONOUS); } TEST_F(HttpPipelinedNetworkTransactionTest, ProxyChangesWhileConnecting) { Initialize(false); DeterministicSocketData data(NULL, 0, NULL, 0); data.set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_REFUSED)); factory_.AddSocketDataProvider(&data); DeterministicSocketData data2(NULL, 0, NULL, 0); data2.set_connect_data(MockConnect(ASYNC, ERR_FAILED)); factory_.AddSocketDataProvider(&data2); HttpNetworkTransaction transaction(DEFAULT_PRIORITY, session_.get()); EXPECT_EQ(ERR_IO_PENDING, transaction.Start(GetRequestInfo("test.html"), callback_.callback(), BoundNetLog())); proxy_config_service_->IncrementConfigId(); EXPECT_EQ(ERR_FAILED, callback_.WaitForResult()); } TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineSharesConnection) { Initialize(true); MockWrite writes[] = { MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(ASYNC, 1, "HTTP/1.1 200 OK\r\n"), MockRead(ASYNC, 2, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 3, "one.html"), MockRead(ASYNC, 4, "HTTP/1.1 200 OK\r\n"), MockRead(ASYNC, 5, "Content-Length: 8\r\n\r\n"), MockRead(ASYNC, 6, "two.html"), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); data_vector_[0]->RunFor(3); // Send + 2 lines of headers. EXPECT_EQ(OK, one_callback.WaitForResult()); ExpectResponse("one.html", *one_transaction.get(), ASYNC); one_transaction.reset(); data_vector_[0]->RunFor(2); // 2 lines of headers. EXPECT_EQ(OK, two_callback.WaitForResult()); ExpectResponse("two.html", two_transaction, ASYNC); } TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineConnectionErrorFailsBoth) { Initialize(true); DeterministicSocketData data(NULL, 0, NULL, 0); data.set_connect_data(MockConnect(ASYNC, ERR_FAILED)); factory_.AddSocketDataProvider(&data); scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); data.Run(); EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); EXPECT_EQ(ERR_FAILED, two_callback.WaitForResult()); } TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineEvictionIsFatal) { Initialize(true); MockWrite writes[] = { MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n"), }; MockRead reads[] = { MockRead(ASYNC, ERR_FAILED, 1), }; AddExpectedConnection(reads, arraysize(reads), writes, arraysize(writes)); scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); HttpNetworkTransaction two_transaction(DEFAULT_PRIORITY, session_.get()); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction.Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); data_vector_[0]->RunFor(2); EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); one_transaction.reset(); EXPECT_EQ(ERR_PIPELINE_EVICTION, two_callback.WaitForResult()); } TEST_F(HttpPipelinedNetworkTransactionTest, ForcedPipelineOrder) { Initialize(true); MockWrite writes[] = { MockWrite(ASYNC, 0, "GET /one.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" "GET /two.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" "GET /three.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" "GET /four.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: keep-alive\r\n\r\n" ), }; MockRead reads[] = { MockRead(ASYNC, ERR_FAILED, 1), }; DeterministicSocketData data( reads, arraysize(reads), writes, arraysize(writes)); data.set_connect_data(MockConnect(ASYNC, OK)); factory_.AddSocketDataProvider(&data); scoped_ptr one_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback one_callback; EXPECT_EQ(ERR_IO_PENDING, one_transaction->Start(GetRequestInfo("one.html"), one_callback.callback(), BoundNetLog())); scoped_ptr two_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback two_callback; EXPECT_EQ(ERR_IO_PENDING, two_transaction->Start(GetRequestInfo("two.html"), two_callback.callback(), BoundNetLog())); scoped_ptr three_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback three_callback; EXPECT_EQ(ERR_IO_PENDING, three_transaction->Start(GetRequestInfo("three.html"), three_callback.callback(), BoundNetLog())); scoped_ptr four_transaction( new HttpNetworkTransaction(DEFAULT_PRIORITY, session_.get())); TestCompletionCallback four_callback; EXPECT_EQ(ERR_IO_PENDING, four_transaction->Start(GetRequestInfo("four.html"), four_callback.callback(), BoundNetLog())); data.RunFor(3); EXPECT_EQ(ERR_FAILED, one_callback.WaitForResult()); one_transaction.reset(); EXPECT_EQ(ERR_PIPELINE_EVICTION, two_callback.WaitForResult()); two_transaction.reset(); EXPECT_EQ(ERR_PIPELINE_EVICTION, three_callback.WaitForResult()); three_transaction.reset(); EXPECT_EQ(ERR_PIPELINE_EVICTION, four_callback.WaitForResult()); } } // anonymous namespace } // namespace net