blob: 686949b3359a940f613e69ed52e13dab6e67ac32 [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 "net/spdy/spdy_session.h"
#include "base/memory/scoped_ptr.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_log_unittest.h"
#include "net/base/request_priority.h"
#include "net/base/test_data_directory.h"
#include "net/base/test_data_stream.h"
#include "net/dns/host_cache.h"
#include "net/socket/next_proto.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_session_test_util.h"
#include "net/spdy/spdy_stream.h"
#include "net/spdy/spdy_stream_test_util.h"
#include "net/spdy/spdy_test_util_common.h"
#include "net/spdy/spdy_test_util_spdy2.h"
#include "net/spdy/spdy_test_utils.h"
#include "net/test/cert_test_util.h"
#include "testing/platform_test.h"
using namespace net::test_spdy2;
namespace net {
namespace {
static const char kTestUrl[] = "http://www.example.org/";
static const char kTestHost[] = "www.example.org";
static const int kTestPort = 80;
static int g_delta_seconds = 0;
base::TimeTicks TheNearFuture() {
return base::TimeTicks::Now() + base::TimeDelta::FromSeconds(g_delta_seconds);
}
} // namespace
class SpdySessionSpdy2Test : public PlatformTest {
protected:
SpdySessionSpdy2Test()
: session_deps_(kProtoSPDY2),
spdy_session_pool_(NULL),
test_url_(kTestUrl),
test_host_port_pair_(kTestHost, kTestPort),
pair_(test_host_port_pair_, ProxyServer::Direct()) {
}
virtual void SetUp() {
g_delta_seconds = 0;
}
void CreateDeterministicNetworkSession() {
http_session_ =
SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
spdy_session_pool_ = http_session_->spdy_session_pool();
}
void CreateNetworkSession() {
http_session_ =
SpdySessionDependencies::SpdyCreateSession(&session_deps_);
spdy_session_pool_ = http_session_->spdy_session_pool();
}
scoped_refptr<SpdySession> GetSession(const HostPortProxyPair& pair) {
EXPECT_FALSE(spdy_session_pool_->HasSession(pair));
scoped_refptr<SpdySession> session =
spdy_session_pool_->Get(pair, BoundNetLog());
EXPECT_TRUE(spdy_session_pool_->HasSession(pair));
return session;
}
// Creates an initialized session to |pair_|.
scoped_refptr<SpdySession> CreateInitializedSession() {
scoped_refptr<SpdySession> session = GetSession(pair_);
EXPECT_EQ(
OK,
InitializeSession(
http_session_.get(), session.get(), test_host_port_pair_));
return session;
}
net::Error InitializeSession(HttpNetworkSession* http_session,
SpdySession* session,
const HostPortPair& host_port_pair) {
transport_params_ = new TransportSocketParams(
host_port_pair, MEDIUM, false, false, OnHostResolutionCallback());
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(),
transport_params_, MEDIUM,
CompletionCallback(),
http_session->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
return session->InitializeWithSocket(connection.release(), false, OK);
}
scoped_refptr<TransportSocketParams> transport_params_;
SpdySessionDependencies session_deps_;
scoped_refptr<HttpNetworkSession> http_session_;
SpdySessionPool* spdy_session_pool_;
GURL test_url_;
HostPortPair test_host_port_pair_;
HostPortProxyPair pair_;
};
TEST_F(SpdySessionSpdy2Test, GoAway) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway(1));
MockRead reads[] = {
CreateMockRead(*goaway, 2),
MockRead(ASYNC, 0, 3) // EOF
};
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, MEDIUM));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
CreateMockWrite(*req2, 1),
};
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
EXPECT_EQ(2, session->GetProtocolVersion());
GURL url("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url, MEDIUM, BoundNetLog());
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session, url, MEDIUM, BoundNetLog());
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url.scheme();
(*headers)["host"] = url.host();
(*headers)["url"] = url.path();
(*headers)["version"] = "HTTP/1.1";
scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
*headers2 = *headers;
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream2->set_spdy_headers(headers2.Pass());
EXPECT_TRUE(spdy_stream2->HasUrl());
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
data.RunFor(2);
EXPECT_EQ(1u, spdy_stream1->stream_id());
EXPECT_EQ(3u, spdy_stream2->stream_id());
EXPECT_TRUE(spdy_session_pool_->HasSession(pair_));
// Read and process the GOAWAY frame.
data.RunFor(1);
EXPECT_FALSE(spdy_session_pool_->HasSession(pair_));
EXPECT_TRUE(session->IsStreamActive(1));
EXPECT_FALSE(session->IsStreamActive(3));
scoped_refptr<SpdySession> session2 = GetSession(pair_);
spdy_stream1->Close();
EXPECT_EQ(NULL, spdy_stream1.get());
// Delete the first session.
session = NULL;
// Delete the second session.
spdy_session_pool_->Remove(session2);
session2 = NULL;
EXPECT_EQ(NULL, spdy_stream2.get());
}
TEST_F(SpdySessionSpdy2Test, ClientPing) {
session_deps_.enable_ping = true;
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
MockRead reads[] = {
CreateMockRead(*read_ping),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
MockWrite writes[] = {
CreateMockWrite(*write_ping),
};
StaticSocketDataProvider data(
reads, arraysize(reads), writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
test::StreamDelegateSendImmediate delegate(
spdy_stream1, scoped_ptr<SpdyHeaderBlock>(), NULL);
spdy_stream1->SetDelegate(&delegate);
base::TimeTicks before_ping_time = base::TimeTicks::Now();
session->set_connection_at_risk_of_loss_time(
base::TimeDelta::FromSeconds(-1));
session->set_hung_interval(base::TimeDelta::FromMilliseconds(50));
session->SendPrefacePingIfNoneInFlight();
EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
session->CheckPingStatus(before_ping_time);
EXPECT_EQ(0, session->pings_in_flight());
EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
EXPECT_FALSE(session->check_ping_status_pending());
EXPECT_GE(session->last_activity_time(), before_ping_time);
EXPECT_FALSE(spdy_session_pool_->HasSession(pair_));
// Delete the first session.
session = NULL;
}
TEST_F(SpdySessionSpdy2Test, ServerPing) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(2));
MockRead reads[] = {
CreateMockRead(*read_ping),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(2));
MockWrite writes[] = {
CreateMockWrite(*write_ping),
};
StaticSocketDataProvider data(
reads, arraysize(reads), writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
test::StreamDelegateSendImmediate delegate(
spdy_stream1, scoped_ptr<SpdyHeaderBlock>(), NULL);
spdy_stream1->SetDelegate(&delegate);
// Flush the SpdySession::OnReadComplete() task.
MessageLoop::current()->RunUntilIdle();
EXPECT_FALSE(spdy_session_pool_->HasSession(pair_));
// Delete the session.
session = NULL;
EXPECT_EQ(NULL, spdy_stream1.get());
}
TEST_F(SpdySessionSpdy2Test, DeleteExpiredPushStreams) {
session_deps_.host_resolver->set_synchronous_mode(true);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
session_deps_.time_func = TheNearFuture;
CreateNetworkSession();
scoped_refptr<SpdySession> session = GetSession(pair_);
// Give the session a SPDY2 framer.
session->buffered_spdy_framer_.reset(new BufferedSpdyFramer(2, false));
// Create the associated stream and add to active streams.
scoped_ptr<SpdyHeaderBlock> request_headers(new SpdyHeaderBlock);
(*request_headers)["scheme"] = "http";
(*request_headers)["host"] = "www.google.com";
(*request_headers)["url"] = "/";
scoped_refptr<SpdyStream> stream(
new SpdyStream(session, std::string(), DEFAULT_PRIORITY,
kSpdyStreamInitialWindowSize,
kSpdyStreamInitialWindowSize,
false, session->net_log_));
stream->set_spdy_headers(request_headers.Pass());
session->ActivateStream(stream);
stream = NULL;
SpdyHeaderBlock headers;
headers["url"] = "http://www.google.com/a.dat";
session->OnSynStream(2, 1, 0, 0, true, false, headers);
// Verify that there is one unclaimed push stream.
EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
SpdySession::PushedStreamMap::iterator iter =
session->unclaimed_pushed_streams_.find("http://www.google.com/a.dat");
EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
// Shift time.
g_delta_seconds = 301;
headers["url"] = "http://www.google.com/b.dat";
session->OnSynStream(4, 1, 0, 0, true, false, headers);
// Verify that the second pushed stream evicted the first pushed stream.
EXPECT_EQ(1u, session->num_unclaimed_pushed_streams());
iter = session->unclaimed_pushed_streams_.find("http://www.google.com/b.dat");
EXPECT_TRUE(session->unclaimed_pushed_streams_.end() != iter);
// Delete the session.
session = NULL;
}
TEST_F(SpdySessionSpdy2Test, FailedPing) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> read_ping(ConstructSpdyPing(1));
MockRead reads[] = {
CreateMockRead(*read_ping),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
scoped_ptr<SpdyFrame> write_ping(ConstructSpdyPing(1));
MockWrite writes[] = {
CreateMockWrite(*write_ping),
};
StaticSocketDataProvider data(
reads, arraysize(reads), writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
test::StreamDelegateSendImmediate delegate(
spdy_stream1, scoped_ptr<SpdyHeaderBlock>(), NULL);
spdy_stream1->SetDelegate(&delegate);
session->set_connection_at_risk_of_loss_time(base::TimeDelta::FromSeconds(0));
session->set_hung_interval(base::TimeDelta::FromSeconds(0));
// Send a PING frame.
session->WritePingFrame(1);
EXPECT_LT(0, session->pings_in_flight());
EXPECT_GE(session->next_ping_id(), static_cast<uint32>(1));
EXPECT_TRUE(session->check_ping_status_pending());
// Assert session is not closed.
EXPECT_FALSE(session->IsClosed());
EXPECT_LT(0u, session->num_active_streams() + session->num_created_streams());
EXPECT_TRUE(spdy_session_pool_->HasSession(pair_));
// We set last time we have received any data in 1 sec less than now.
// CheckPingStatus will trigger timeout because hung interval is zero.
base::TimeTicks now = base::TimeTicks::Now();
session->last_activity_time_ = now - base::TimeDelta::FromSeconds(1);
session->CheckPingStatus(now);
EXPECT_TRUE(session->IsClosed());
EXPECT_EQ(0u, session->num_active_streams());
EXPECT_EQ(0u, session->num_unclaimed_pushed_streams());
EXPECT_FALSE(spdy_session_pool_->HasSession(pair_));
// Delete the first session.
session = NULL;
EXPECT_EQ(NULL, spdy_stream1.get());
}
TEST_F(SpdySessionSpdy2Test, CloseIdleSessions) {
MockConnect connect_data(SYNCHRONOUS, OK);
MockRead reads[] = {
MockRead(ASYNC, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
CreateNetworkSession();
// Set up session 1
const std::string kTestHost1("http://www.a.com");
HostPortPair test_host_port_pair1(kTestHost1, 80);
HostPortProxyPair pair1(test_host_port_pair1, ProxyServer::Direct());
scoped_refptr<SpdySession> session1 = GetSession(pair1);
EXPECT_EQ(
OK,
InitializeSession(
http_session_.get(), session1.get(), test_host_port_pair1));
GURL url1(kTestHost1);
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session1, url1, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
// Set up session 2
session_deps_.socket_factory->AddSocketDataProvider(&data);
const std::string kTestHost2("http://www.b.com");
HostPortPair test_host_port_pair2(kTestHost2, 80);
HostPortProxyPair pair2(test_host_port_pair2, ProxyServer::Direct());
scoped_refptr<SpdySession> session2 = GetSession(pair2);
EXPECT_EQ(
OK,
InitializeSession(
http_session_.get(), session2.get(), test_host_port_pair2));
GURL url2(kTestHost2);
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session2, url2, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream2.get() != NULL);
// Set up session 3
session_deps_.socket_factory->AddSocketDataProvider(&data);
const std::string kTestHost3("http://www.c.com");
HostPortPair test_host_port_pair3(kTestHost3, 80);
HostPortProxyPair pair3(test_host_port_pair3, ProxyServer::Direct());
scoped_refptr<SpdySession> session3 = GetSession(pair3);
EXPECT_EQ(
OK,
InitializeSession(
http_session_.get(), session3.get(), test_host_port_pair3));
GURL url3(kTestHost3);
base::WeakPtr<SpdyStream> spdy_stream3 =
CreateStreamSynchronously(session3, url3, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream3.get() != NULL);
// All sessions are active and not closed
EXPECT_TRUE(session1->is_active());
EXPECT_FALSE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
EXPECT_TRUE(session3->is_active());
EXPECT_FALSE(session3->IsClosed());
// Should not do anything, all are active
spdy_session_pool_->CloseIdleSessions();
EXPECT_TRUE(session1->is_active());
EXPECT_FALSE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
EXPECT_TRUE(session3->is_active());
EXPECT_FALSE(session3->IsClosed());
// Make sessions 1 and 3 inactive, but keep them open.
// Session 2 still open and active
session1->CloseCreatedStream(spdy_stream1, OK);
EXPECT_EQ(NULL, spdy_stream1.get());
session3->CloseCreatedStream(spdy_stream3, OK);
EXPECT_EQ(NULL, spdy_stream3.get());
EXPECT_FALSE(session1->is_active());
EXPECT_FALSE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
EXPECT_FALSE(session3->is_active());
EXPECT_FALSE(session3->IsClosed());
// Should close session 1 and 3, 2 should be left open
spdy_session_pool_->CloseIdleSessions();
EXPECT_FALSE(session1->is_active());
EXPECT_TRUE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
EXPECT_FALSE(session3->is_active());
EXPECT_TRUE(session3->IsClosed());
// Should not do anything
spdy_session_pool_->CloseIdleSessions();
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
// Make 2 not active
session2->CloseCreatedStream(spdy_stream2, OK);
EXPECT_EQ(NULL, spdy_stream2.get());
EXPECT_FALSE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
// This should close session 2
spdy_session_pool_->CloseIdleSessions();
EXPECT_FALSE(session2->is_active());
EXPECT_TRUE(session2->IsClosed());
}
// Start with max concurrent streams set to 1. Request two streams. Receive a
// settings frame setting max concurrent streams to 2. Have the callback
// release the stream, which releases its reference (the last) to the session.
// Make sure nothing blows up.
// http://crbug.com/57331
TEST_F(SpdySessionSpdy2Test, OnSettings) {
session_deps_.host_resolver->set_synchronous_mode(true);
SettingsMap new_settings;
const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
const uint32 max_concurrent_streams = 2;
new_settings[kSpdySettingsIds1] =
SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
// Set up the socket so we read a SETTINGS frame that raises max concurrent
// streams to 2.
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
MockRead reads[] = {
CreateMockRead(*settings_frame),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
// Initialize the SpdySetting with 1 max concurrent streams.
spdy_session_pool_->http_server_properties()->SetSpdySetting(
test_host_port_pair_,
kSpdySettingsIds1,
SETTINGS_FLAG_PLEASE_PERSIST,
1);
scoped_refptr<SpdySession> session = CreateInitializedSession();
// Create 2 streams. First will succeed. Second will be pending.
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
StreamReleaserCallback stream_releaser;
SpdyStreamRequest request;
ASSERT_EQ(ERR_IO_PENDING,
request.StartRequest(session, test_url_, MEDIUM,
BoundNetLog(),
stream_releaser.MakeCallback(&request)));
EXPECT_EQ(OK, stream_releaser.WaitForResult());
}
// Start with max concurrent streams set to 1 (that is persisted). Receive a
// settings frame setting max concurrent streams to 2 and which also clears the
// persisted data. Verify that persisted data is correct.
TEST_F(SpdySessionSpdy2Test, ClearSettings) {
session_deps_.host_resolver->set_synchronous_mode(true);
SettingsMap new_settings;
const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
const uint32 max_concurrent_streams = 2;
new_settings[kSpdySettingsIds1] =
SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
// Set up the socket so we read a SETTINGS frame that raises max concurrent
// streams to 2 and clears previously persisted data.
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
uint8 flags = SETTINGS_FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS;
test::SetFrameFlags(settings_frame.get(), flags, kSpdyVersion3);
MockRead reads[] = {
CreateMockRead(*settings_frame),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
// Initialize the SpdySetting with 1 max concurrent streams.
spdy_session_pool_->http_server_properties()->SetSpdySetting(
test_host_port_pair_,
kSpdySettingsIds1,
SETTINGS_FLAG_PLEASE_PERSIST,
1);
EXPECT_EQ(1u, spdy_session_pool_->http_server_properties()->GetSpdySettings(
test_host_port_pair_).size());
scoped_refptr<SpdySession> session = CreateInitializedSession();
// Create 2 streams. First will succeed. Second will be pending.
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
StreamReleaserCallback stream_releaser;
SpdyStreamRequest request;
ASSERT_EQ(ERR_IO_PENDING,
request.StartRequest(session, test_url_, MEDIUM,
BoundNetLog(),
stream_releaser.MakeCallback(&request)));
EXPECT_EQ(OK, stream_releaser.WaitForResult());
// Make sure that persisted data is cleared.
EXPECT_EQ(0u, spdy_session_pool_->http_server_properties()->GetSpdySettings(
test_host_port_pair_).size());
// Make sure session's max_concurrent_streams is 2.
EXPECT_EQ(2u, session->max_concurrent_streams());
session = NULL;
}
// Start with max concurrent streams set to 1. Request two streams. When the
// first completes, have the callback close itself, which should trigger the
// second stream creation. Then cancel that one immediately. Don't crash.
// http://crbug.com/63532
TEST_F(SpdySessionSpdy2Test, CancelPendingCreateStream) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
MockConnect connect_data(SYNCHRONOUS, OK);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
// Initialize the SpdySetting with 1 max concurrent streams.
spdy_session_pool_->http_server_properties()->SetSpdySetting(
test_host_port_pair_,
SETTINGS_MAX_CONCURRENT_STREAMS,
SETTINGS_FLAG_PLEASE_PERSIST,
1);
scoped_refptr<SpdySession> session = CreateInitializedSession();
// Create 2 streams. First will succeed. Second will be pending.
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, test_url_, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
// Use scoped_ptr to let us invalidate the memory when we want to, to trigger
// a valgrind error if the callback is invoked when it's not supposed to be.
scoped_ptr<TestCompletionCallback> callback(new TestCompletionCallback);
SpdyStreamRequest request;
ASSERT_EQ(ERR_IO_PENDING,
request.StartRequest(session, test_url_, MEDIUM,
BoundNetLog(),
callback->callback()));
// Release the first one, this will allow the second to be created.
spdy_stream1->Cancel();
EXPECT_EQ(NULL, spdy_stream1.get());
request.CancelRequest();
callback.reset();
// Should not crash when running the pending callback.
MessageLoop::current()->RunUntilIdle();
}
TEST_F(SpdySessionSpdy2Test, SendInitialSettingsOnNewSession) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
SettingsMap settings;
const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
settings[kSpdySettingsIds1] =
SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kMaxConcurrentPushedStreams);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
MockWrite writes[] = {
CreateMockWrite(*settings_frame),
};
StaticSocketDataProvider data(
reads, arraysize(reads), writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
SpdySessionPoolPeer pool_peer(spdy_session_pool_);
pool_peer.EnableSendingInitialSettings(true);
scoped_refptr<SpdySession> session = CreateInitializedSession();
MessageLoop::current()->RunUntilIdle();
EXPECT_TRUE(data.at_write_eof());
}
TEST_F(SpdySessionSpdy2Test, SendSettingsOnNewSession) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
// Create the bogus setting that we want to verify is sent out.
// Note that it will be marked as SETTINGS_FLAG_PERSISTED when sent out. But
// to persist it into the HttpServerProperties, we need to mark as
// SETTINGS_FLAG_PLEASE_PERSIST.
SettingsMap settings;
const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_UPLOAD_BANDWIDTH;
const uint32 kBogusSettingValue = 0xCDCD;
settings[kSpdySettingsIds1] =
SettingsFlagsAndValue(SETTINGS_FLAG_PERSISTED, kBogusSettingValue);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(settings));
MockWrite writes[] = {
CreateMockWrite(*settings_frame),
};
StaticSocketDataProvider data(
reads, arraysize(reads), writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
spdy_session_pool_->http_server_properties()->SetSpdySetting(
test_host_port_pair_,
kSpdySettingsIds1,
SETTINGS_FLAG_PLEASE_PERSIST,
kBogusSettingValue);
scoped_refptr<SpdySession> session = CreateInitializedSession();
MessageLoop::current()->RunUntilIdle();
EXPECT_TRUE(data.at_write_eof());
}
namespace {
// Specifies the style for closing the connection.
enum SpdyPoolCloseSessionsType {
SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
SPDY_POOL_CLOSE_CURRENT_SESSIONS,
SPDY_POOL_CLOSE_IDLE_SESSIONS,
};
// Initialize the SpdySession with socket.
void IPPoolingInitializedSession(
const std::string& group_name,
const scoped_refptr<TransportSocketParams>& transport_params,
HttpNetworkSession* http_session,
SpdySession* session) {
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(group_name,
transport_params, MEDIUM, CompletionCallback(),
http_session->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
}
// This test has three variants, one for each style of closing the connection.
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
// the sessions are closed manually, calling SpdySessionPool::Remove() directly.
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_CURRENT_SESSIONS,
// sessions are closed with SpdySessionPool::CloseCurrentSessions().
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_IDLE_SESSIONS,
// sessions are closed with SpdySessionPool::CloseIdleSessions().
void IPPoolingTest(SpdyPoolCloseSessionsType close_sessions_type) {
const int kTestPort = 80;
struct TestHosts {
std::string url;
std::string name;
std::string iplist;
HostPortProxyPair pair;
AddressList addresses;
} test_hosts[] = {
{ "http:://www.foo.com",
"www.foo.com",
"192.0.2.33,192.168.0.1,192.168.0.5"
},
{ "http://js.foo.com",
"js.foo.com",
"192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33"
},
{ "http://images.foo.com",
"images.foo.com",
"192.168.0.4,192.168.0.3"
},
};
SpdySessionDependencies session_deps(kProtoSPDY2);
session_deps.host_resolver->set_synchronous_mode(true);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_hosts); i++) {
session_deps.host_resolver->rules()->AddIPLiteralRule(
test_hosts[i].name, test_hosts[i].iplist, std::string());
// This test requires that the HostResolver cache be populated. Normal
// code would have done this already, but we do it manually.
HostResolver::RequestInfo info(HostPortPair(test_hosts[i].name, kTestPort));
session_deps.host_resolver->Resolve(
info, &test_hosts[i].addresses, CompletionCallback(), NULL,
BoundNetLog());
// Setup a HostPortProxyPair
test_hosts[i].pair = HostPortProxyPair(
HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct());
}
MockConnect connect_data(SYNCHRONOUS, OK);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps.socket_factory->AddSSLSocketDataProvider(&ssl);
scoped_refptr<HttpNetworkSession> http_session(
SpdySessionDependencies::SpdyCreateSession(&session_deps));
// Setup the first session to the first host.
SpdySessionPool* spdy_session_pool(http_session->spdy_session_pool());
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
scoped_refptr<SpdySession> session =
spdy_session_pool->Get(test_hosts[0].pair, BoundNetLog());
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
HostPortPair test_host_port_pair(test_hosts[0].name, kTestPort);
// Initialize session for the first host.
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair,
MEDIUM,
false,
false,
OnHostResolutionCallback()));
IPPoolingInitializedSession(test_host_port_pair.ToString(),
transport_params, http_session, session);
// TODO(rtenneti): MockClientSocket::GetPeerAddress return's 0 as the port
// number. Fix it to return port 80 and then use GetPeerAddress to AddAlias.
SpdySessionPoolPeer pool_peer(spdy_session_pool);
pool_peer.AddAlias(test_hosts[0].addresses.front(), test_hosts[0].pair);
// Flush the SpdySession::OnReadComplete() task.
MessageLoop::current()->RunUntilIdle();
// The third host has no overlap with the first, so it can't pool IPs.
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
// The second host overlaps with the first, and should IP pool.
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
// Verify that the second host, through a proxy, won't share the IP.
HostPortProxyPair proxy_pair(test_hosts[1].pair.first,
ProxyServer::FromPacString("HTTP http://proxy.foo.com/"));
EXPECT_FALSE(spdy_session_pool->HasSession(proxy_pair));
// Overlap between 2 and 3 does is not transitive to 1.
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
// Create a new session to host 2.
scoped_refptr<SpdySession> session2 =
spdy_session_pool->Get(test_hosts[2].pair, BoundNetLog());
// Verify that we have sessions for everything.
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[0].pair));
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[2].pair));
// Initialize session for host 2.
session_deps.socket_factory->AddSocketDataProvider(&data);
IPPoolingInitializedSession(test_hosts[2].pair.first.ToString(),
transport_params, http_session, session2);
// Grab the session to host 1 and verify that it is the same session
// we got with host 0, and that is a different than host 2's session.
scoped_refptr<SpdySession> session1 =
spdy_session_pool->Get(test_hosts[1].pair, BoundNetLog());
EXPECT_EQ(session.get(), session1.get());
EXPECT_NE(session2.get(), session1.get());
// Initialize session for host 1.
session_deps.socket_factory->AddSocketDataProvider(&data);
IPPoolingInitializedSession(test_hosts[2].pair.first.ToString(),
transport_params, http_session, session2);
// Remove the aliases and observe that we still have a session for host1.
pool_peer.RemoveAliases(test_hosts[0].pair);
pool_peer.RemoveAliases(test_hosts[1].pair);
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
// Expire the host cache
session_deps.host_resolver->GetHostCache()->clear();
EXPECT_TRUE(spdy_session_pool->HasSession(test_hosts[1].pair));
// Cleanup the sessions.
switch (close_sessions_type) {
case SPDY_POOL_CLOSE_SESSIONS_MANUALLY:
spdy_session_pool->Remove(session);
session = NULL;
spdy_session_pool->Remove(session2);
session2 = NULL;
break;
case SPDY_POOL_CLOSE_CURRENT_SESSIONS:
spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
break;
case SPDY_POOL_CLOSE_IDLE_SESSIONS:
GURL url(test_hosts[0].url);
base::WeakPtr<SpdyStream> spdy_stream =
CreateStreamSynchronously(session, url, MEDIUM, BoundNetLog());
GURL url1(test_hosts[1].url);
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session1, url1, MEDIUM, BoundNetLog());
GURL url2(test_hosts[2].url);
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session2, url2, MEDIUM, BoundNetLog());
// Close streams to make spdy_session and spdy_session1 inactive.
session->CloseCreatedStream(spdy_stream, OK);
EXPECT_EQ(NULL, spdy_stream.get());
session1->CloseCreatedStream(spdy_stream1, OK);
EXPECT_EQ(NULL, spdy_stream1.get());
// Check spdy_session and spdy_session1 are not closed.
EXPECT_FALSE(session->is_active());
EXPECT_FALSE(session->IsClosed());
EXPECT_FALSE(session1->is_active());
EXPECT_FALSE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
// Test that calling CloseIdleSessions, does not cause a crash.
// http://crbug.com/181400
spdy_session_pool->CloseIdleSessions();
// Verify spdy_session and spdy_session1 are closed.
EXPECT_FALSE(session->is_active());
EXPECT_TRUE(session->IsClosed());
EXPECT_FALSE(session1->is_active());
EXPECT_TRUE(session1->IsClosed());
EXPECT_TRUE(session2->is_active());
EXPECT_FALSE(session2->IsClosed());
spdy_stream2->Cancel();
EXPECT_EQ(NULL, spdy_stream2.get());
spdy_session_pool->Remove(session2);
session2 = NULL;
break;
}
// Verify that the map is all cleaned up.
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[0].pair));
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[1].pair));
EXPECT_FALSE(spdy_session_pool->HasSession(test_hosts[2].pair));
}
} // namespace
TEST_F(SpdySessionSpdy2Test, IPPooling) {
IPPoolingTest(SPDY_POOL_CLOSE_SESSIONS_MANUALLY);
}
TEST_F(SpdySessionSpdy2Test, IPPoolingCloseCurrentSessions) {
IPPoolingTest(SPDY_POOL_CLOSE_CURRENT_SESSIONS);
}
TEST_F(SpdySessionSpdy2Test, IPPoolingCloseIdleSessions) {
IPPoolingTest(SPDY_POOL_CLOSE_IDLE_SESSIONS);
}
TEST_F(SpdySessionSpdy2Test, ClearSettingsStorageOnIPAddressChanged) {
CreateNetworkSession();
HttpServerProperties* test_http_server_properties =
spdy_session_pool_->http_server_properties();
SettingsFlagsAndValue flags_and_value1(SETTINGS_FLAG_PLEASE_PERSIST, 2);
test_http_server_properties->SetSpdySetting(
test_host_port_pair_,
SETTINGS_MAX_CONCURRENT_STREAMS,
SETTINGS_FLAG_PLEASE_PERSIST,
2);
EXPECT_NE(0u, test_http_server_properties->GetSpdySettings(
test_host_port_pair_).size());
spdy_session_pool_->OnIPAddressChanged();
EXPECT_EQ(0u, test_http_server_properties->GetSpdySettings(
test_host_port_pair_).size());
}
TEST_F(SpdySessionSpdy2Test, CloseSessionOnError) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
MockRead reads[] = {
CreateMockRead(*goaway),
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
CapturingBoundNetLog log;
scoped_refptr<SpdySession> session =
spdy_session_pool_->Get(pair_, log.bound());
EXPECT_TRUE(spdy_session_pool_->HasSession(pair_));
InitializeSession(http_session_.get(), session.get(), test_host_port_pair_);
// Flush the SpdySession::OnReadComplete() task.
MessageLoop::current()->RunUntilIdle();
EXPECT_FALSE(spdy_session_pool_->HasSession(pair_));
// Check that the NetLog was filled reasonably.
net::CapturingNetLog::CapturedEntryList entries;
log.GetEntries(&entries);
EXPECT_LT(0u, entries.size());
// Check that we logged SPDY_SESSION_CLOSE correctly.
int pos = net::ExpectLogContainsSomewhere(
entries, 0,
net::NetLog::TYPE_SPDY_SESSION_CLOSE,
net::NetLog::PHASE_NONE);
CapturingNetLog::CapturedEntry entry = entries[pos];
int error_code = 0;
ASSERT_TRUE(entry.GetNetErrorCode(&error_code));
EXPECT_EQ(ERR_CONNECTION_CLOSED, error_code);
}
TEST_F(SpdySessionSpdy2Test, OutOfOrderSynStreams) {
// Construct the request.
MockConnect connect_data(SYNCHRONOUS, OK);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, HIGHEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req1, 2),
CreateMockWrite(*req2, 1),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
MockRead reads[] = {
CreateMockRead(*resp1, 3),
CreateMockRead(*body1, 4),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
MockRead(ASYNC, 0, 7) // EOF
};
session_deps_.host_resolver->set_synchronous_mode(true);
StaticSocketDataProvider data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url, LOWEST, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session, url, HIGHEST, BoundNetLog());
ASSERT_TRUE(spdy_stream2.get() != NULL);
EXPECT_EQ(0u, spdy_stream2->stream_id());
test::StreamDelegateDoNothing delegate2(spdy_stream2);
spdy_stream2->SetDelegate(&delegate2);
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url.scheme();
(*headers)["host"] = url.host();
(*headers)["url"] = url.path();
(*headers)["version"] = "HTTP/1.1";
scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
*headers2 = *headers;
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream2->set_spdy_headers(headers2.Pass());
EXPECT_TRUE(spdy_stream2->HasUrl());
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(NULL, spdy_stream1.get());
EXPECT_EQ(NULL, spdy_stream2.get());
EXPECT_EQ(3u, delegate1.stream_id());
EXPECT_EQ(1u, delegate2.stream_id());
}
TEST_F(SpdySessionSpdy2Test, CancelStream) {
MockConnect connect_data(SYNCHRONOUS, OK);
// Request 1, at HIGHEST priority, will be cancelled before it writes data.
// Request 2, at LOWEST priority, will be a full request and will be id 1.
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req2, 0),
};
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(1, true));
MockRead reads[] = {
CreateMockRead(*resp2, 1),
CreateMockRead(*body2, 2),
MockRead(ASYNC, 0, 3) // EOF
};
session_deps_.host_resolver->set_synchronous_mode(true);
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, HIGHEST, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
GURL url2("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session, url2, LOWEST, BoundNetLog());
ASSERT_TRUE(spdy_stream2.get() != NULL);
EXPECT_EQ(0u, spdy_stream2->stream_id());
test::StreamDelegateDoNothing delegate2(spdy_stream2);
spdy_stream2->SetDelegate(&delegate2);
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
*headers2 = *headers;
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream2->set_spdy_headers(headers2.Pass());
EXPECT_TRUE(spdy_stream2->HasUrl());
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
EXPECT_EQ(0u, spdy_stream1->stream_id());
spdy_stream1->Cancel();
EXPECT_EQ(NULL, spdy_stream1.get());
EXPECT_EQ(0u, delegate1.stream_id());
data.RunFor(1);
EXPECT_EQ(0u, delegate1.stream_id());
EXPECT_EQ(1u, delegate2.stream_id());
spdy_stream2->Cancel();
EXPECT_EQ(NULL, spdy_stream2.get());
}
TEST_F(SpdySessionSpdy2Test, CloseSessionWithTwoCreatedStreams) {
session_deps_.host_resolver->set_synchronous_mode(true);
// Test that if a sesion is closed with two created streams pending,
// it does not crash. http://crbug.com/139518
MockConnect connect_data(SYNCHRONOUS, OK);
// No actual data will be sent.
MockWrite writes[] = {
MockWrite(ASYNC, 0, 1) // EOF
};
MockRead reads[] = {
MockRead(ASYNC, 0, 0) // EOF
};
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, HIGHEST, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
GURL url2("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream2 =
CreateStreamSynchronously(session, url2, LOWEST, BoundNetLog());
ASSERT_TRUE(spdy_stream2.get() != NULL);
EXPECT_EQ(0u, spdy_stream2->stream_id());
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
*headers2 = *headers;
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
spdy_stream2->set_spdy_headers(headers2.Pass());
EXPECT_TRUE(spdy_stream2->HasUrl());
test::StreamDelegateDoNothing delegate2(spdy_stream2);
spdy_stream2->SetDelegate(&delegate2);
spdy_stream1->SendRequest(false);
spdy_stream2->SendRequest(false);
// Ensure that the streams have not yet been activated and assigned an id.
EXPECT_EQ(0u, spdy_stream1->stream_id());
EXPECT_EQ(0u, spdy_stream2->stream_id());
// Ensure we don't crash while closing the session.
session->CloseSessionOnError(ERR_ABORTED, true, std::string());
EXPECT_EQ(NULL, spdy_stream1.get());
EXPECT_EQ(NULL, spdy_stream2.get());
EXPECT_TRUE(delegate1.StreamIsClosed());
EXPECT_TRUE(delegate2.StreamIsClosed());
}
TEST_F(SpdySessionSpdy2Test, VerifyDomainAuthentication) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
// No actual data will be sent.
MockWrite writes[] = {
MockWrite(ASYNC, 0, 1) // EOF
};
MockRead reads[] = {
MockRead(ASYNC, 0, 0) // EOF
};
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
// Load a cert that is valid for:
// www.example.org
// mail.example.org
// www.example.com
base::FilePath certs_dir = GetTestCertsDirectory();
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
ssl.cert = test_cert;
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = GetSession(pair_);
SSLConfig ssl_config;
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair_,
MEDIUM,
false,
false,
OnHostResolutionCallback()));
scoped_refptr<SOCKSSocketParams> socks_params;
scoped_refptr<HttpProxySocketParams> http_proxy_params;
scoped_refptr<SSLSocketParams> ssl_params(
new SSLSocketParams(transport_params,
socks_params,
http_proxy_params,
ProxyServer::SCHEME_DIRECT,
test_host_port_pair_,
ssl_config,
0,
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(test_host_port_pair_.ToString(),
ssl_params, MEDIUM, CompletionCallback(),
http_session_->GetSSLSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.com"));
EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
}
TEST_F(SpdySessionSpdy2Test, ConnectionPooledWithTlsChannelId) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
// No actual data will be sent.
MockWrite writes[] = {
MockWrite(ASYNC, 0, 1) // EOF
};
MockRead reads[] = {
MockRead(ASYNC, 0, 0) // EOF
};
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
// Load a cert that is valid for:
// www.example.org
// mail.example.org
// www.example.com
base::FilePath certs_dir = GetTestCertsDirectory();
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(certs_dir, "spdy_pooling.pem"));
ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
ssl.channel_id_sent = true;
ssl.cert = test_cert;
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = GetSession(pair_);
SSLConfig ssl_config;
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair_,
MEDIUM,
false,
false,
OnHostResolutionCallback()));
scoped_refptr<SOCKSSocketParams> socks_params;
scoped_refptr<HttpProxySocketParams> http_proxy_params;
scoped_refptr<SSLSocketParams> ssl_params(
new SSLSocketParams(transport_params,
socks_params,
http_proxy_params,
ProxyServer::SCHEME_DIRECT,
test_host_port_pair_,
ssl_config,
0,
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(test_host_port_pair_.ToString(),
ssl_params, MEDIUM, CompletionCallback(),
http_session_->GetSSLSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), false, OK));
EXPECT_TRUE(session->VerifyDomainAuthentication("www.example.org"));
EXPECT_TRUE(session->VerifyDomainAuthentication("mail.example.org"));
EXPECT_FALSE(session->VerifyDomainAuthentication("mail.example.com"));
EXPECT_FALSE(session->VerifyDomainAuthentication("mail.google.com"));
}
TEST_F(SpdySessionSpdy2Test, CloseTwoStalledCreateStream) {
// TODO(rtenneti): Define a helper class/methods and move the common code in
// this file.
MockConnect connect_data(SYNCHRONOUS, OK);
SettingsMap new_settings;
const SpdySettingsIds kSpdySettingsIds1 = SETTINGS_MAX_CONCURRENT_STREAMS;
const uint32 max_concurrent_streams = 1;
new_settings[kSpdySettingsIds1] =
SettingsFlagsAndValue(SETTINGS_FLAG_NONE, max_concurrent_streams);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
scoped_ptr<SpdyFrame> req2(ConstructSpdyGet(NULL, 0, false, 3, LOWEST));
scoped_ptr<SpdyFrame> req3(ConstructSpdyGet(NULL, 0, false, 5, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req1, 1),
CreateMockWrite(*req2, 4),
CreateMockWrite(*req3, 7),
};
// Set up the socket so we read a SETTINGS frame that sets max concurrent
// streams to 1.
scoped_ptr<SpdyFrame> settings_frame(ConstructSpdySettings(new_settings));
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 3));
scoped_ptr<SpdyFrame> body2(ConstructSpdyBodyFrame(3, true));
scoped_ptr<SpdyFrame> resp3(ConstructSpdyGetSynReply(NULL, 0, 5));
scoped_ptr<SpdyFrame> body3(ConstructSpdyBodyFrame(5, true));
MockRead reads[] = {
CreateMockRead(*settings_frame),
CreateMockRead(*resp1, 2),
CreateMockRead(*body1, 3),
CreateMockRead(*resp2, 5),
CreateMockRead(*body2, 6),
CreateMockRead(*resp3, 8),
CreateMockRead(*body3, 9),
MockRead(ASYNC, 0, 10) // EOF
};
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
// Read the settings frame.
data.RunFor(1);
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, LOWEST, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
TestCompletionCallback callback2;
GURL url2("http://www.google.com");
SpdyStreamRequest request2;
ASSERT_EQ(ERR_IO_PENDING,
request2.StartRequest(session, url2, LOWEST,
BoundNetLog(),
callback2.callback()));
TestCompletionCallback callback3;
GURL url3("http://www.google.com");
SpdyStreamRequest request3;
ASSERT_EQ(ERR_IO_PENDING,
request3.StartRequest(session, url3, LOWEST,
BoundNetLog(),
callback3.callback()));
EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
scoped_ptr<SpdyHeaderBlock> headers2(new SpdyHeaderBlock);
*headers2 = *headers;
scoped_ptr<SpdyHeaderBlock> headers3(new SpdyHeaderBlock);
*headers3 = *headers;
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream1->SendRequest(false);
// Run until 1st stream is closed and 2nd one is opened.
EXPECT_EQ(0u, delegate1.stream_id());
data.RunFor(3);
// Pump loop for SpdySession::ProcessPendingStreamRequests().
MessageLoop::current()->RunUntilIdle();
EXPECT_EQ(NULL, spdy_stream1.get());
EXPECT_EQ(1u, delegate1.stream_id());
EXPECT_EQ(2u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
base::WeakPtr<SpdyStream> stream2 = request2.ReleaseStream();
test::StreamDelegateDoNothing delegate2(stream2);
stream2->SetDelegate(&delegate2);
stream2->set_spdy_headers(headers2.Pass());
EXPECT_TRUE(stream2->HasUrl());
stream2->SendRequest(false);
// Run until 2nd stream is closed.
EXPECT_EQ(0u, delegate2.stream_id());
data.RunFor(3);
EXPECT_EQ(NULL, stream2.get());
EXPECT_EQ(3u, delegate2.stream_id());
EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
base::WeakPtr<SpdyStream> stream3 = request3.ReleaseStream();
ASSERT_TRUE(stream3.get() != NULL);
test::StreamDelegateDoNothing delegate3(stream3);
stream3->SetDelegate(&delegate3);
stream3->set_spdy_headers(headers3.Pass());
EXPECT_TRUE(stream3->HasUrl());
stream3->SendRequest(false);
EXPECT_EQ(0u, delegate3.stream_id());
data.RunFor(4);
EXPECT_EQ(NULL, stream3.get());
EXPECT_EQ(5u, delegate3.stream_id());
EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
}
TEST_F(SpdySessionSpdy2Test, CancelTwoStalledCreateStream) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
MockConnect connect_data(SYNCHRONOUS, OK);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
// Initialize the SpdySetting with 1 max concurrent streams.
spdy_session_pool_->http_server_properties()->SetSpdySetting(
test_host_port_pair_,
SETTINGS_MAX_CONCURRENT_STREAMS,
SETTINGS_FLAG_PLEASE_PERSIST,
1);
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, LOWEST, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
TestCompletionCallback callback2;
GURL url2("http://www.google.com");
SpdyStreamRequest request2;
ASSERT_EQ(ERR_IO_PENDING,
request2.StartRequest(session, url2, LOWEST,
BoundNetLog(),
callback2.callback()));
TestCompletionCallback callback3;
GURL url3("http://www.google.com");
SpdyStreamRequest request3;
ASSERT_EQ(ERR_IO_PENDING,
request3.StartRequest(session, url3, LOWEST,
BoundNetLog(),
callback3.callback()));
EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(2u, session->pending_create_stream_queues(LOWEST));
// Cancel the first stream, this will allow the second stream to be created.
EXPECT_TRUE(spdy_stream1.get() != NULL);
spdy_stream1->Cancel();
EXPECT_EQ(NULL, spdy_stream1.get());
callback2.WaitForResult();
EXPECT_EQ(2u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
// Cancel the second stream, this will allow the third stream to be created.
base::WeakPtr<SpdyStream> spdy_stream2 = request2.ReleaseStream();
spdy_stream2->Cancel();
EXPECT_EQ(NULL, spdy_stream2.get());
EXPECT_EQ(1u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
// Cancel the third stream.
base::WeakPtr<SpdyStream> spdy_stream3 = request3.ReleaseStream();
spdy_stream3->Cancel();
EXPECT_EQ(NULL, spdy_stream3.get());
EXPECT_EQ(0u, session->num_active_streams() + session->num_created_streams());
EXPECT_EQ(0u, session->pending_create_stream_queues(LOWEST));
}
TEST_F(SpdySessionSpdy2Test, NeedsCredentials) {
MockConnect connect_data(SYNCHRONOUS, OK);
MockRead reads[] = {
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
ssl.channel_id_sent = true;
ssl.protocol_negotiated = kProtoSPDY2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
CreateNetworkSession();
const std::string kTestHost("www.foo.com");
const int kTestPort = 80;
HostPortPair test_host_port_pair(kTestHost, kTestPort);
HostPortProxyPair pair(test_host_port_pair, ProxyServer::Direct());
scoped_refptr<SpdySession> session = GetSession(pair);
SSLConfig ssl_config;
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(test_host_port_pair,
MEDIUM,
false,
false,
OnHostResolutionCallback()));
scoped_refptr<SOCKSSocketParams> socks_params;
scoped_refptr<HttpProxySocketParams> http_proxy_params;
scoped_refptr<SSLSocketParams> ssl_params(
new SSLSocketParams(transport_params,
socks_params,
http_proxy_params,
ProxyServer::SCHEME_DIRECT,
test_host_port_pair,
ssl_config,
0,
false,
false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(test_host_port_pair.ToString(),
ssl_params, MEDIUM, CompletionCallback(),
http_session_->GetSSLSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, session->InitializeWithSocket(connection.release(), true, OK));
EXPECT_FALSE(session->NeedsCredentials());
// Flush the SpdySession::OnReadComplete() task.
MessageLoop::current()->RunUntilIdle();
spdy_session_pool_->Remove(session);
}
// Test that SpdySession::DoRead reads data from the socket without yielding.
// This test makes 32k - 1 bytes of data available on the socket for reading. It
// then verifies that it has read all the available data without yielding.
TEST_F(SpdySessionSpdy2Test, ReadDataWithoutYielding) {
MockConnect connect_data(SYNCHRONOUS, OK);
BufferedSpdyFramer framer(2, false);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
};
// Build buffer of size kMaxReadBytes / 4 (-spdy_data_frame_size).
ASSERT_EQ(32 * 1024, kMaxReadBytes);
const int kPayloadSize =
kMaxReadBytes / 4 - framer.GetControlFrameHeaderSize();
TestDataStream test_stream;
scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize));
char* payload_data = payload->data();
test_stream.GetBytes(payload_data, kPayloadSize);
scoped_ptr<SpdyFrame> partial_data_frame(
framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE));
scoped_ptr<SpdyFrame> finish_data_frame(
framer.CreateDataFrame(1, payload_data, kPayloadSize - 1, DATA_FLAG_FIN));
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
// Write 1 byte less than kMaxReadBytes to check that DoRead reads up to 32k
// bytes.
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*partial_data_frame, 2),
CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS),
CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS),
CreateMockRead(*finish_data_frame, 5, SYNCHRONOUS),
MockRead(ASYNC, 0, 6) // EOF
};
// Create SpdySession and SpdyStream and send the request.
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.host_resolver->set_synchronous_mode(true);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream1->SendRequest(false);
// Set up the TaskObserver to verify SpdySession::DoRead doesn't post a task.
SpdySessionTestTaskObserver observer("spdy_session.cc", "DoRead");
// Run until 1st read.
EXPECT_EQ(0u, delegate1.stream_id());
data.RunFor(2);
EXPECT_EQ(1u, delegate1.stream_id());
EXPECT_EQ(0u, observer.executed_count());
// Read all the data and verify SpdySession::DoRead has not posted a task.
data.RunFor(4);
EXPECT_EQ(NULL, spdy_stream1.get());
// Verify task observer's executed_count is zero, which indicates DoRead read
// all the available data.
EXPECT_EQ(0u, observer.executed_count());
EXPECT_TRUE(data.at_write_eof());
EXPECT_TRUE(data.at_read_eof());
}
// Test that SpdySession::DoRead yields while reading the data. This test makes
// 32k + 1 bytes of data available on the socket for reading. It then verifies
// that DoRead has yielded even though there is data available for it to read
// (i.e, socket()->Read didn't return ERR_IO_PENDING during socket reads).
TEST_F(SpdySessionSpdy2Test, TestYieldingDuringReadData) {
MockConnect connect_data(SYNCHRONOUS, OK);
BufferedSpdyFramer framer(2, false);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
};
// Build buffer of size kMaxReadBytes / 4 (-spdy_data_frame_size).
ASSERT_EQ(32 * 1024, kMaxReadBytes);
const int kPayloadSize =
kMaxReadBytes / 4 - framer.GetControlFrameHeaderSize();
TestDataStream test_stream;
scoped_refptr<net::IOBuffer> payload(new net::IOBuffer(kPayloadSize));
char* payload_data = payload->data();
test_stream.GetBytes(payload_data, kPayloadSize);
scoped_ptr<SpdyFrame> partial_data_frame(
framer.CreateDataFrame(1, payload_data, kPayloadSize, DATA_FLAG_NONE));
scoped_ptr<SpdyFrame> finish_data_frame(
framer.CreateDataFrame(1, "h", 1, DATA_FLAG_FIN));
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
// Write 1 byte more than kMaxReadBytes to check that DoRead yields.
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*partial_data_frame, 2),
CreateMockRead(*partial_data_frame, 3, SYNCHRONOUS),
CreateMockRead(*partial_data_frame, 4, SYNCHRONOUS),
CreateMockRead(*partial_data_frame, 5, SYNCHRONOUS),
CreateMockRead(*finish_data_frame, 6, SYNCHRONOUS),
MockRead(ASYNC, 0, 7) // EOF
};
// Create SpdySession and SpdyStream and send the request.
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.host_resolver->set_synchronous_mode(true);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream1->SendRequest(false);
// Set up the TaskObserver to verify SpdySession::DoRead posts a task.
SpdySessionTestTaskObserver observer("spdy_session.cc", "DoRead");
// Run until 1st read.
EXPECT_EQ(0u, delegate1.stream_id());
data.RunFor(2);
EXPECT_EQ(1u, delegate1.stream_id());
EXPECT_EQ(0u, observer.executed_count());
// Read all the data and verify SpdySession::DoRead has posted a task.
data.RunFor(6);
EXPECT_EQ(NULL, spdy_stream1.get());
// Verify task observer's executed_count is 1, which indicates DoRead has
// posted only one task and thus yielded though there is data available for it
// to read.
EXPECT_EQ(1u, observer.executed_count());
EXPECT_TRUE(data.at_write_eof());
EXPECT_TRUE(data.at_read_eof());
}
// Test that SpdySession::DoRead() tests interactions of yielding + async,
// by doing the following MockReads.
//
// MockRead of SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K
// ASYNC 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 8K, SYNCHRONOUS 2K.
//
// The above reads 26K synchronously. Since that is less that 32K, we will
// attempt to read again. However, that DoRead() will return ERR_IO_PENDING
// (because of async read), so DoRead() will yield. When we come back, DoRead()
// will read the results from the async read, and rest of the data
// synchronously.
TEST_F(SpdySessionSpdy2Test, TestYieldingDuringAsyncReadData) {
MockConnect connect_data(SYNCHRONOUS, OK);
BufferedSpdyFramer framer(2, false);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
};
// Build buffer of size kMaxReadBytes / 4 (-spdy_data_frame_size).
ASSERT_EQ(32 * 1024, kMaxReadBytes);
TestDataStream test_stream;
const int kEightKPayloadSize =
kMaxReadBytes / 4 - framer.GetControlFrameHeaderSize();
scoped_refptr<net::IOBuffer> eightk_payload(
new net::IOBuffer(kEightKPayloadSize));
char* eightk_payload_data = eightk_payload->data();
test_stream.GetBytes(eightk_payload_data, kEightKPayloadSize);
// Build buffer of 2k size.
TestDataStream test_stream2;
const int kTwoKPayloadSize = kEightKPayloadSize - 6 * 1024;
scoped_refptr<net::IOBuffer> twok_payload(
new net::IOBuffer(kTwoKPayloadSize));
char* twok_payload_data = twok_payload->data();
test_stream2.GetBytes(twok_payload_data, kTwoKPayloadSize);
scoped_ptr<SpdyFrame> eightk_data_frame(framer.CreateDataFrame(
1, eightk_payload_data, kEightKPayloadSize, DATA_FLAG_NONE));
scoped_ptr<SpdyFrame> twok_data_frame(framer.CreateDataFrame(
1, twok_payload_data, kTwoKPayloadSize, DATA_FLAG_NONE));
scoped_ptr<SpdyFrame> finish_data_frame(framer.CreateDataFrame(
1, "h", 1, DATA_FLAG_FIN));
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*eightk_data_frame, 2),
CreateMockRead(*eightk_data_frame, 3, SYNCHRONOUS),
CreateMockRead(*eightk_data_frame, 4, SYNCHRONOUS),
CreateMockRead(*twok_data_frame, 5, SYNCHRONOUS),
CreateMockRead(*eightk_data_frame, 6, ASYNC),
CreateMockRead(*eightk_data_frame, 7, SYNCHRONOUS),
CreateMockRead(*eightk_data_frame, 8, SYNCHRONOUS),
CreateMockRead(*eightk_data_frame, 9, SYNCHRONOUS),
CreateMockRead(*twok_data_frame, 10, SYNCHRONOUS),
CreateMockRead(*finish_data_frame, 11, SYNCHRONOUS),
MockRead(ASYNC, 0, 12) // EOF
};
// Create SpdySession and SpdyStream and send the request.
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.host_resolver->set_synchronous_mode(true);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, MEDIUM, BoundNetLog());
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
test::StreamDelegateDoNothing delegate1(spdy_stream1);
spdy_stream1->SetDelegate(&delegate1);
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream1->SendRequest(false);
// Set up the TaskObserver to monitor SpdySession::DoRead posting of tasks.
SpdySessionTestTaskObserver observer("spdy_session.cc", "DoRead");
// Run until 1st read.
EXPECT_EQ(0u, delegate1.stream_id());
data.RunFor(2);
EXPECT_EQ(1u, delegate1.stream_id());
EXPECT_EQ(0u, observer.executed_count());
// Read all the data and verify SpdySession::DoRead has posted a task.
data.RunFor(12);
EXPECT_EQ(NULL, spdy_stream1.get());
// Verify task observer's executed_count is 1, which indicates DoRead has
// posted only one task and thus yielded though there is data available for
// it to read.
EXPECT_EQ(1u, observer.executed_count());
EXPECT_TRUE(data.at_write_eof());
EXPECT_TRUE(data.at_read_eof());
}
// Send a GoAway frame when SpdySession is in DoLoop. If scoped_refptr to
// <SpdySession> is deleted from SpdySession::DoLoop(), we get a crash because
// GoAway could delete the SpdySession from the SpdySessionPool and the last
// reference to SpdySession.
TEST_F(SpdySessionSpdy2Test, GoAwayWhileInDoLoop) {
MockConnect connect_data(SYNCHRONOUS, OK);
BufferedSpdyFramer framer(2, false);
scoped_ptr<SpdyFrame> req1(ConstructSpdyGet(NULL, 0, false, 1, MEDIUM));
MockWrite writes[] = {
CreateMockWrite(*req1, 0),
};
scoped_ptr<SpdyFrame> resp1(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> body1(ConstructSpdyBodyFrame(1, true));
scoped_ptr<SpdyFrame> goaway(ConstructSpdyGoAway());
MockRead reads[] = {
CreateMockRead(*resp1, 1),
CreateMockRead(*body1, 2),
CreateMockRead(*goaway, 3),
MockRead(ASYNC, 0, 4) // EOF
};
// Create SpdySession and SpdyStream and send the request.
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
data.set_connect_data(connect_data);
session_deps_.host_resolver->set_synchronous_mode(true);
session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
session_deps_.deterministic_socket_factory->AddSSLSocketDataProvider(&ssl);
CreateDeterministicNetworkSession();
scoped_refptr<SpdySession> session = CreateInitializedSession();
GURL url1("http://www.google.com");
base::WeakPtr<SpdyStream> spdy_stream1 =
CreateStreamSynchronously(session, url1, MEDIUM, BoundNetLog());
session = NULL;
ASSERT_TRUE(spdy_stream1.get() != NULL);
EXPECT_EQ(0u, spdy_stream1->stream_id());
scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)["method"] = "GET";
(*headers)["scheme"] = url1.scheme();
(*headers)["host"] = url1.host();
(*headers)["url"] = url1.path();
(*headers)["version"] = "HTTP/1.1";
spdy_stream1->set_spdy_headers(headers.Pass());
EXPECT_TRUE(spdy_stream1->HasUrl());
spdy_stream1->SendRequest(false);
// Run until 1st read.
EXPECT_EQ(0u, spdy_stream1->stream_id());
data.RunFor(1);
EXPECT_EQ(1u, spdy_stream1->stream_id());
// Only references to SpdySession are held by DoLoop and
// SpdySessionPool. If DoLoop doesn't hold the reference, we get a
// crash if SpdySession is deleted from the SpdySessionPool.
// Run until GoAway.
data.RunFor(4);
EXPECT_EQ(NULL, spdy_stream1.get());
EXPECT_TRUE(data.at_write_eof());
EXPECT_TRUE(data.at_read_eof());
}
// Within this framework, a SpdySession should be initialized with
// flow control disabled and with protocol version 2.
TEST_F(SpdySessionSpdy2Test, ProtocolNegotiation) {
session_deps_.host_resolver->set_synchronous_mode(true);
MockConnect connect_data(SYNCHRONOUS, OK);
MockRead reads[] = {
MockRead(SYNCHRONOUS, 0, 0) // EOF
};
StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0);
data.set_connect_data(connect_data);
session_deps_.socket_factory->AddSocketDataProvider(&data);
CreateNetworkSession();
scoped_refptr<SpdySession> session = GetSession(pair_);
EXPECT_EQ(SpdySession::FLOW_CONTROL_NONE, session->flow_control_state());
EXPECT_TRUE(session->buffered_spdy_framer_ == NULL);
EXPECT_EQ(0, session->session_send_window_size_);
EXPECT_EQ(0, session->session_recv_window_size_);
EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
InitializeSession(
http_session_.get(), session.get(), test_host_port_pair_);
EXPECT_EQ(SpdySession::FLOW_CONTROL_NONE, session->flow_control_state());
EXPECT_EQ(kSpdyVersion2, session->buffered_spdy_framer_->protocol_version());
EXPECT_EQ(0, session->session_send_window_size_);
EXPECT_EQ(0, session->session_recv_window_size_);
EXPECT_EQ(0, session->session_unacked_recv_window_bytes_);
}
} // namespace net