OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <string> |
| 6 #include <vector> |
| 7 |
| 8 #include "net/spdy/spdy_websocket_stream.h" |
| 9 #include "net/base/completion_callback.h" |
| 10 #include "net/proxy/proxy_server.h" |
| 11 #include "net/spdy/spdy_protocol.h" |
| 12 #include "net/spdy/spdy_session.h" |
| 13 #include "net/spdy/spdy_test_util.h" |
| 14 #include "testing/gtest/include/gtest/gtest.h" |
| 15 |
| 16 namespace net { |
| 17 |
| 18 spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeReq( |
| 19 const char* const url, |
| 20 const char* const origin, |
| 21 const char* const protocol, |
| 22 bool compressed, |
| 23 spdy::SpdyStreamId stream_id, |
| 24 RequestPriority request_priority) { |
| 25 const SpdyHeaderInfo kSynStreamHeader = { |
| 26 spdy::SYN_STREAM, |
| 27 stream_id, |
| 28 0, // Associated stream ID |
| 29 net::ConvertRequestPriorityToSpdyPriority(request_priority), |
| 30 spdy::CONTROL_FLAG_NONE, |
| 31 compressed, |
| 32 spdy::INVALID, // Status |
| 33 NULL, // Data, |
| 34 0, // Length |
| 35 spdy::DATA_FLAG_NONE |
| 36 }; |
| 37 |
| 38 const char* const headers[] = { |
| 39 "url", |
| 40 url, |
| 41 "origin", |
| 42 origin, |
| 43 "protocol", |
| 44 protocol, |
| 45 }; |
| 46 int header_size = arraysize(headers) / 2; |
| 47 if (protocol == NULL) |
| 48 header_size -= 1; |
| 49 |
| 50 return ConstructSpdyPacket( |
| 51 kSynStreamHeader, |
| 52 NULL, |
| 53 0, |
| 54 headers, |
| 55 header_size); |
| 56 } |
| 57 |
| 58 spdy::SpdyFrame* ConstructSpdyWebSocketHandshakeResp( |
| 59 const char* const url, |
| 60 const char* const origin, |
| 61 const char* const protocol, |
| 62 bool compressed, |
| 63 spdy::SpdyStreamId stream_id, |
| 64 RequestPriority request_priority) { |
| 65 const SpdyHeaderInfo kSynReplyHeader = { |
| 66 spdy::SYN_REPLY, |
| 67 stream_id, |
| 68 0, // Associated stream ID |
| 69 net::ConvertRequestPriorityToSpdyPriority(request_priority), |
| 70 spdy::CONTROL_FLAG_NONE, |
| 71 false, |
| 72 spdy::INVALID, // Status |
| 73 NULL, // Data |
| 74 0, // Length |
| 75 spdy::DATA_FLAG_NONE |
| 76 }; |
| 77 |
| 78 const char* const headers[] = { |
| 79 "sec-websocket-location", |
| 80 url, |
| 81 "sec-websocket-origin", |
| 82 origin, |
| 83 "sec-websocket-protocol", |
| 84 protocol, |
| 85 }; |
| 86 int header_size = arraysize(headers) / 2; |
| 87 if (protocol == NULL) |
| 88 header_size -= 1; |
| 89 |
| 90 return ConstructSpdyPacket( |
| 91 kSynReplyHeader, |
| 92 NULL, |
| 93 0, |
| 94 headers, |
| 95 header_size); |
| 96 } |
| 97 |
| 98 spdy::SpdyFrame* ConstructSpdyWebSocketFrame( |
| 99 const char* data, |
| 100 int len, |
| 101 spdy::SpdyStreamId stream_id, |
| 102 bool fin) { |
| 103 spdy::SpdyFramer framer; |
| 104 return framer.CreateDataFrame( |
| 105 stream_id, data, len, |
| 106 fin ? spdy::DATA_FLAG_FIN : spdy::DATA_FLAG_NONE); |
| 107 } |
| 108 |
| 109 struct SpdyWebSocketStreamEvent { |
| 110 enum EventType { |
| 111 EVENT_CREATED, |
| 112 EVENT_SENT_HEADERS, EVENT_RECEIVED_HEADER, |
| 113 EVENT_SENT_DATA, EVENT_RECEIVED_DATA, |
| 114 EVENT_CLOSE, |
| 115 }; |
| 116 SpdyWebSocketStreamEvent(EventType type, |
| 117 SpdyWebSocketStream* stream, |
| 118 const spdy::SpdyHeaderBlock& headers, |
| 119 int result, |
| 120 const std::string& data) |
| 121 : event_type(type), |
| 122 stream(stream), |
| 123 headers(headers), |
| 124 result(result), |
| 125 data(data) {} |
| 126 |
| 127 EventType event_type; |
| 128 SpdyWebSocketStream* stream; |
| 129 spdy::SpdyHeaderBlock headers; |
| 130 int result; |
| 131 std::string data; |
| 132 }; |
| 133 |
| 134 class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStreamDelegate { |
| 135 public: |
| 136 explicit SpdyWebSocketStreamEventRecorder(CompletionCallback* callback) |
| 137 : on_created_(NULL), |
| 138 on_sent_headers_(NULL), |
| 139 on_received_header_(NULL), |
| 140 on_sent_data_(NULL), |
| 141 on_received_data_(NULL), |
| 142 on_close_(NULL), |
| 143 callback_(callback) {} |
| 144 virtual ~SpdyWebSocketStreamEventRecorder() { |
| 145 delete on_created_; |
| 146 delete on_sent_headers_; |
| 147 delete on_received_header_; |
| 148 delete on_sent_data_; |
| 149 delete on_received_data_; |
| 150 delete on_close_; |
| 151 } |
| 152 |
| 153 void SetOnCreated(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 154 on_created_ = callback; |
| 155 } |
| 156 void SetOnSentHeaders(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 157 on_sent_headers_ = callback; |
| 158 } |
| 159 void SetOnReceivedHeader( |
| 160 Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 161 on_received_header_ = callback; |
| 162 } |
| 163 void SetOnSentData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 164 on_sent_data_ = callback; |
| 165 } |
| 166 void SetOnReceivedData(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 167 on_received_data_ = callback; |
| 168 } |
| 169 void SetOnClose(Callback1<SpdyWebSocketStreamEvent*>::Type* callback) { |
| 170 on_close_ = callback; |
| 171 } |
| 172 |
| 173 virtual void OnCreatedSpdyStream(SpdyWebSocketStream* stream, int result) { |
| 174 events_.push_back( |
| 175 SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_CREATED, |
| 176 stream, |
| 177 spdy::SpdyHeaderBlock(), |
| 178 result, |
| 179 std::string())); |
| 180 if (on_created_) |
| 181 on_created_->Run(&events_.back()); |
| 182 } |
| 183 |
| 184 virtual void OnSentSpdyHeaders(SpdyWebSocketStream* stream) { |
| 185 events_.push_back( |
| 186 SpdyWebSocketStreamEvent(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, |
| 187 stream, |
| 188 spdy::SpdyHeaderBlock(), |
| 189 OK, |
| 190 std::string())); |
| 191 if (on_sent_data_) |
| 192 on_sent_data_->Run(&events_.back()); |
| 193 } |
| 194 virtual int OnReceivedSpdyResponseHeader( |
| 195 SpdyWebSocketStream* stream, |
| 196 const spdy::SpdyHeaderBlock& headers, |
| 197 int status) { |
| 198 events_.push_back( |
| 199 SpdyWebSocketStreamEvent( |
| 200 SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, |
| 201 stream, |
| 202 headers, |
| 203 status, |
| 204 std::string())); |
| 205 if (on_received_header_) |
| 206 on_received_header_->Run(&events_.back()); |
| 207 return status; |
| 208 } |
| 209 virtual void OnSentSpdyData( |
| 210 SpdyWebSocketStream* stream, int amount_sent) { |
| 211 events_.push_back( |
| 212 SpdyWebSocketStreamEvent( |
| 213 SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
| 214 stream, |
| 215 spdy::SpdyHeaderBlock(), |
| 216 amount_sent, |
| 217 std::string())); |
| 218 if (on_sent_data_) |
| 219 on_sent_data_->Run(&events_.back()); |
| 220 } |
| 221 virtual void OnReceivedSpdyData( |
| 222 SpdyWebSocketStream* stream, const char* data, int length) { |
| 223 events_.push_back( |
| 224 SpdyWebSocketStreamEvent( |
| 225 SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
| 226 stream, |
| 227 spdy::SpdyHeaderBlock(), |
| 228 length, |
| 229 std::string(data, length))); |
| 230 if (on_received_data_) |
| 231 on_received_data_->Run(&events_.back()); |
| 232 } |
| 233 virtual void OnCloseSpdyStream(SpdyWebSocketStream* stream) { |
| 234 events_.push_back( |
| 235 SpdyWebSocketStreamEvent( |
| 236 SpdyWebSocketStreamEvent::EVENT_CLOSE, |
| 237 stream, |
| 238 spdy::SpdyHeaderBlock(), |
| 239 OK, |
| 240 std::string())); |
| 241 if (on_close_) |
| 242 on_close_->Run(&events_.back()); |
| 243 if (callback_) |
| 244 callback_->Run(net::OK); |
| 245 } |
| 246 |
| 247 const std::vector<SpdyWebSocketStreamEvent>& GetSeenEvents() const { |
| 248 return events_; |
| 249 } |
| 250 |
| 251 private: |
| 252 std::vector<SpdyWebSocketStreamEvent> events_; |
| 253 Callback1<SpdyWebSocketStreamEvent*>::Type* on_created_; |
| 254 Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_headers_; |
| 255 Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_header_; |
| 256 Callback1<SpdyWebSocketStreamEvent*>::Type* on_sent_data_; |
| 257 Callback1<SpdyWebSocketStreamEvent*>::Type* on_received_data_; |
| 258 Callback1<SpdyWebSocketStreamEvent*>::Type* on_close_; |
| 259 CompletionCallback* callback_; |
| 260 |
| 261 DISALLOW_COPY_AND_ASSIGN(SpdyWebSocketStreamEventRecorder); |
| 262 }; |
| 263 |
| 264 class SpdyWebSocketStreamTest : public testing::Test { |
| 265 public: |
| 266 OrderedSocketData* data() { return data_; } |
| 267 |
| 268 void DoSendHelloFrame(SpdyWebSocketStreamEvent* event) { |
| 269 std::string frame; |
| 270 frame.append(1, '\0'); |
| 271 frame.append("hello"); |
| 272 frame.append(1, '\xff'); |
| 273 event->stream->SendData(frame.data(), frame.size()); |
| 274 } |
| 275 |
| 276 void DoSendClosingFrame(SpdyWebSocketStreamEvent* event) { |
| 277 static const char kClosingFrame[] = "\xff\0"; |
| 278 event->stream->SendData(kClosingFrame, 2); |
| 279 } |
| 280 |
| 281 void DoClose(SpdyWebSocketStreamEvent* event) { |
| 282 event->stream->Close(); |
| 283 } |
| 284 |
| 285 protected: |
| 286 SpdyWebSocketStreamTest() {} |
| 287 virtual ~SpdyWebSocketStreamTest() {} |
| 288 |
| 289 virtual void SetUp() {} |
| 290 virtual void TearDown() { |
| 291 MessageLoop::current()->RunAllPending(); |
| 292 } |
| 293 |
| 294 void EnableCompression(bool enabled) { |
| 295 spdy::SpdyFramer::set_enable_compression_default(enabled); |
| 296 } |
| 297 int InitSession(MockRead* reads, size_t reads_count, |
| 298 MockWrite* writes, size_t writes_count, |
| 299 HostPortPair& host_port_pair) { |
| 300 HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); |
| 301 data_ = new OrderedSocketData(reads, reads_count, writes, writes_count); |
| 302 session_deps_.socket_factory->AddSocketDataProvider(data_.get()); |
| 303 http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| 304 session_ = http_session_->spdy_session_pool()-> |
| 305 Get(pair, http_session_->mutable_spdy_settings(), BoundNetLog()); |
| 306 tcp_params_ = new TCPSocketParams(host_port_pair.host(), |
| 307 host_port_pair.port(), |
| 308 MEDIUM, GURL(), false); |
| 309 TestCompletionCallback callback; |
| 310 scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); |
| 311 EXPECT_EQ(ERR_IO_PENDING, |
| 312 connection->Init(host_port_pair.ToString(), tcp_params_, MEDIUM, |
| 313 &callback, http_session_->tcp_socket_pool(), |
| 314 BoundNetLog())); |
| 315 EXPECT_EQ(OK, callback.WaitForResult()); |
| 316 return session_->InitializeWithSocket(connection.release(), false, OK); |
| 317 } |
| 318 |
| 319 SpdySessionDependencies session_deps_; |
| 320 scoped_refptr<OrderedSocketData> data_; |
| 321 scoped_refptr<HttpNetworkSession> http_session_; |
| 322 scoped_refptr<SpdySession> session_; |
| 323 scoped_refptr<TCPSocketParams> tcp_params_; |
| 324 }; |
| 325 |
| 326 TEST_F(SpdyWebSocketStreamTest, Echo) { |
| 327 EnableCompression(false); |
| 328 SpdySession::SetSSLMode(false); |
| 329 |
| 330 spdy::SpdyStreamId stream_id = 1; |
| 331 |
| 332 scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyWebSocketHandshakeReq( |
| 333 "ws://example.com/echo", |
| 334 "http://example.com/wsdemo", |
| 335 NULL, |
| 336 false, |
| 337 stream_id, |
| 338 HIGHEST)); |
| 339 scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyWebSocketHandshakeResp( |
| 340 "ws://example.com/echo", |
| 341 "http://example.com/wsdemo", |
| 342 NULL, |
| 343 false, |
| 344 stream_id, |
| 345 HIGHEST)); |
| 346 |
| 347 static const char* const msg_frame = "\0hello\xff"; |
| 348 scoped_ptr<spdy::SpdyFrame> msg(ConstructSpdyWebSocketFrame( |
| 349 msg_frame, 7, stream_id, false)); |
| 350 |
| 351 static const char* const closing_frame = "\xff\0"; |
| 352 scoped_ptr<spdy::SpdyFrame> closing(ConstructSpdyWebSocketFrame( |
| 353 closing_frame, 2, stream_id, false)); |
| 354 |
| 355 MockWrite writes[] = { |
| 356 CreateMockWrite(*req.get(), 1), |
| 357 CreateMockWrite(*msg.get(), 3), |
| 358 CreateMockWrite(*closing.get(), 5) |
| 359 }; |
| 360 |
| 361 MockRead reads[] = { |
| 362 CreateMockRead(*resp.get(), 2), |
| 363 CreateMockRead(*msg.get(), 4), |
| 364 // Skip sequence 6 to notify closing has been sent. |
| 365 CreateMockRead(*closing.get(), 7), |
| 366 MockRead(false, 0, 8) // EOF |
| 367 }; |
| 368 |
| 369 HostPortPair host_port_pair("example.com", 80); |
| 370 HostPortProxyPair pair(host_port_pair, ProxyServer::Direct()); |
| 371 EXPECT_EQ(OK, InitSession(reads, arraysize(reads), writes, arraysize(writes), |
| 372 host_port_pair)); |
| 373 |
| 374 TestCompletionCallback callback; |
| 375 |
| 376 scoped_ptr<SpdyWebSocketStreamEventRecorder> delegate( |
| 377 new SpdyWebSocketStreamEventRecorder(&callback)); |
| 378 // Necessary for NewCallback. |
| 379 SpdyWebSocketStreamTest* test = this; |
| 380 delegate->SetOnReceivedHeader( |
| 381 NewCallback(test, &SpdyWebSocketStreamTest::DoSendHelloFrame)); |
| 382 delegate->SetOnReceivedData( |
| 383 NewCallback(test, &SpdyWebSocketStreamTest::DoSendClosingFrame)); |
| 384 |
| 385 scoped_ptr<SpdyWebSocketStream> websocket_stream( |
| 386 new SpdyWebSocketStream(session_, delegate.get())); |
| 387 |
| 388 BoundNetLog net_log; |
| 389 GURL url("ws://example.com/echo"); |
| 390 ASSERT_EQ(OK, websocket_stream->InitializeStream(url, HIGHEST, net_log)); |
| 391 |
| 392 EXPECT_EQ(stream_id, websocket_stream->stream_id()); |
| 393 |
| 394 |
| 395 linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); |
| 396 (*headers)["url"] = "ws://example.com/echo"; |
| 397 (*headers)["origin"] = "http://example.com/wsdemo"; |
| 398 |
| 399 websocket_stream->SendRequest(headers); |
| 400 |
| 401 callback.WaitForResult(); |
| 402 |
| 403 const std::vector<SpdyWebSocketStreamEvent>& events = |
| 404 delegate->GetSeenEvents(); |
| 405 ASSERT_EQ(8U, events.size()); |
| 406 |
| 407 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CREATED, |
| 408 events[0].event_type); |
| 409 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_HEADERS, |
| 410 events[1].event_type); |
| 411 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER, |
| 412 events[2].event_type); |
| 413 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
| 414 events[3].event_type); |
| 415 EXPECT_EQ(7, events[3].result); |
| 416 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
| 417 events[4].event_type); |
| 418 EXPECT_EQ(7, events[4].result); |
| 419 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_SENT_DATA, |
| 420 events[5].event_type); |
| 421 EXPECT_EQ(2, events[5].result); |
| 422 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_RECEIVED_DATA, |
| 423 events[6].event_type); |
| 424 EXPECT_EQ(2, events[6].result); |
| 425 EXPECT_EQ(SpdyWebSocketStreamEvent::EVENT_CLOSE, |
| 426 events[7].event_type); |
| 427 |
| 428 EXPECT_TRUE(!http_session_->spdy_session_pool()->HasSession(pair)); |
| 429 EXPECT_TRUE(data()->at_read_eof()); |
| 430 EXPECT_TRUE(data()->at_write_eof()); |
| 431 } |
| 432 |
| 433 } // namespace net |
OLD | NEW |