| // 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/socket_stream/socket_stream.h" | |
| #include <string> | |
| #include <vector> | |
| #include "base/bind.h" | |
| #include "base/bind_helpers.h" | |
| #include "base/callback.h" | |
| #include "base/utf_string_conversions.h" | |
| #include "net/base/auth.h" | |
| #include "net/base/mock_host_resolver.h" | |
| #include "net/base/net_log.h" | |
| #include "net/base/net_log_unittest.h" | |
| #include "net/base/test_completion_callback.h" | |
| #include "net/http/http_network_session.h" | |
| #include "net/proxy/proxy_service.h" | |
| #include "net/socket/socket_test_util.h" | |
| #include "net/url_request/url_request_test_util.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "testing/platform_test.h" | |
| namespace net { | |
| namespace { | |
| struct SocketStreamEvent { | |
| enum EventType { | |
| EVENT_START_OPEN_CONNECTION, EVENT_CONNECTED, EVENT_SENT_DATA, | |
| EVENT_RECEIVED_DATA, EVENT_CLOSE, EVENT_AUTH_REQUIRED, EVENT_ERROR, | |
| }; | |
| SocketStreamEvent(EventType type, | |
| SocketStream* socket_stream, | |
| int num, | |
| const std::string& str, | |
| AuthChallengeInfo* auth_challenge_info, | |
| int error) | |
| : event_type(type), socket(socket_stream), number(num), data(str), | |
| auth_info(auth_challenge_info), error_code(error) {} | |
| EventType event_type; | |
| SocketStream* socket; | |
| int number; | |
| std::string data; | |
| scoped_refptr<AuthChallengeInfo> auth_info; | |
| int error_code; | |
| }; | |
| class SocketStreamEventRecorder : public SocketStream::Delegate { | |
| public: | |
| explicit SocketStreamEventRecorder(const CompletionCallback& callback) | |
| : callback_(callback) {} | |
| virtual ~SocketStreamEventRecorder() {} | |
| void SetOnStartOpenConnection( | |
| const base::Callback<int(SocketStreamEvent*)>& callback) { | |
| on_start_open_connection_ = callback; | |
| } | |
| void SetOnConnected( | |
| const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_connected_ = callback; | |
| } | |
| void SetOnSentData( | |
| const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_sent_data_ = callback; | |
| } | |
| void SetOnReceivedData( | |
| const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_received_data_ = callback; | |
| } | |
| void SetOnClose(const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_close_ = callback; | |
| } | |
| void SetOnAuthRequired( | |
| const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_auth_required_ = callback; | |
| } | |
| void SetOnError(const base::Callback<void(SocketStreamEvent*)>& callback) { | |
| on_error_ = callback; | |
| } | |
| virtual int OnStartOpenConnection( | |
| SocketStream* socket, | |
| const CompletionCallback& callback) OVERRIDE { | |
| connection_callback_ = callback; | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| socket, 0, std::string(), NULL, OK)); | |
| if (!on_start_open_connection_.is_null()) | |
| return on_start_open_connection_.Run(&events_.back()); | |
| return OK; | |
| } | |
| virtual void OnConnected(SocketStream* socket, | |
| int num_pending_send_allowed) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_CONNECTED, | |
| socket, num_pending_send_allowed, std::string(), | |
| NULL, OK)); | |
| if (!on_connected_.is_null()) | |
| on_connected_.Run(&events_.back()); | |
| } | |
| virtual void OnSentData(SocketStream* socket, | |
| int amount_sent) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_SENT_DATA, socket, | |
| amount_sent, std::string(), NULL, OK)); | |
| if (!on_sent_data_.is_null()) | |
| on_sent_data_.Run(&events_.back()); | |
| } | |
| virtual void OnReceivedData(SocketStream* socket, | |
| const char* data, int len) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_RECEIVED_DATA, socket, len, | |
| std::string(data, len), NULL, OK)); | |
| if (!on_received_data_.is_null()) | |
| on_received_data_.Run(&events_.back()); | |
| } | |
| virtual void OnClose(SocketStream* socket) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_CLOSE, socket, 0, | |
| std::string(), NULL, OK)); | |
| if (!on_close_.is_null()) | |
| on_close_.Run(&events_.back()); | |
| if (!callback_.is_null()) | |
| callback_.Run(OK); | |
| } | |
| virtual void OnAuthRequired(SocketStream* socket, | |
| AuthChallengeInfo* auth_info) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_AUTH_REQUIRED, socket, 0, | |
| std::string(), auth_info, OK)); | |
| if (!on_auth_required_.is_null()) | |
| on_auth_required_.Run(&events_.back()); | |
| } | |
| virtual void OnError(const SocketStream* socket, int error) OVERRIDE { | |
| events_.push_back( | |
| SocketStreamEvent(SocketStreamEvent::EVENT_ERROR, NULL, 0, | |
| std::string(), NULL, error)); | |
| if (!on_error_.is_null()) | |
| on_error_.Run(&events_.back()); | |
| if (!callback_.is_null()) | |
| callback_.Run(error); | |
| } | |
| void DoClose(SocketStreamEvent* event) { | |
| event->socket->Close(); | |
| } | |
| void DoRestartWithAuth(SocketStreamEvent* event) { | |
| VLOG(1) << "RestartWithAuth username=" << credentials_.username() | |
| << " password=" << credentials_.password(); | |
| event->socket->RestartWithAuth(credentials_); | |
| } | |
| void SetAuthInfo(const AuthCredentials& credentials) { | |
| credentials_ = credentials; | |
| } | |
| void CompleteConnection(int result) { | |
| connection_callback_.Run(result); | |
| } | |
| const std::vector<SocketStreamEvent>& GetSeenEvents() const { | |
| return events_; | |
| } | |
| private: | |
| std::vector<SocketStreamEvent> events_; | |
| base::Callback<int(SocketStreamEvent*)> on_start_open_connection_; | |
| base::Callback<void(SocketStreamEvent*)> on_connected_; | |
| base::Callback<void(SocketStreamEvent*)> on_sent_data_; | |
| base::Callback<void(SocketStreamEvent*)> on_received_data_; | |
| base::Callback<void(SocketStreamEvent*)> on_close_; | |
| base::Callback<void(SocketStreamEvent*)> on_auth_required_; | |
| base::Callback<void(SocketStreamEvent*)> on_error_; | |
| const CompletionCallback callback_; | |
| CompletionCallback connection_callback_; | |
| AuthCredentials credentials_; | |
| DISALLOW_COPY_AND_ASSIGN(SocketStreamEventRecorder); | |
| }; | |
| class TestURLRequestContextWithProxy : public TestURLRequestContext { | |
| public: | |
| explicit TestURLRequestContextWithProxy(const std::string& proxy) | |
| : TestURLRequestContext(true) { | |
| context_storage_.set_proxy_service(ProxyService::CreateFixed(proxy)); | |
| Init(); | |
| } | |
| virtual ~TestURLRequestContextWithProxy() {} | |
| }; | |
| class TestSocketStreamNetworkDelegate : public TestNetworkDelegate { | |
| public: | |
| TestSocketStreamNetworkDelegate() | |
| : before_connect_result_(OK) {} | |
| virtual ~TestSocketStreamNetworkDelegate() {} | |
| virtual int OnBeforeSocketStreamConnect( | |
| SocketStream* stream, | |
| const CompletionCallback& callback) OVERRIDE { | |
| return before_connect_result_; | |
| } | |
| void SetBeforeConnectResult(int result) { | |
| before_connect_result_ = result; | |
| } | |
| private: | |
| int before_connect_result_; | |
| }; | |
| } // namespace | |
| class SocketStreamTest : public PlatformTest { | |
| public: | |
| virtual ~SocketStreamTest() {} | |
| virtual void SetUp() { | |
| mock_socket_factory_.reset(); | |
| handshake_request_ = kWebSocketHandshakeRequest; | |
| handshake_response_ = kWebSocketHandshakeResponse; | |
| } | |
| virtual void TearDown() { | |
| mock_socket_factory_.reset(); | |
| } | |
| virtual void SetWebSocketHandshakeMessage( | |
| const char* request, const char* response) { | |
| handshake_request_ = request; | |
| handshake_response_ = response; | |
| } | |
| virtual void AddWebSocketMessage(const std::string& message) { | |
| messages_.push_back(message); | |
| } | |
| virtual MockClientSocketFactory* GetMockClientSocketFactory() { | |
| mock_socket_factory_.reset(new MockClientSocketFactory); | |
| return mock_socket_factory_.get(); | |
| } | |
| virtual void DoSendWebSocketHandshake(SocketStreamEvent* event) { | |
| event->socket->SendData( | |
| handshake_request_.data(), handshake_request_.size()); | |
| } | |
| virtual void DoCloseFlushPendingWriteTest(SocketStreamEvent* event) { | |
| // handshake response received. | |
| for (size_t i = 0; i < messages_.size(); i++) { | |
| std::vector<char> frame; | |
| frame.push_back('\0'); | |
| frame.insert(frame.end(), messages_[i].begin(), messages_[i].end()); | |
| frame.push_back('\xff'); | |
| EXPECT_TRUE(event->socket->SendData(&frame[0], frame.size())); | |
| } | |
| // Actual StreamSocket close must happen after all frames queued by | |
| // SendData above are sent out. | |
| event->socket->Close(); | |
| } | |
| virtual int DoSwitchToSpdyTest(SocketStreamEvent* event) { | |
| return ERR_PROTOCOL_SWITCHED; | |
| } | |
| virtual int DoIOPending(SocketStreamEvent* event) { | |
| io_test_callback_.callback().Run(OK); | |
| return ERR_IO_PENDING; | |
| } | |
| static const char kWebSocketHandshakeRequest[]; | |
| static const char kWebSocketHandshakeResponse[]; | |
| protected: | |
| TestCompletionCallback io_test_callback_; | |
| private: | |
| std::string handshake_request_; | |
| std::string handshake_response_; | |
| std::vector<std::string> messages_; | |
| scoped_ptr<MockClientSocketFactory> mock_socket_factory_; | |
| }; | |
| const char SocketStreamTest::kWebSocketHandshakeRequest[] = | |
| "GET /demo HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Connection: Upgrade\r\n" | |
| "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" | |
| "Sec-WebSocket-Protocol: sample\r\n" | |
| "Upgrade: WebSocket\r\n" | |
| "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" | |
| "Origin: http://example.com\r\n" | |
| "\r\n" | |
| "^n:ds[4U"; | |
| const char SocketStreamTest::kWebSocketHandshakeResponse[] = | |
| "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" | |
| "Upgrade: WebSocket\r\n" | |
| "Connection: Upgrade\r\n" | |
| "Sec-WebSocket-Origin: http://example.com\r\n" | |
| "Sec-WebSocket-Location: ws://example.com/demo\r\n" | |
| "Sec-WebSocket-Protocol: sample\r\n" | |
| "\r\n" | |
| "8jKS'y:G*Co,Wxa-"; | |
| TEST_F(SocketStreamTest, CloseFlushPendingWrite) { | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind( | |
| &SocketStreamTest::DoSendWebSocketHandshake, base::Unretained(this))); | |
| delegate->SetOnReceivedData(base::Bind( | |
| &SocketStreamTest::DoCloseFlushPendingWriteTest, | |
| base::Unretained(this))); | |
| TestURLRequestContext context; | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| MockWrite data_writes[] = { | |
| MockWrite(SocketStreamTest::kWebSocketHandshakeRequest), | |
| MockWrite(ASYNC, "\0message1\xff", 10), | |
| MockWrite(ASYNC, "\0message2\xff", 10) | |
| }; | |
| MockRead data_reads[] = { | |
| MockRead(SocketStreamTest::kWebSocketHandshakeResponse), | |
| // Server doesn't close the connection after handshake. | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| AddWebSocketMessage("message1"); | |
| AddWebSocketMessage("message2"); | |
| DelayedSocketData data_provider( | |
| 1, data_reads, arraysize(data_reads), | |
| data_writes, arraysize(data_writes)); | |
| MockClientSocketFactory* mock_socket_factory = | |
| GetMockClientSocketFactory(); | |
| mock_socket_factory->AddSocketDataProvider(&data_provider); | |
| socket_stream->SetClientSocketFactory(mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(8U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[2].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_RECEIVED_DATA, events[3].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[4].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[5].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[6].event_type); | |
| EXPECT_EQ(ERR_CONNECTION_CLOSED, events[6].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[7].event_type); | |
| } | |
| TEST_F(SocketStreamTest, BasicAuthProxy) { | |
| MockClientSocketFactory mock_socket_factory; | |
| MockWrite data_writes1[] = { | |
| MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n\r\n"), | |
| }; | |
| MockRead data_reads1[] = { | |
| MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"), | |
| MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), | |
| MockRead("\r\n"), | |
| }; | |
| StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), | |
| data_writes1, arraysize(data_writes1)); | |
| mock_socket_factory.AddSocketDataProvider(&data1); | |
| MockWrite data_writes2[] = { | |
| MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n" | |
| "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), | |
| }; | |
| MockRead data_reads2[] = { | |
| MockRead("HTTP/1.1 200 Connection Established\r\n"), | |
| MockRead("Proxy-agent: Apache/2.2.8\r\n"), | |
| MockRead("\r\n"), | |
| // SocketStream::DoClose is run asynchronously. Socket can be read after | |
| // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate | |
| // server doesn't close the connection. | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| StaticSocketDataProvider data2(data_reads2, arraysize(data_reads2), | |
| data_writes2, arraysize(data_writes2)); | |
| mock_socket_factory.AddSocketDataProvider(&data2); | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, | |
| base::Unretained(delegate.get()))); | |
| delegate->SetAuthInfo(AuthCredentials(ASCIIToUTF16("foo"), | |
| ASCIIToUTF16("bar"))); | |
| delegate->SetOnAuthRequired(base::Bind( | |
| &SocketStreamEventRecorder::DoRestartWithAuth, | |
| base::Unretained(delegate.get()))); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| TestURLRequestContextWithProxy context("myproxy:70"); | |
| socket_stream->set_context(&context); | |
| socket_stream->SetClientSocketFactory(&mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(5U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_AUTH_REQUIRED, events[1].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[2].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[3].event_type); | |
| EXPECT_EQ(ERR_ABORTED, events[3].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[4].event_type); | |
| // TODO(eroman): Add back NetLogTest here... | |
| } | |
| TEST_F(SocketStreamTest, BasicAuthProxyWithAuthCache) { | |
| MockClientSocketFactory mock_socket_factory; | |
| MockWrite data_writes[] = { | |
| // WebSocket(SocketStream) always uses CONNECT when it is configured to use | |
| // proxy so the port may not be 443. | |
| MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n" | |
| "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), | |
| }; | |
| MockRead data_reads[] = { | |
| MockRead("HTTP/1.1 200 Connection Established\r\n"), | |
| MockRead("Proxy-agent: Apache/2.2.8\r\n"), | |
| MockRead("\r\n"), | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| StaticSocketDataProvider data(data_reads, arraysize(data_reads), | |
| data_writes, arraysize(data_writes)); | |
| mock_socket_factory.AddSocketDataProvider(&data); | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, | |
| base::Unretained(delegate.get()))); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| TestURLRequestContextWithProxy context("myproxy:70"); | |
| HttpAuthCache* auth_cache = | |
| context.http_transaction_factory()->GetSession()->http_auth_cache(); | |
| auth_cache->Add(GURL("http://myproxy:70"), | |
| "MyRealm1", | |
| HttpAuth::AUTH_SCHEME_BASIC, | |
| "Basic realm=MyRealm1", | |
| AuthCredentials(ASCIIToUTF16("foo"), | |
| ASCIIToUTF16("bar")), | |
| "/"); | |
| socket_stream->set_context(&context); | |
| socket_stream->SetClientSocketFactory(&mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(4U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); | |
| EXPECT_EQ(ERR_ABORTED, events[2].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type); | |
| } | |
| TEST_F(SocketStreamTest, WSSBasicAuthProxyWithAuthCache) { | |
| MockClientSocketFactory mock_socket_factory; | |
| MockWrite data_writes1[] = { | |
| MockWrite("CONNECT example.com:443 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n" | |
| "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), | |
| }; | |
| MockRead data_reads1[] = { | |
| MockRead("HTTP/1.1 200 Connection Established\r\n"), | |
| MockRead("Proxy-agent: Apache/2.2.8\r\n"), | |
| MockRead("\r\n"), | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), | |
| data_writes1, arraysize(data_writes1)); | |
| mock_socket_factory.AddSocketDataProvider(&data1); | |
| SSLSocketDataProvider data2(ASYNC, OK); | |
| mock_socket_factory.AddSSLSocketDataProvider(&data2); | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, | |
| base::Unretained(delegate.get()))); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("wss://example.com/demo"), delegate.get())); | |
| TestURLRequestContextWithProxy context("myproxy:70"); | |
| HttpAuthCache* auth_cache = | |
| context.http_transaction_factory()->GetSession()->http_auth_cache(); | |
| auth_cache->Add(GURL("http://myproxy:70"), | |
| "MyRealm1", | |
| HttpAuth::AUTH_SCHEME_BASIC, | |
| "Basic realm=MyRealm1", | |
| AuthCredentials(ASCIIToUTF16("foo"), | |
| ASCIIToUTF16("bar")), | |
| "/"); | |
| socket_stream->set_context(&context); | |
| socket_stream->SetClientSocketFactory(&mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(4U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); | |
| EXPECT_EQ(ERR_ABORTED, events[2].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type); | |
| } | |
| TEST_F(SocketStreamTest, IOPending) { | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind( | |
| &SocketStreamTest::DoSendWebSocketHandshake, base::Unretained(this))); | |
| delegate->SetOnReceivedData(base::Bind( | |
| &SocketStreamTest::DoCloseFlushPendingWriteTest, | |
| base::Unretained(this))); | |
| delegate->SetOnStartOpenConnection(base::Bind( | |
| &SocketStreamTest::DoIOPending, base::Unretained(this))); | |
| TestURLRequestContext context; | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| MockWrite data_writes[] = { | |
| MockWrite(SocketStreamTest::kWebSocketHandshakeRequest), | |
| MockWrite(ASYNC, "\0message1\xff", 10), | |
| MockWrite(ASYNC, "\0message2\xff", 10) | |
| }; | |
| MockRead data_reads[] = { | |
| MockRead(SocketStreamTest::kWebSocketHandshakeResponse), | |
| // Server doesn't close the connection after handshake. | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| AddWebSocketMessage("message1"); | |
| AddWebSocketMessage("message2"); | |
| DelayedSocketData data_provider( | |
| 1, data_reads, arraysize(data_reads), | |
| data_writes, arraysize(data_writes)); | |
| MockClientSocketFactory* mock_socket_factory = | |
| GetMockClientSocketFactory(); | |
| mock_socket_factory->AddSocketDataProvider(&data_provider); | |
| socket_stream->SetClientSocketFactory(mock_socket_factory); | |
| socket_stream->Connect(); | |
| io_test_callback_.WaitForResult(); | |
| EXPECT_EQ(SocketStream::STATE_RESOLVE_PROTOCOL_COMPLETE, | |
| socket_stream->next_state_); | |
| delegate->CompleteConnection(OK); | |
| EXPECT_EQ(OK, test_callback.WaitForResult()); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(8U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[2].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_RECEIVED_DATA, events[3].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[4].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_SENT_DATA, events[5].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[6].event_type); | |
| EXPECT_EQ(ERR_CONNECTION_CLOSED, events[6].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[7].event_type); | |
| } | |
| TEST_F(SocketStreamTest, SwitchToSpdy) { | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnStartOpenConnection(base::Bind( | |
| &SocketStreamTest::DoSwitchToSpdyTest, base::Unretained(this))); | |
| TestURLRequestContext context; | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| socket_stream->Connect(); | |
| EXPECT_EQ(ERR_PROTOCOL_SWITCHED, test_callback.WaitForResult()); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(2U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type); | |
| EXPECT_EQ(ERR_PROTOCOL_SWITCHED, events[1].error_code); | |
| } | |
| TEST_F(SocketStreamTest, SwitchAfterPending) { | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnStartOpenConnection(base::Bind( | |
| &SocketStreamTest::DoIOPending, base::Unretained(this))); | |
| TestURLRequestContext context; | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| socket_stream->Connect(); | |
| io_test_callback_.WaitForResult(); | |
| EXPECT_EQ(SocketStream::STATE_RESOLVE_PROTOCOL_COMPLETE, | |
| socket_stream->next_state_); | |
| delegate->CompleteConnection(ERR_PROTOCOL_SWITCHED); | |
| EXPECT_EQ(ERR_PROTOCOL_SWITCHED, test_callback.WaitForResult()); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(2U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type); | |
| EXPECT_EQ(ERR_PROTOCOL_SWITCHED, events[1].error_code); | |
| } | |
| // Test a connection though a secure proxy. | |
| TEST_F(SocketStreamTest, SecureProxyConnectError) { | |
| MockClientSocketFactory mock_socket_factory; | |
| MockWrite data_writes[] = { | |
| MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n\r\n") | |
| }; | |
| MockRead data_reads[] = { | |
| MockRead("HTTP/1.1 200 Connection Established\r\n"), | |
| MockRead("Proxy-agent: Apache/2.2.8\r\n"), | |
| MockRead("\r\n"), | |
| // SocketStream::DoClose is run asynchronously. Socket can be read after | |
| // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate | |
| // server doesn't close the connection. | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| StaticSocketDataProvider data(data_reads, arraysize(data_reads), | |
| data_writes, arraysize(data_writes)); | |
| mock_socket_factory.AddSocketDataProvider(&data); | |
| SSLSocketDataProvider ssl(SYNCHRONOUS, ERR_SSL_PROTOCOL_ERROR); | |
| mock_socket_factory.AddSSLSocketDataProvider(&ssl); | |
| TestCompletionCallback test_callback; | |
| TestURLRequestContextWithProxy context("https://myproxy:70"); | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, | |
| base::Unretained(delegate.get()))); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| socket_stream->SetClientSocketFactory(&mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(3U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[1].event_type); | |
| EXPECT_EQ(ERR_SSL_PROTOCOL_ERROR, events[1].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[2].event_type); | |
| } | |
| // Test a connection though a secure proxy. | |
| TEST_F(SocketStreamTest, SecureProxyConnect) { | |
| MockClientSocketFactory mock_socket_factory; | |
| MockWrite data_writes[] = { | |
| MockWrite("CONNECT example.com:80 HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Proxy-Connection: keep-alive\r\n\r\n") | |
| }; | |
| MockRead data_reads[] = { | |
| MockRead("HTTP/1.1 200 Connection Established\r\n"), | |
| MockRead("Proxy-agent: Apache/2.2.8\r\n"), | |
| MockRead("\r\n"), | |
| // SocketStream::DoClose is run asynchronously. Socket can be read after | |
| // "\r\n". We have to give ERR_IO_PENDING to SocketStream then to indicate | |
| // server doesn't close the connection. | |
| MockRead(ASYNC, ERR_IO_PENDING) | |
| }; | |
| StaticSocketDataProvider data(data_reads, arraysize(data_reads), | |
| data_writes, arraysize(data_writes)); | |
| mock_socket_factory.AddSocketDataProvider(&data); | |
| SSLSocketDataProvider ssl(SYNCHRONOUS, OK); | |
| mock_socket_factory.AddSSLSocketDataProvider(&ssl); | |
| TestCompletionCallback test_callback; | |
| TestURLRequestContextWithProxy context("https://myproxy:70"); | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| delegate->SetOnConnected(base::Bind(&SocketStreamEventRecorder::DoClose, | |
| base::Unretained(delegate.get()))); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| socket_stream->SetClientSocketFactory(&mock_socket_factory); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(4U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_START_OPEN_CONNECTION, | |
| events[0].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CONNECTED, events[1].event_type); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[2].event_type); | |
| EXPECT_EQ(ERR_ABORTED, events[2].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[3].event_type); | |
| } | |
| TEST_F(SocketStreamTest, BeforeConnectFailed) { | |
| TestCompletionCallback test_callback; | |
| scoped_ptr<SocketStreamEventRecorder> delegate( | |
| new SocketStreamEventRecorder(test_callback.callback())); | |
| TestURLRequestContext context; | |
| TestSocketStreamNetworkDelegate network_delegate; | |
| network_delegate.SetBeforeConnectResult(ERR_ACCESS_DENIED); | |
| context.set_network_delegate(&network_delegate); | |
| scoped_refptr<SocketStream> socket_stream( | |
| new SocketStream(GURL("ws://example.com/demo"), delegate.get())); | |
| socket_stream->set_context(&context); | |
| socket_stream->Connect(); | |
| test_callback.WaitForResult(); | |
| const std::vector<SocketStreamEvent>& events = delegate->GetSeenEvents(); | |
| ASSERT_EQ(2U, events.size()); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_ERROR, events[0].event_type); | |
| EXPECT_EQ(ERR_ACCESS_DENIED, events[0].error_code); | |
| EXPECT_EQ(SocketStreamEvent::EVENT_CLOSE, events[1].event_type); | |
| } | |
| } // namespace net |