blob: 544f6867763140ade3838b63ccf2f05ab3078a20 [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_http_stream.h"
#include "crypto/ec_private_key.h"
#include "crypto/ec_signature_creator.h"
#include "crypto/signature_creator.h"
#include "net/base/asn1_util.h"
#include "net/base/default_server_bound_cert_store.h"
#include "net/base/upload_data_stream.h"
#include "net/base/upload_element_reader.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_test_util_spdy2.h"
#include "testing/gtest/include/gtest/gtest.h"
using namespace net::test_spdy2;
namespace net {
class SpdyHttpStreamSpdy2Test : public testing::Test {
public:
OrderedSocketData* data() { return data_.get(); }
protected:
SpdyHttpStreamSpdy2Test() {}
virtual void TearDown() {
crypto::ECSignatureCreator::SetFactoryForTesting(NULL);
UploadDataStream::ResetMergeChunks();
MessageLoop::current()->RunUntilIdle();
}
void set_merge_chunks(bool merge) {
UploadDataStream::set_merge_chunks(merge);
}
int InitSession(MockRead* reads, size_t reads_count,
MockWrite* writes, size_t writes_count,
HostPortPair& host_port_pair) {
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
data_.reset(new OrderedSocketData(reads, reads_count,
writes, writes_count));
session_deps_.socket_factory->AddSocketDataProvider(data_.get());
http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
transport_params_ = new TransportSocketParams(host_port_pair,
MEDIUM, false, false,
OnHostResolutionCallback());
TestCompletionCallback callback;
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(ERR_IO_PENDING,
connection->Init(host_port_pair.ToString(),
transport_params_,
MEDIUM,
callback.callback(),
http_session_->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
EXPECT_EQ(OK, callback.WaitForResult());
return session_->InitializeWithSocket(connection.release(), false, OK);
}
SpdySessionDependencies session_deps_;
scoped_ptr<OrderedSocketData> data_;
scoped_refptr<HttpNetworkSession> http_session_;
scoped_refptr<SpdySession> session_;
scoped_refptr<TransportSocketParams> transport_params_;
};
TEST_F(SpdyHttpStreamSpdy2Test, SendRequest) {
scoped_ptr<SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req.get(), 1),
};
scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
MockRead reads[] = {
CreateMockRead(*resp, 2),
MockRead(SYNCHRONOUS, 0, 3) // EOF
};
HostPortPair host_port_pair("www.google.com", 80);
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
host_port_pair));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL("http://www.google.com/");
TestCompletionCallback callback;
HttpResponseInfo response;
HttpRequestHeaders headers;
BoundNetLog net_log;
scoped_ptr<SpdyHttpStream> http_stream(
new SpdyHttpStream(session_.get(), true));
ASSERT_EQ(
OK,
http_stream->InitializeStream(&request, net_log, CompletionCallback()));
EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
callback.callback()));
EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
// This triggers the MockWrite and read 2
callback.WaitForResult();
// This triggers read 3. The empty read causes the session to shut down.
data()->CompleteRead();
// Because we abandoned the stream, we don't expect to find a session in the
// pool anymore.
EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
EXPECT_TRUE(data()->at_read_eof());
EXPECT_TRUE(data()->at_write_eof());
}
TEST_F(SpdyHttpStreamSpdy2Test, SendChunkedPost) {
set_merge_chunks(false);
scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
scoped_ptr<SpdyFrame> chunk2(ConstructSpdyBodyFrame(1, true));
MockWrite writes[] = {
CreateMockWrite(*req.get(), 0),
CreateMockWrite(*chunk1, 1), // POST upload frames
CreateMockWrite(*chunk2, 2),
};
scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
MockRead reads[] = {
CreateMockRead(*resp, 3),
CreateMockRead(*chunk1, 4),
CreateMockRead(*chunk2, 5),
MockRead(SYNCHRONOUS, 0, 6) // EOF
};
HostPortPair host_port_pair("www.google.com", 80);
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
host_port_pair));
UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.google.com/");
request.upload_data_stream = &upload_stream;
ASSERT_EQ(OK, upload_stream.InitSync());
TestCompletionCallback callback;
HttpResponseInfo response;
HttpRequestHeaders headers;
BoundNetLog net_log;
SpdyHttpStream http_stream(session_.get(), true);
ASSERT_EQ(
OK,
http_stream.InitializeStream(&request, net_log, CompletionCallback()));
EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest(
headers, &response, callback.callback()));
EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
// This triggers the MockWrite and read 2
callback.WaitForResult();
// This triggers read 3. The empty read causes the session to shut down.
data()->CompleteRead();
MessageLoop::current()->RunUntilIdle();
// Because we abandoned the stream, we don't expect to find a session in the
// pool anymore.
EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
EXPECT_TRUE(data()->at_read_eof());
EXPECT_TRUE(data()->at_write_eof());
}
// Test to ensure the SpdyStream state machine does not get confused when a
// chunk becomes available while a write is pending.
TEST_F(SpdyHttpStreamSpdy2Test, DelayedSendChunkedPost) {
set_merge_chunks(false);
const char kUploadData1[] = "12345678";
const int kUploadData1Size = arraysize(kUploadData1)-1;
scoped_ptr<SpdyFrame> req(ConstructChunkedSpdyPost(NULL, 0));
scoped_ptr<SpdyFrame> chunk1(ConstructSpdyBodyFrame(1, false));
scoped_ptr<SpdyFrame> chunk2(
ConstructSpdyBodyFrame(1, kUploadData1, kUploadData1Size, false));
scoped_ptr<SpdyFrame> chunk3(ConstructSpdyBodyFrame(1, true));
MockWrite writes[] = {
CreateMockWrite(*req.get(), 0),
CreateMockWrite(*chunk1, 1), // POST upload frames
CreateMockWrite(*chunk2, 2),
CreateMockWrite(*chunk3, 3),
};
scoped_ptr<SpdyFrame> resp(ConstructSpdyPostSynReply(NULL, 0));
MockRead reads[] = {
CreateMockRead(*resp, 4),
CreateMockRead(*chunk1, 5),
CreateMockRead(*chunk2, 6),
CreateMockRead(*chunk3, 7),
MockRead(ASYNC, 0, 8) // EOF
};
HostPortPair host_port_pair("www.google.com", 80);
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
DeterministicSocketData data(reads, arraysize(reads),
writes, arraysize(writes));
DeterministicMockClientSocketFactory* socket_factory =
session_deps_.deterministic_socket_factory.get();
socket_factory->AddSocketDataProvider(&data);
http_session_ = SpdySessionDependencies::SpdyCreateSessionDeterministic(
&session_deps_);
session_ = http_session_->spdy_session_pool()->Get(pair, BoundNetLog());
transport_params_ = new TransportSocketParams(host_port_pair,
MEDIUM, false, false,
OnHostResolutionCallback());
TestCompletionCallback callback;
scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
EXPECT_EQ(ERR_IO_PENDING,
connection->Init(host_port_pair.ToString(),
transport_params_,
MEDIUM,
callback.callback(),
http_session_->GetTransportSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL),
BoundNetLog()));
callback.WaitForResult();
EXPECT_EQ(OK,
session_->InitializeWithSocket(connection.release(), false, OK));
UploadDataStream upload_stream(UploadDataStream::CHUNKED, 0);
HttpRequestInfo request;
request.method = "POST";
request.url = GURL("http://www.google.com/");
request.upload_data_stream = &upload_stream;
ASSERT_EQ(OK, upload_stream.InitSync());
upload_stream.AppendChunk(kUploadData, kUploadDataSize, false);
BoundNetLog net_log;
scoped_ptr<SpdyHttpStream> http_stream(
new SpdyHttpStream(session_.get(), true));
ASSERT_EQ(OK, http_stream->InitializeStream(&request,
net_log,
CompletionCallback()));
HttpRequestHeaders headers;
HttpResponseInfo response;
// This will attempt to Write() the initial request and headers, which will
// complete asynchronously.
EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
callback.callback()));
EXPECT_TRUE(http_session_->spdy_session_pool()->HasSession(pair));
// Complete the initial request write and the first chunk.
data.RunFor(2);
ASSERT_TRUE(callback.have_result());
EXPECT_GT(callback.WaitForResult(), 0);
// Now append the final two chunks which will enqueue two more writes.
upload_stream.AppendChunk(kUploadData1, kUploadData1Size, false);
upload_stream.AppendChunk(kUploadData, kUploadDataSize, true);
// Finish writing all the chunks.
data.RunFor(2);
// Read response headers.
data.RunFor(1);
ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback()));
// Read and check |chunk1| response.
data.RunFor(1);
scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize));
ASSERT_EQ(kUploadDataSize,
http_stream->ReadResponseBody(buf1,
kUploadDataSize,
callback.callback()));
EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize));
// Read and check |chunk2| response.
data.RunFor(1);
scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size));
ASSERT_EQ(kUploadData1Size,
http_stream->ReadResponseBody(buf2,
kUploadData1Size,
callback.callback()));
EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size));
// Read and check |chunk3| response.
data.RunFor(1);
scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize));
ASSERT_EQ(kUploadDataSize,
http_stream->ReadResponseBody(buf3,
kUploadDataSize,
callback.callback()));
EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize));
// Finish reading the |EOF|.
data.RunFor(1);
ASSERT_TRUE(response.headers.get());
ASSERT_EQ(200, response.headers->response_code());
EXPECT_TRUE(data.at_read_eof());
EXPECT_TRUE(data.at_write_eof());
}
// Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
TEST_F(SpdyHttpStreamSpdy2Test, SpdyURLTest) {
const char * const full_url = "http://www.google.com/foo?query=what#anchor";
const char * const base_url = "http://www.google.com/foo?query=what";
scoped_ptr<SpdyFrame> req(ConstructSpdyGet(base_url, false, 1, LOWEST));
MockWrite writes[] = {
CreateMockWrite(*req.get(), 1),
};
scoped_ptr<SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1));
MockRead reads[] = {
CreateMockRead(*resp, 2),
MockRead(SYNCHRONOUS, 0, 3) // EOF
};
HostPortPair host_port_pair("www.google.com", 80);
HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
host_port_pair));
HttpRequestInfo request;
request.method = "GET";
request.url = GURL(full_url);
TestCompletionCallback callback;
HttpResponseInfo response;
HttpRequestHeaders headers;
BoundNetLog net_log;
scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true));
ASSERT_EQ(
OK,
http_stream->InitializeStream(&request, net_log, CompletionCallback()));
EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response,
callback.callback()));
const SpdyHeaderBlock& spdy_header =
http_stream->stream()->spdy_headers();
if (spdy_header.find("url") != spdy_header.end())
EXPECT_EQ("/foo?query=what", spdy_header.find("url")->second);
else
FAIL() << "No url is set in spdy_header!";
// This triggers the MockWrite and read 2
callback.WaitForResult();
// This triggers read 3. The empty read causes the session to shut down.
data()->CompleteRead();
// Because we abandoned the stream, we don't expect to find a session in the
// pool anymore.
EXPECT_FALSE(http_session_->spdy_session_pool()->HasSession(pair));
EXPECT_TRUE(data()->at_read_eof());
EXPECT_TRUE(data()->at_write_eof());
}
// TODO(willchan): Write a longer test for SpdyStream that exercises all
// methods.
} // namespace net