blob: 56af9e02f2563fdfa1f5805732f81c53697d40a8 [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 "base/memory/ref_counted.h"
#include "net/base/completion_callback.h"
#include "net/base/net_log_unittest.h"
#include "net/spdy/buffered_spdy_framer.h"
#include "net/spdy/spdy_stream.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_test_util_spdy3.h"
#include "testing/gtest/include/gtest/gtest.h"
using namespace net::test_spdy3;
// TODO(ukai): factor out common part with spdy_http_stream_unittest.cc
//
namespace net {
namespace {
class TestSpdyStreamDelegate : public SpdyStream::Delegate {
public:
TestSpdyStreamDelegate(SpdyStream* stream,
IOBufferWithSize* buf,
const CompletionCallback& callback)
: stream_(stream),
buf_(buf),
callback_(callback),
send_headers_completed_(false),
response_(new SpdyHeaderBlock),
data_sent_(0),
closed_(false) {}
virtual ~TestSpdyStreamDelegate() {}
virtual bool OnSendHeadersComplete(int status) {
send_headers_completed_ = true;
return true;
}
virtual int OnSendBody() {
ADD_FAILURE() << "OnSendBody should not be called";
return ERR_UNEXPECTED;
}
virtual int OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
ADD_FAILURE() << "OnSendBodyComplete should not be called";
return ERR_UNEXPECTED;
}
virtual int OnResponseReceived(const SpdyHeaderBlock& response,
base::Time response_time,
int status) {
EXPECT_TRUE(send_headers_completed_);
*response_ = response;
if (buf_) {
EXPECT_EQ(ERR_IO_PENDING,
stream_->WriteStreamData(buf_.get(), buf_->size(),
DATA_FLAG_NONE));
}
return status;
}
virtual void OnDataReceived(const char* buffer, int bytes) {
received_data_ += std::string(buffer, bytes);
}
virtual void OnDataSent(int length) {
data_sent_ += length;
}
virtual void OnClose(int status) {
closed_ = true;
CompletionCallback callback = callback_;
callback_.Reset();
callback.Run(OK);
}
virtual void set_chunk_callback(net::ChunkCallback *) {}
bool send_headers_completed() const { return send_headers_completed_; }
const linked_ptr<SpdyHeaderBlock>& response() const {
return response_;
}
const std::string& received_data() const { return received_data_; }
int data_sent() const { return data_sent_; }
bool closed() const { return closed_; }
private:
SpdyStream* stream_;
scoped_refptr<IOBufferWithSize> buf_;
CompletionCallback callback_;
bool send_headers_completed_;
linked_ptr<SpdyHeaderBlock> response_;
std::string received_data_;
int data_sent_;
bool closed_;
};
SpdyFrame* ConstructSpdyBodyFrame(const char* data, int length) {
BufferedSpdyFramer framer(3);
return framer.CreateDataFrame(1, data, length, DATA_FLAG_NONE);
}
} // anonymous namespace
class SpdyStreamSpdy3Test : public testing::Test {
protected:
SpdyStreamSpdy3Test() {
}
scoped_refptr<SpdySession> CreateSpdySession() {
HostPortPair host_port_pair("www.google.com", 80);
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
scoped_refptr<SpdySession> session(
session_->spdy_session_pool()->Get(pair, BoundNetLog()));
return session;
}
virtual void SetUp() {
SpdySession::set_default_protocol(kProtoSPDY3);
}
virtual void TearDown() {
MessageLoop::current()->RunAllPending();
}
scoped_refptr<HttpNetworkSession> session_;
private:
SpdyTestStateHelper spdy_state_;
};
TEST_F(SpdyStreamSpdy3Test, SendDataAfterOpen) {
SpdySessionDependencies session_deps;
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
const SpdyHeaderInfo kSynStartHeader = {
SYN_STREAM,
1,
0,
net::ConvertRequestPriorityToSpdyPriority(LOWEST),
0,
CONTROL_FLAG_NONE,
false,
INVALID,
NULL,
0,
DATA_FLAG_NONE
};
static const char* const kGetHeaders[] = {
":method",
"GET",
":scheme",
"http",
":host",
"www.google.com",
":path",
"/",
":version",
"HTTP/1.1",
};
scoped_ptr<SpdyFrame> req(
ConstructSpdyPacket(
kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
scoped_ptr<SpdyFrame> msg(
ConstructSpdyBodyFrame("\0hello!\xff", 8));
MockWrite writes[] = {
CreateMockWrite(*req),
CreateMockWrite(*msg),
};
writes[0].sequence_number = 0;
writes[1].sequence_number = 2;
scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> echo(
ConstructSpdyBodyFrame("\0hello!\xff", 8));
MockRead reads[] = {
CreateMockRead(*resp),
CreateMockRead(*echo),
MockRead(ASYNC, 0, 0), // EOF
};
reads[0].sequence_number = 1;
reads[1].sequence_number = 3;
reads[2].sequence_number = 4;
scoped_ptr<OrderedSocketData> data(
new OrderedSocketData(reads, arraysize(reads),
writes, arraysize(writes)));
MockConnect connect_data(SYNCHRONOUS, OK);
data->set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(data.get());
scoped_refptr<SpdySession> session(CreateSpdySession());
const char* kStreamUrl = "http://www.google.com/";
GURL url(kStreamUrl);
HostPortPair host_port_pair("www.google.com", 80);
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(host_port_pair, LOWEST, false, false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
LOWEST, CompletionCallback(),
session_->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
session->InitializeWithSocket(connection.release(), false, OK);
scoped_refptr<SpdyStream> stream;
ASSERT_EQ(
OK,
session->CreateStream(url, LOWEST, &stream, BoundNetLog(),
CompletionCallback()));
scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
memcpy(buf->data(), "\0hello!\xff", 8);
TestCompletionCallback callback;
scoped_ptr<TestSpdyStreamDelegate> delegate(
new TestSpdyStreamDelegate(stream.get(), buf.get(), callback.callback()));
stream->SetDelegate(delegate.get());
EXPECT_FALSE(stream->HasUrl());
linked_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)[":method"] = "GET";
(*headers)[":scheme"] = url.scheme();
(*headers)[":host"] = url.host();
(*headers)[":path"] = url.path();
(*headers)[":version"] = "HTTP/1.1";
stream->set_spdy_headers(headers);
EXPECT_TRUE(stream->HasUrl());
EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_TRUE(delegate->send_headers_completed());
EXPECT_EQ("200", (*delegate->response())[":status"]);
EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
EXPECT_EQ(8, delegate->data_sent());
EXPECT_TRUE(delegate->closed());
}
TEST_F(SpdyStreamSpdy3Test, PushedStream) {
const char kStreamUrl[] = "http://www.google.com/";
SpdySessionDependencies session_deps;
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
MockRead reads[] = {
MockRead(ASYNC, 0, 0), // EOF
};
scoped_ptr<OrderedSocketData> data(
new OrderedSocketData(reads, arraysize(reads), NULL, 0));
MockConnect connect_data(SYNCHRONOUS, OK);
data->set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(data.get());
HostPortPair host_port_pair("www.google.com", 80);
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(host_port_pair, LOWEST, false, false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
LOWEST, CompletionCallback(),
session_->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
spdy_session->InitializeWithSocket(connection.release(), false, OK);
BoundNetLog net_log;
// Conjure up a stream.
scoped_refptr<SpdyStream> stream = new SpdyStream(spdy_session,
2,
true,
net_log);
EXPECT_FALSE(stream->response_received());
EXPECT_FALSE(stream->HasUrl());
// Set a couple of headers.
SpdyHeaderBlock response;
GURL url(kStreamUrl);
response[":host"] = url.host();
response[":scheme"] = url.scheme();
response[":path"] = url.path();
stream->OnResponseReceived(response);
// Send some basic headers.
SpdyHeaderBlock headers;
response[":status"] = "200";
response[":version"] = "OK";
stream->OnHeaders(headers);
stream->set_response_received();
EXPECT_TRUE(stream->response_received());
EXPECT_TRUE(stream->HasUrl());
EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
}
TEST_F(SpdyStreamSpdy3Test, StreamError) {
SpdySessionDependencies session_deps;
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps);
SpdySessionPoolPeer pool_peer_(session_->spdy_session_pool());
const SpdyHeaderInfo kSynStartHeader = {
SYN_STREAM,
1,
0,
net::ConvertRequestPriorityToSpdyPriority(LOWEST),
0,
CONTROL_FLAG_NONE,
false,
INVALID,
NULL,
0,
DATA_FLAG_NONE
};
static const char* const kGetHeaders[] = {
":method",
"GET",
":scheme",
"http",
":host",
"www.google.com",
":path",
"/",
":version",
"HTTP/1.1",
};
scoped_ptr<SpdyFrame> req(
ConstructSpdyPacket(
kSynStartHeader, NULL, 0, kGetHeaders, arraysize(kGetHeaders) / 2));
scoped_ptr<SpdyFrame> msg(
ConstructSpdyBodyFrame("\0hello!\xff", 8));
MockWrite writes[] = {
CreateMockWrite(*req),
CreateMockWrite(*msg),
};
writes[0].sequence_number = 0;
writes[1].sequence_number = 2;
scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
scoped_ptr<SpdyFrame> echo(
ConstructSpdyBodyFrame("\0hello!\xff", 8));
MockRead reads[] = {
CreateMockRead(*resp),
CreateMockRead(*echo),
MockRead(ASYNC, 0, 0), // EOF
};
reads[0].sequence_number = 1;
reads[1].sequence_number = 3;
reads[2].sequence_number = 4;
net::CapturingBoundNetLog log(net::CapturingNetLog::kUnbounded);
scoped_ptr<OrderedSocketData> data(
new OrderedSocketData(reads, arraysize(reads),
writes, arraysize(writes)));
MockConnect connect_data(SYNCHRONOUS, OK);
data->set_connect_data(connect_data);
session_deps.socket_factory->AddSocketDataProvider(data.get());
scoped_refptr<SpdySession> session(CreateSpdySession());
const char* kStreamUrl = "http://www.google.com/";
GURL url(kStreamUrl);
HostPortPair host_port_pair("www.google.com", 80);
scoped_refptr<TransportSocketParams> transport_params(
new TransportSocketParams(host_port_pair, LOWEST, false, false));
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(OK, connection->Init(host_port_pair.ToString(), transport_params,
LOWEST, CompletionCallback(),
session_->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
log.bound()));
session->InitializeWithSocket(connection.release(), false, OK);
scoped_refptr<SpdyStream> stream;
ASSERT_EQ(
OK,
session->CreateStream(url, LOWEST, &stream, log.bound(),
CompletionCallback()));
scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(8));
memcpy(buf->data(), "\0hello!\xff", 8);
TestCompletionCallback callback;
scoped_ptr<TestSpdyStreamDelegate> delegate(
new TestSpdyStreamDelegate(stream.get(), buf.get(), callback.callback()));
stream->SetDelegate(delegate.get());
EXPECT_FALSE(stream->HasUrl());
linked_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock);
(*headers)[":method"] = "GET";
(*headers)[":scheme"] = url.scheme();
(*headers)[":host"] = url.host();
(*headers)[":path"] = url.path();
(*headers)[":version"] = "HTTP/1.1";
stream->set_spdy_headers(headers);
EXPECT_TRUE(stream->HasUrl());
EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
EXPECT_EQ(ERR_IO_PENDING, stream->SendRequest(true));
const SpdyStreamId stream_id = stream->stream_id();
EXPECT_EQ(OK, callback.WaitForResult());
EXPECT_TRUE(delegate->send_headers_completed());
EXPECT_EQ("200", (*delegate->response())[":status"]);
EXPECT_EQ("HTTP/1.1", (*delegate->response())[":version"]);
EXPECT_EQ(std::string("\0hello!\xff", 8), delegate->received_data());
EXPECT_EQ(8, delegate->data_sent());
EXPECT_TRUE(delegate->closed());
// Check that the NetLog was filled reasonably.
net::CapturingNetLog::EntryList entries;
log.GetEntries(&entries);
EXPECT_LT(0u, entries.size());
// Check that we logged SPDY_STREAM_ERROR correctly.
int pos = net::ExpectLogContainsSomewhere(
entries, 0,
net::NetLog::TYPE_SPDY_STREAM_ERROR,
net::NetLog::PHASE_NONE);
CapturingNetLog::Entry entry = entries[pos];
NetLogSpdyStreamErrorParameter* request_params =
static_cast<NetLogSpdyStreamErrorParameter*>(
entry.extra_parameters.get());
EXPECT_EQ(stream_id, request_params->stream_id());
}
} // namespace net