blob: 4ccb1a5bad2c766ccfa2a659b52abbf8d6833638 [file] [log] [blame]
// 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 <string>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "net/base/address_list.h"
#include "net/base/host_cache.h"
#include "net/base/io_buffer.h"
#include "net/base/mock_host_resolver.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/request_priority.h"
#include "net/base/ssl_config_service_defaults.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 "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_;
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 = 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<IOBuffer> 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<int>(expected.length()), callback_.WaitForResult());
} else {
EXPECT_EQ(static_cast<int>(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(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(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<IOBuffer> 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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(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(session_.get());
TestCompletionCallback two_callback;
EXPECT_EQ(ERR_IO_PENDING,
two_transaction.Start(GetRequestInfo("two.html", options),
two_callback.callback(), BoundNetLog()));
HttpNetworkTransaction three_transaction(session_.get());
TestCompletionCallback three_callback;
EXPECT_EQ(ERR_IO_PENDING,
three_transaction.Start(GetRequestInfo("three.html", options),
three_callback.callback(),
BoundNetLog()));
HttpNetworkTransaction four_transaction(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<DeterministicSocketData> data_vector_;
TestCompletionCallback callback_;
ScopedVector<HttpRequestInfo> request_info_vector_;
SimpleProxyConfigService* proxy_config_service_;
scoped_ptr<ProxyService> proxy_service_;
MockHostResolver mock_resolver_;
scoped_refptr<SSLConfigService> ssl_config_;
scoped_ptr<HttpAuthHandlerMock::Factory> auth_handler_factory_;
HttpServerPropertiesImpl http_server_properties_;
scoped_refptr<HttpNetworkSession> 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(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(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(session_.get());
TestCompletionCallback two_callback;
EXPECT_EQ(ERR_IO_PENDING,
two_transaction.Start(GetRequestInfo("two.html"),
two_callback.callback(), BoundNetLog()));
scoped_refptr<IOBuffer> 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(session_.get());
TestCompletionCallback one_callback;
EXPECT_EQ(ERR_IO_PENDING,
one_transaction.Start(GetRequestInfo("one.html"),
one_callback.callback(), BoundNetLog()));
HttpNetworkTransaction two_transaction(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(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(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";
HttpAuth::ChallengeTokenizer 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(session_.get());
EXPECT_EQ(ERR_IO_PENDING,
transaction.Start(GetRequestInfo("one.html"), callback_.callback(),
BoundNetLog()));
EXPECT_EQ(OK, callback_.WaitForResult());
AuthCredentials credentials(ASCIIToUTF16("user"), 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(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(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()));
MessageLoop::current()->RunUntilIdle();
HttpNetworkTransaction second_two_transaction(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 MessageLoop::TaskObserver {
public:
DataRunnerObserver(DeterministicSocketData* data, int run_before_task)
: data_(data),
run_before_task_(run_before_task),
current_task_(0) { }
virtual void WillProcessTask(base::TimeTicks) override {
++current_task_;
if (current_task_ == run_before_task_) {
data_->Run();
MessageLoop::current()->RemoveTaskObserver(this);
}
}
virtual void DidProcessTask(base::TimeTicks) 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(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(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);
MessageLoop::current()->AddTaskObserver(&observer);
data_vector_[0]->SetStop(4);
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(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback one_callback;
EXPECT_EQ(ERR_IO_PENDING,
one_transaction->Start(GetRequestInfo("one.html"),
one_callback.callback(), BoundNetLog()));
HttpNetworkTransaction two_transaction(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback one_callback;
EXPECT_EQ(ERR_IO_PENDING,
one_transaction->Start(GetRequestInfo("one.html"),
one_callback.callback(), BoundNetLog()));
HttpNetworkTransaction two_transaction(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback one_callback;
EXPECT_EQ(ERR_IO_PENDING,
one_transaction->Start(GetRequestInfo("one.html"),
one_callback.callback(), BoundNetLog()));
HttpNetworkTransaction two_transaction(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<HttpNetworkTransaction> one_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback one_callback;
EXPECT_EQ(ERR_IO_PENDING,
one_transaction->Start(GetRequestInfo("one.html"),
one_callback.callback(), BoundNetLog()));
scoped_ptr<HttpNetworkTransaction> two_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback two_callback;
EXPECT_EQ(ERR_IO_PENDING,
two_transaction->Start(GetRequestInfo("two.html"),
two_callback.callback(), BoundNetLog()));
scoped_ptr<HttpNetworkTransaction> three_transaction(
new HttpNetworkTransaction(session_.get()));
TestCompletionCallback three_callback;
EXPECT_EQ(ERR_IO_PENDING,
three_transaction->Start(GetRequestInfo("three.html"),
three_callback.callback(), BoundNetLog()));
scoped_ptr<HttpNetworkTransaction> four_transaction(
new HttpNetworkTransaction(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