| Index: net/spdy/spdy_websocket_stream_unittest.cc
|
| diff --git a/net/spdy/spdy_websocket_stream_unittest.cc b/net/spdy/spdy_websocket_stream_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..05f3110b60a0bc723aabee1ced47bcb3a263b60d
|
| --- /dev/null
|
| +++ b/net/spdy/spdy_websocket_stream_unittest.cc
|
| @@ -0,0 +1,433 @@
|
| +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "net/spdy/spdy_websocket_stream.h"
|
| +#include "net/base/completion_callback.h"
|
| +#include "net/proxy/proxy_server.h"
|
| +#include "net/spdy/spdy_protocol.h"
|
| +#include "net/spdy/spdy_session.h"
|
| +#include "net/spdy/spdy_test_util.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace net {
|
| +
|
| +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeReq(
|
| + const char* const url,
|
| + const char* const origin,
|
| + const char* const protocol,
|
| + bool compressed,
|
| + spdy::SpdyStreamId stream_id,
|
| + RequestPriority request_priority) {
|
| + const SpdyHeaderInfo kSynStreamHeader = {
|
| + spdy::SYN_STREAM,
|
| + stream_id,
|
| + 0, // Associated stream ID
|
| + net::ConvertRequestPriorityToSpdyPriority(request_priority),
|
| + spdy::CONTROL_FLAG_NONE,
|
| + compressed,
|
| + spdy::INVALID, // Status
|
| + NULL, // Data,
|
| + 0, // Length
|
| + spdy::DATA_FLAG_NONE
|
| + };
|
| +
|
| + const char* const headers[] = {
|
| + "url",
|
| + url,
|
| + "origin",
|
| + origin,
|
| + "protocol",
|
| + protocol,
|
| + };
|
| + int header_size = arraysize(headers) / 2;
|
| + if (protocol == NULL)
|
| + header_size -= 1;
|
| +
|
| + return ConstructSpdyPacket(
|
| + kSynStreamHeader,
|
| + NULL,
|
| + 0,
|
| + headers,
|
| + header_size);
|
| +}
|
| +
|
| +spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeResp(
|
| + const char* const url,
|
| + const char* const origin,
|
| + const char* const protocol,
|
| + bool compressed,
|
| + spdy::SpdyStreamId stream_id,
|
| + RequestPriority request_priority) {
|
| + const SpdyHeaderInfo kSynReplyHeader = {
|
| + spdy::SYN_REPLY,
|
| + stream_id,
|
| + 0, // Associated stream ID
|
| + net::ConvertRequestPriorityToSpdyPriority(request_priority),
|
| + spdy::CONTROL_FLAG_NONE,
|
| + false,
|
| + spdy::INVALID, // Status
|
| + NULL, // Data
|
| + 0, // Length
|
| + spdy::DATA_FLAG_NONE
|
| + };
|
| +
|
| + const char* const headers[] = {
|
| + "sec-websocket-location",
|
| + url,
|
| + "sec-websocket-origin",
|
| + origin,
|
| + "sec-websocket-protocol",
|
| + protocol,
|
| + };
|
| + int header_size = arraysize(headers) / 2;
|
| + if (protocol == NULL)
|
| + header_size -= 1;
|
| +
|
| + return ConstructSpdyPacket(
|
| + kSynReplyHeader,
|
| + NULL,
|
| + 0,
|
| + headers,
|
| + header_size);
|
| +}
|
| +
|
| +spdy::SpdyFrame* ConstructSpdyWebSocketFrame(
|
| + const char* data,
|
| + int len,
|
| + spdy::SpdyStreamId stream_id,
|
| + bool fin) {
|
| + spdy::SpdyFramer framer;
|
| + return framer.CreateDataFrame(
|
| + stream_id, data, len,
|
| + fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE);
|
| +}
|
| +
|
| +struct SpdyWebSocketStreamEvent {
|
| + enum EventType {
|
| + EVENT_CREATED,
|
| + EVENT_SENT_HEADERS, EVENT_RECEIVED_HEADER,
|
| + EVENT_SENT_DATA, EVENT_RECEIVED_DATA,
|
| + EVENT_CLOSE,
|
| + };
|
| + SpdyWebSocketStreamEvent(EventType type,
|
| + SpdyWebSocketStream* stream,
|
| + const spdy::SpdyHeaderBlock& headers,
|
| + int result,
|
| + const std::string& data)
|
| + : event_type(type),
|
| + stream(stream),
|
| + headers(headers),
|
| + result(result),
|
| + data(data) {}
|
| +
|
| + EventType event_type;
|
| + SpdyWebSocketStream* stream;
|
| + spdy::SpdyHeaderBlock headers;
|
| + int result;
|
| + std::string data;
|
| +};
|
| +
|
| +class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStreamDelegate {
|
| + public:
|
| + explicit SpdyWebSocketStreamEventRecorder(CompletionCallback* callback)
|
| + : on_created_(NULL),
|
| + on_sent_headers_(NULL),
|
| + on_received_header_(NULL),
|
| + on_sent_data_(NULL),
|
| + on_received_data_(NULL),
|
| + on_close_(NULL),
|
| + callback_(callback) {}
|
| + virtual ~SpdyWebSocketStreamEventRecorder() {
|
| + delete on_created_;
|
| + delete on_sent_headers_;
|
| + delete on_received_header_;
|
| + delete on_sent_data_;
|
| + delete on_received_data_;
|
| + delete on_close_;
|
| + }
|
| +
|
| + void SetOnCreated(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_created_ = callback;
|
| + }
|
| + void SetOnSentHeaders(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_sent_headers_ = callback;
|
| + }
|
| + void SetOnReceivedHeader(
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_received_header_ = callback;
|
| + }
|
| + void SetOnSentData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_sent_data_ = callback;
|
| + }
|
| + void SetOnReceivedData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_received_data_ = callback;
|
| + }
|
| + void SetOnClose(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) {
|
| + on_close_ = callback;
|
| + }
|
| +
|
| + virtual void OnCreatedSpdyStream(SpdyWebSocketStream* stream, int result) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED,
|
| + stream,
|
| + spdy::SpdyHeaderBlock(),
|
| + result,
|
| + std::string()));
|
| + if (on_created_)
|
| + on_created_->Run(&events_.back());
|
| + }
|
| +
|
| + virtual void OnSentSpdyHeaders(SpdyWebSocketStream* stream) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
|
| + stream,
|
| + spdy::SpdyHeaderBlock(),
|
| + OK,
|
| + std::string()));
|
| + if (on_sent_data_)
|
| + on_sent_data_->Run(&events_.back());
|
| + }
|
| + virtual int OnReceivedSpdyResponseHeader(
|
| + SpdyWebSocketStream* stream,
|
| + const spdy::SpdyHeaderBlock& headers,
|
| + int status) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(
|
| + SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
|
| + stream,
|
| + headers,
|
| + status,
|
| + std::string()));
|
| + if (on_received_header_)
|
| + on_received_header_->Run(&events_.back());
|
| + return status;
|
| + }
|
| + virtual void OnSentSpdyData(
|
| + SpdyWebSocketStream* stream, int amount_sent) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(
|
| + SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
|
| + stream,
|
| + spdy::SpdyHeaderBlock(),
|
| + amount_sent,
|
| + std::string()));
|
| + if (on_sent_data_)
|
| + on_sent_data_->Run(&events_.back());
|
| + }
|
| + virtual void OnReceivedSpdyData(
|
| + SpdyWebSocketStream* stream, const char* data, int length) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(
|
| + SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
|
| + stream,
|
| + spdy::SpdyHeaderBlock(),
|
| + length,
|
| + std::string(data, length)));
|
| + if (on_received_data_)
|
| + on_received_data_->Run(&events_.back());
|
| + }
|
| + virtual void OnCloseSpdyStream(SpdyWebSocketStream* stream) {
|
| + events_.push_back(
|
| + SpdyWebSocketStreamEvent(
|
| + SpdyWebSocketStreamEvent::EVENT_CLOSE,
|
| + stream,
|
| + spdy::SpdyHeaderBlock(),
|
| + OK,
|
| + std::string()));
|
| + if (on_close_)
|
| + on_close_->Run(&events_.back());
|
| + if (callback_)
|
| + callback_->Run(net::OK);
|
| + }
|
| +
|
| + const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const {
|
| + return events_;
|
| + }
|
| +
|
| + private:
|
| + std::vector<SpdyWebSocketStreamEvent> events_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_created_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_headers_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_header_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_data_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_data_;
|
| + Callback1<SpdyWebSocketStreamEvent*>::Type* on_close_;
|
| + CompletionCallback* callback_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder);
|
| +};
|
| +
|
| +class SpdyWebSocketStreamTest : public testing::Test {
|
| + public:
|
| + OrderedSocketData* data() { return data_; }
|
| +
|
| + void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) {
|
| + std::string frame;
|
| + frame.append(1, '\0');
|
| + frame.append("hello");
|
| + frame.append(1, '\xff');
|
| + event->stream->SendData(frame.data(), frame.size());
|
| + }
|
| +
|
| + void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) {
|
| + static const char kClosingFrame[] = "\xff\0";
|
| + event->stream->SendData(kClosingFrame, 2);
|
| + }
|
| +
|
| + void DoClose(SpdyWebSocketStreamEvent* event) {
|
| + event->stream->Close();
|
| + }
|
| +
|
| + protected:
|
| + SpdyWebSocketStreamTest() {}
|
| + virtual ~SpdyWebSocketStreamTest() {}
|
| +
|
| + virtual void SetUp() {}
|
| + virtual void TearDown() {
|
| + MessageLoop::current()->RunAllPending();
|
| + }
|
| +
|
| + void EnableCompression(bool enabled) {
|
| + spdy::SpdyFramer::set_enable_compression_default(enabled);
|
| + }
|
| + 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_ = 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, http_session_->mutable_spdy_settings(), BoundNetLog());
|
| + tcp_params_ = new TCPSocketParams(host_port_pair.host(),
|
| + host_port_pair.port(),
|
| + MEDIUM, GURL(), false);
|
| + TestCompletionCallback callback;
|
| + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle);
|
| + EXPECT_EQ(ERR_IO_PENDING,
|
| + connection->Init(host_port_pair.ToString(), tcp_params_, MEDIUM,
|
| + &callback, http_session_->tcp_socket_pool(),
|
| + BoundNetLog()));
|
| + EXPECT_EQ(OK, callback.WaitForResult());
|
| + return session_->InitializeWithSocket(connection.release(), false, OK);
|
| + }
|
| +
|
| + SpdySessionDependencies session_deps_;
|
| + scoped_refptr<OrderedSocketData> data_;
|
| + scoped_refptr<HttpNetworkSession> http_session_;
|
| + scoped_refptr<SpdySession> session_;
|
| + scoped_refptr<TCPSocketParams> tcp_params_;
|
| +};
|
| +
|
| +TEST_F(SpdyWebSocketStreamTest, Echo) {
|
| + EnableCompression(false);
|
| + SpdySession::SetSSLMode(false);
|
| +
|
| + spdy::SpdyStreamId stream_id = 1;
|
| +
|
| + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyWebSocketHandshakeReq(
|
| + "ws://example.com/echo",
|
| + "http://example.com/wsdemo",
|
| + NULL,
|
| + false,
|
| + stream_id,
|
| + HIGHEST));
|
| + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyWebSocketHandshakeResp(
|
| + "ws://example.com/echo",
|
| + "http://example.com/wsdemo",
|
| + NULL,
|
| + false,
|
| + stream_id,
|
| + HIGHEST));
|
| +
|
| + static const char* const msg_frame = "\0hello\xff";
|
| + scoped_ptr<spdy::SpdyFrame> msg(ConstructSpdyWebSocketFrame(
|
| + msg_frame, 7, stream_id, false));
|
| +
|
| + static const char* const closing_frame = "\xff\0";
|
| + scoped_ptr<spdy::SpdyFrame> closing(ConstructSpdyWebSocketFrame(
|
| + closing_frame, 2, stream_id, false));
|
| +
|
| + MockWrite writes[] = {
|
| + CreateMockWrite(*req.get(), 1),
|
| + CreateMockWrite(*msg.get(), 3),
|
| + CreateMockWrite(*closing.get(), 5)
|
| + };
|
| +
|
| + MockRead reads[] = {
|
| + CreateMockRead(*resp.get(), 2),
|
| + CreateMockRead(*msg.get(), 4),
|
| + // Skip sequence 6 to notify closing has been sent.
|
| + CreateMockRead(*closing.get(), 7),
|
| + MockRead(false, 0, 8) // EOF
|
| + };
|
| +
|
| + HostPortPair host_port_pair("example.com", 80);
|
| + HostPortProxyPair pair(host_port_pair, ProxyServer::Direct());
|
| + EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes),
|
| + host_port_pair));
|
| +
|
| + TestCompletionCallback callback;
|
| +
|
| + scoped_ptr<SpdyWebSocketStreamEventRecorder> delegate(
|
| + new SpdyWebSocketStreamEventRecorder(&callback));
|
| + // Necessary for NewCallback.
|
| + SpdyWebSocketStreamTest* test = this;
|
| + delegate->SetOnReceivedHeader(
|
| + NewCallback(test, &SpdyWebSocketStreamTest::DoSendHelloFrame));
|
| + delegate->SetOnReceivedData(
|
| + NewCallback(test, &SpdyWebSocketStreamTest::DoSendClosingFrame));
|
| +
|
| + scoped_ptr<SpdyWebSocketStream> websocket_stream(
|
| + new SpdyWebSocketStream(session_, delegate.get()));
|
| +
|
| + BoundNetLog net_log;
|
| + GURL url("ws://example.com/echo");
|
| + ASSERT_EQ(OK, websocket_stream->InitializeStream(url, HIGHEST, net_log));
|
| +
|
| + EXPECT_EQ(stream_id, websocket_stream->stream_id());
|
| +
|
| +
|
| + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock);
|
| + (*headers)["url"] = "ws://example.com/echo";
|
| + (*headers)["origin"] = "http://example.com/wsdemo";
|
| +
|
| + websocket_stream->SendRequest(headers);
|
| +
|
| + callback.WaitForResult();
|
| +
|
| + const std::vector<SpdyWebSocketStreamEvent>& events =
|
| + delegate->GetSeenEvents();
|
| + ASSERT_EQ(8U, events.size());
|
| +
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED,
|
| + events[0].event_type);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS,
|
| + events[1].event_type);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
|
| + events[2].event_type);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
|
| + events[3].event_type);
|
| + EXPECT_EQ(7, events[3].result);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
|
| + events[4].event_type);
|
| + EXPECT_EQ(7, events[4].result);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA,
|
| + events[5].event_type);
|
| + EXPECT_EQ(2, events[5].result);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA,
|
| + events[6].event_type);
|
| + EXPECT_EQ(2, events[6].result);
|
| + EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE,
|
| + events[7].event_type);
|
| +
|
| + EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(pair));
|
| + EXPECT_TRUE(data()->at_read_eof());
|
| + EXPECT_TRUE(data()->at_write_eof());
|
| +}
|
| +
|
| +} // namespace net
|
|
|