OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 "base/macros.h" |
| 6 #include "base/memory/linked_ptr.h" |
| 7 #include "base/memory/ref_counted.h" |
| 8 #include "base/memory/scoped_ptr.h" |
| 9 #include "base/run_loop.h" |
| 10 #include "base/strings/string_util.h" |
| 11 #include "mojo/application/application_test_base_chromium.h" |
| 12 #include "mojo/application/public/cpp/application_connection.h" |
| 13 #include "mojo/application/public/cpp/application_impl.h" |
| 14 #include "mojo/common/data_pipe_utils.h" |
| 15 #include "mojo/services/network/net_address_type_converters.h" |
| 16 #include "mojo/services/network/public/interfaces/http_server.mojom.h" |
| 17 #include "mojo/services/network/public/interfaces/network_service.mojom.h" |
| 18 #include "net/base/io_buffer.h" |
| 19 #include "net/base/net_errors.h" |
| 20 #include "net/base/test_completion_callback.h" |
| 21 #include "net/http/http_response_headers.h" |
| 22 #include "net/http/http_util.h" |
| 23 #include "net/socket/tcp_client_socket.h" |
| 24 #include "testing/gtest/include/gtest/gtest.h" |
| 25 |
| 26 namespace mojo { |
| 27 namespace { |
| 28 |
| 29 const int kMaxExpectedResponseLength = 2048; |
| 30 |
| 31 NetAddressPtr GetLocalHostWithAnyPort() { |
| 32 NetAddressPtr addr(NetAddress::New()); |
| 33 addr->family = NET_ADDRESS_FAMILY_IPV4; |
| 34 addr->ipv4 = NetAddressIPv4::New(); |
| 35 addr->ipv4->port = 0; |
| 36 addr->ipv4->addr.resize(4); |
| 37 addr->ipv4->addr[0] = 127; |
| 38 addr->ipv4->addr[1] = 0; |
| 39 addr->ipv4->addr[2] = 0; |
| 40 addr->ipv4->addr[3] = 1; |
| 41 |
| 42 return addr.Pass(); |
| 43 } |
| 44 |
| 45 using TestHeaders = std::vector<std::pair<std::string, std::string>>; |
| 46 |
| 47 struct TestRequest { |
| 48 std::string method; |
| 49 std::string url; |
| 50 TestHeaders headers; |
| 51 scoped_ptr<std::string> body; |
| 52 }; |
| 53 |
| 54 struct TestResponse { |
| 55 uint32_t status_code; |
| 56 TestHeaders headers; |
| 57 scoped_ptr<std::string> body; |
| 58 }; |
| 59 |
| 60 std::string MakeRequestMessage(const TestRequest& data) { |
| 61 std::string message = data.method + " " + data.url + " HTTP/1.1\r\n"; |
| 62 for (const auto& item : data.headers) |
| 63 message += item.first + ": " + item.second + "\r\n"; |
| 64 message += "\r\n"; |
| 65 if (data.body) |
| 66 message += *data.body; |
| 67 |
| 68 return message; |
| 69 } |
| 70 |
| 71 URLResponsePtr MakeResponseStruct(const TestResponse& data) { |
| 72 URLResponsePtr response(URLResponse::New()); |
| 73 response->status_code = data.status_code; |
| 74 response->headers.resize(data.headers.size()); |
| 75 size_t index = 0; |
| 76 for (const auto& item : data.headers) { |
| 77 HTTPHeaderPtr header(HTTPHeader::New()); |
| 78 header->name = item.first; |
| 79 header->value = item.second; |
| 80 response->headers[index++] = header.Pass(); |
| 81 } |
| 82 |
| 83 if (data.body) { |
| 84 uint32_t num_bytes = static_cast<uint32_t>(data.body->size()); |
| 85 MojoCreateDataPipeOptions options; |
| 86 options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| 87 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; |
| 88 options.element_num_bytes = 1; |
| 89 options.capacity_num_bytes = num_bytes; |
| 90 DataPipe data_pipe(options); |
| 91 response->body = data_pipe.consumer_handle.Pass(); |
| 92 MojoResult result = |
| 93 WriteDataRaw(data_pipe.producer_handle.get(), data.body->data(), |
| 94 &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE); |
| 95 EXPECT_EQ(MOJO_RESULT_OK, result); |
| 96 } |
| 97 |
| 98 return response.Pass(); |
| 99 } |
| 100 |
| 101 void CheckHeaders(const TestHeaders& expected, |
| 102 const Array<HTTPHeaderPtr>& headers) { |
| 103 // The server impl fiddles with Content-Length and Content-Type. So we don't |
| 104 // do a strict check here. |
| 105 std::map<std::string, std::string> header_map; |
| 106 for (size_t i = 0; i < headers.size(); ++i) { |
| 107 std::string lower_name = |
| 108 base::StringToLowerASCII(headers[i]->name.To<std::string>()); |
| 109 header_map[lower_name] = headers[i]->value; |
| 110 } |
| 111 |
| 112 for (const auto& item : expected) { |
| 113 std::string lower_name = base::StringToLowerASCII(item.first); |
| 114 EXPECT_NE(header_map.end(), header_map.find(lower_name)); |
| 115 EXPECT_EQ(item.second, header_map[lower_name]); |
| 116 } |
| 117 } |
| 118 |
| 119 void CheckRequest(const TestRequest& expected, URLRequestPtr request) { |
| 120 EXPECT_EQ(expected.method, request->method); |
| 121 EXPECT_EQ(expected.url, request->url); |
| 122 CheckHeaders(expected.headers, request->headers); |
| 123 if (expected.body) { |
| 124 EXPECT_EQ(1u, request->body.size()); |
| 125 std::string body; |
| 126 common::BlockingCopyToString(request->body[0].Pass(), &body); |
| 127 EXPECT_EQ(*expected.body, body); |
| 128 } else { |
| 129 EXPECT_EQ(0u, request->body.size()); |
| 130 } |
| 131 } |
| 132 |
| 133 void CheckResponse(const TestResponse& expected, const std::string& response) { |
| 134 int header_end = |
| 135 net::HttpUtil::LocateEndOfHeaders(response.c_str(), response.size()); |
| 136 std::string assembled_headers = |
| 137 net::HttpUtil::AssembleRawHeaders(response.c_str(), header_end); |
| 138 scoped_refptr<net::HttpResponseHeaders> parsed_headers( |
| 139 new net::HttpResponseHeaders(assembled_headers)); |
| 140 EXPECT_EQ(expected.status_code, |
| 141 static_cast<uint32_t>(parsed_headers->response_code())); |
| 142 for (const auto& item : expected.headers) |
| 143 EXPECT_TRUE(parsed_headers->HasHeaderValue(item.first, item.second)); |
| 144 |
| 145 if (expected.body) { |
| 146 EXPECT_NE(-1, header_end); |
| 147 std::string body(response, static_cast<size_t>(header_end)); |
| 148 EXPECT_EQ(*expected.body, body); |
| 149 } else { |
| 150 EXPECT_EQ(response.size(), static_cast<size_t>(header_end)); |
| 151 } |
| 152 } |
| 153 |
| 154 class TestHttpClient { |
| 155 public: |
| 156 TestHttpClient() : connect_result_(net::OK) {} |
| 157 |
| 158 void Connect(const net::IPEndPoint& address) { |
| 159 net::AddressList addresses(address); |
| 160 net::NetLog::Source source; |
| 161 socket_.reset(new net::TCPClientSocket(addresses, NULL, source)); |
| 162 |
| 163 base::RunLoop run_loop; |
| 164 connect_result_ = socket_->Connect(base::Bind(&TestHttpClient::OnConnect, |
| 165 base::Unretained(this), |
| 166 run_loop.QuitClosure())); |
| 167 if (connect_result_ == net::ERR_IO_PENDING) |
| 168 run_loop.Run(); |
| 169 |
| 170 ASSERT_EQ(net::OK, connect_result_); |
| 171 } |
| 172 |
| 173 void Send(const std::string& data) { |
| 174 write_buffer_ = new net::DrainableIOBuffer(new net::StringIOBuffer(data), |
| 175 data.length()); |
| 176 Write(); |
| 177 } |
| 178 |
| 179 // Note: This method determines the end of the response only by Content-Length |
| 180 // and connection termination. Besides, it doesn't truncate at the end of the |
| 181 // response, so |message| may return more data (e.g., part of the next |
| 182 // response). |
| 183 void ReadResponse(std::string* message) { |
| 184 if (!Read(message, 1)) |
| 185 return; |
| 186 while (!IsCompleteResponse(*message)) { |
| 187 std::string chunk; |
| 188 if (!Read(&chunk, 1)) |
| 189 return; |
| 190 message->append(chunk); |
| 191 } |
| 192 return; |
| 193 } |
| 194 |
| 195 private: |
| 196 void OnConnect(const base::Closure& quit_loop, int result) { |
| 197 connect_result_ = result; |
| 198 quit_loop.Run(); |
| 199 } |
| 200 |
| 201 void Write() { |
| 202 int result = socket_->Write( |
| 203 write_buffer_.get(), write_buffer_->BytesRemaining(), |
| 204 base::Bind(&TestHttpClient::OnWrite, base::Unretained(this))); |
| 205 if (result != net::ERR_IO_PENDING) |
| 206 OnWrite(result); |
| 207 } |
| 208 |
| 209 void OnWrite(int result) { |
| 210 ASSERT_GT(result, 0); |
| 211 write_buffer_->DidConsume(result); |
| 212 if (write_buffer_->BytesRemaining()) |
| 213 Write(); |
| 214 } |
| 215 |
| 216 bool Read(std::string* message, int expected_bytes) { |
| 217 int total_bytes_received = 0; |
| 218 message->clear(); |
| 219 while (total_bytes_received < expected_bytes) { |
| 220 net::TestCompletionCallback callback; |
| 221 ReadInternal(callback.callback()); |
| 222 int bytes_received = callback.WaitForResult(); |
| 223 if (bytes_received <= 0) |
| 224 return false; |
| 225 |
| 226 total_bytes_received += bytes_received; |
| 227 message->append(read_buffer_->data(), bytes_received); |
| 228 } |
| 229 return true; |
| 230 } |
| 231 |
| 232 void ReadInternal(const net::CompletionCallback& callback) { |
| 233 read_buffer_ = new net::IOBufferWithSize(kMaxExpectedResponseLength); |
| 234 int result = |
| 235 socket_->Read(read_buffer_.get(), kMaxExpectedResponseLength, callback); |
| 236 if (result != net::ERR_IO_PENDING) |
| 237 callback.Run(result); |
| 238 } |
| 239 |
| 240 bool IsCompleteResponse(const std::string& response) { |
| 241 // Check end of headers first. |
| 242 int end_of_headers = |
| 243 net::HttpUtil::LocateEndOfHeaders(response.data(), response.size()); |
| 244 if (end_of_headers < 0) |
| 245 return false; |
| 246 |
| 247 // Return true if response has data equal to or more than content length. |
| 248 int64 body_size = static_cast<int64>(response.size()) - end_of_headers; |
| 249 DCHECK_LE(0, body_size); |
| 250 scoped_refptr<net::HttpResponseHeaders> headers( |
| 251 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( |
| 252 response.data(), end_of_headers))); |
| 253 return body_size >= headers->GetContentLength(); |
| 254 } |
| 255 |
| 256 scoped_refptr<net::IOBufferWithSize> read_buffer_; |
| 257 scoped_refptr<net::DrainableIOBuffer> write_buffer_; |
| 258 scoped_ptr<net::TCPClientSocket> socket_; |
| 259 int connect_result_; |
| 260 |
| 261 DISALLOW_COPY_AND_ASSIGN(TestHttpClient); |
| 262 }; |
| 263 |
| 264 class HttpConnectionDelegateImpl : public HttpConnectionDelegate { |
| 265 public: |
| 266 struct PendingRequest { |
| 267 URLRequestPtr request; |
| 268 OnReceivedRequestCallback callback; |
| 269 }; |
| 270 |
| 271 HttpConnectionDelegateImpl(HttpConnectionPtr connection, |
| 272 InterfaceRequest<HttpConnectionDelegate> request) |
| 273 : connection_(connection.Pass()), |
| 274 binding_(this, request.Pass()), |
| 275 run_loop_(nullptr), |
| 276 wait_for_request_count_(0) {} |
| 277 ~HttpConnectionDelegateImpl() override {} |
| 278 |
| 279 // HttpConnectionDelegate implementation: |
| 280 void OnReceivedRequest(URLRequestPtr request, |
| 281 const OnReceivedRequestCallback& callback) override { |
| 282 linked_ptr<PendingRequest> pending_request(new PendingRequest); |
| 283 pending_request->request = request.Pass(); |
| 284 pending_request->callback = callback; |
| 285 pending_requests_.push_back(pending_request); |
| 286 if (run_loop_ && pending_requests_.size() >= wait_for_request_count_) { |
| 287 wait_for_request_count_ = 0; |
| 288 run_loop_->Quit(); |
| 289 } |
| 290 } |
| 291 |
| 292 void OnReceivedWebSocketRequest( |
| 293 URLRequestPtr request, |
| 294 const OnReceivedWebSocketRequestCallback& callback) override { |
| 295 NOTREACHED(); |
| 296 } |
| 297 |
| 298 void SendResponse(URLResponsePtr response) { |
| 299 ASSERT_FALSE(pending_requests_.empty()); |
| 300 linked_ptr<PendingRequest> request = pending_requests_[0]; |
| 301 pending_requests_.erase(pending_requests_.begin()); |
| 302 request->callback.Run(response.Pass()); |
| 303 } |
| 304 |
| 305 void WaitForRequest(size_t count) { |
| 306 DCHECK(!run_loop_); |
| 307 |
| 308 wait_for_request_count_ = count; |
| 309 base::RunLoop run_loop; |
| 310 run_loop_ = &run_loop; |
| 311 run_loop.Run(); |
| 312 run_loop_ = nullptr; |
| 313 } |
| 314 |
| 315 std::vector<linked_ptr<PendingRequest>>& pending_requests() { |
| 316 return pending_requests_; |
| 317 } |
| 318 |
| 319 private: |
| 320 HttpConnectionPtr connection_; |
| 321 Binding<HttpConnectionDelegate> binding_; |
| 322 std::vector<linked_ptr<PendingRequest>> pending_requests_; |
| 323 // Pointing to a stack-allocated RunLoop instance. |
| 324 base::RunLoop* run_loop_; |
| 325 size_t wait_for_request_count_; |
| 326 |
| 327 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl); |
| 328 }; |
| 329 |
| 330 class HttpServerDelegateImpl : public HttpServerDelegate { |
| 331 public: |
| 332 explicit HttpServerDelegateImpl(HttpServerDelegatePtr* delegate_ptr) |
| 333 : binding_(this, delegate_ptr), |
| 334 run_loop_(nullptr), |
| 335 wait_for_connection_count_(0) {} |
| 336 ~HttpServerDelegateImpl() override {} |
| 337 |
| 338 // HttpServerDelegate implementation. |
| 339 void OnConnected(HttpConnectionPtr connection, |
| 340 InterfaceRequest<HttpConnectionDelegate> delegate) override { |
| 341 connections_.push_back(make_linked_ptr( |
| 342 new HttpConnectionDelegateImpl(connection.Pass(), delegate.Pass()))); |
| 343 if (run_loop_ && connections_.size() >= wait_for_connection_count_) { |
| 344 wait_for_connection_count_ = 0; |
| 345 run_loop_->Quit(); |
| 346 } |
| 347 } |
| 348 |
| 349 void WaitForConnection(size_t count) { |
| 350 DCHECK(!run_loop_); |
| 351 |
| 352 wait_for_connection_count_ = count; |
| 353 base::RunLoop run_loop; |
| 354 run_loop_ = &run_loop; |
| 355 run_loop.Run(); |
| 356 run_loop_ = nullptr; |
| 357 } |
| 358 |
| 359 std::vector<linked_ptr<HttpConnectionDelegateImpl>>& connections() { |
| 360 return connections_; |
| 361 } |
| 362 |
| 363 private: |
| 364 Binding<HttpServerDelegate> binding_; |
| 365 std::vector<linked_ptr<HttpConnectionDelegateImpl>> connections_; |
| 366 // Pointing to a stack-allocated RunLoop instance. |
| 367 base::RunLoop* run_loop_; |
| 368 size_t wait_for_connection_count_; |
| 369 |
| 370 DISALLOW_COPY_AND_ASSIGN(HttpServerDelegateImpl); |
| 371 }; |
| 372 |
| 373 class HttpServerAppTest : public test::ApplicationTestBase { |
| 374 public: |
| 375 HttpServerAppTest() : message_loop_(base::MessageLoop::TYPE_IO) {} |
| 376 ~HttpServerAppTest() override {} |
| 377 |
| 378 protected: |
| 379 bool ShouldCreateDefaultRunLoop() override { return false; } |
| 380 |
| 381 void SetUp() override { |
| 382 ApplicationTestBase::SetUp(); |
| 383 |
| 384 ApplicationConnection* connection = |
| 385 application_impl()->ConnectToApplication("mojo:network_service"); |
| 386 connection->ConnectToService(&network_service_); |
| 387 } |
| 388 |
| 389 void CreateHttpServer(HttpServerDelegatePtr delegate, |
| 390 NetAddressPtr* out_bound_to) { |
| 391 network_service_->CreateHttpServer( |
| 392 GetLocalHostWithAnyPort(), delegate.Pass(), |
| 393 [out_bound_to](NetworkErrorPtr result, NetAddressPtr bound_to) { |
| 394 ASSERT_EQ(net::OK, result->code); |
| 395 EXPECT_NE(0u, bound_to->ipv4->port); |
| 396 *out_bound_to = bound_to.Pass(); |
| 397 }); |
| 398 network_service_.WaitForIncomingMethodCall(); |
| 399 } |
| 400 |
| 401 NetworkServicePtr network_service_; |
| 402 |
| 403 private: |
| 404 base::MessageLoop message_loop_; |
| 405 |
| 406 DISALLOW_COPY_AND_ASSIGN(HttpServerAppTest); |
| 407 }; |
| 408 |
| 409 } // namespace |
| 410 |
| 411 TEST_F(HttpServerAppTest, BasicHttpRequestResponse) { |
| 412 NetAddressPtr bound_to; |
| 413 HttpServerDelegatePtr server_delegate_ptr; |
| 414 HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr); |
| 415 CreateHttpServer(server_delegate_ptr.Pass(), &bound_to); |
| 416 |
| 417 TestHttpClient client; |
| 418 client.Connect(bound_to.To<net::IPEndPoint>()); |
| 419 |
| 420 server_delegate_impl.WaitForConnection(1); |
| 421 HttpConnectionDelegateImpl& connection = |
| 422 *server_delegate_impl.connections()[0]; |
| 423 |
| 424 TestRequest request_data = {"HEAD", "/test", {{"Hello", "World"}}, nullptr}; |
| 425 client.Send(MakeRequestMessage(request_data)); |
| 426 |
| 427 connection.WaitForRequest(1); |
| 428 |
| 429 CheckRequest(request_data, connection.pending_requests()[0]->request.Pass()); |
| 430 |
| 431 TestResponse response_data = {200, {{"Content-Length", "4"}}, nullptr}; |
| 432 connection.SendResponse(MakeResponseStruct(response_data)); |
| 433 // This causes the underlying TCP connection to be closed. The client can |
| 434 // determine the end of the response based on that. |
| 435 server_delegate_impl.connections().clear(); |
| 436 |
| 437 std::string response_message; |
| 438 client.ReadResponse(&response_message); |
| 439 |
| 440 CheckResponse(response_data, response_message); |
| 441 } |
| 442 |
| 443 TEST_F(HttpServerAppTest, HttpRequestResponseWithBody) { |
| 444 NetAddressPtr bound_to; |
| 445 HttpServerDelegatePtr server_delegate_ptr; |
| 446 HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr); |
| 447 CreateHttpServer(server_delegate_ptr.Pass(), &bound_to); |
| 448 |
| 449 TestHttpClient client; |
| 450 client.Connect(bound_to.To<net::IPEndPoint>()); |
| 451 |
| 452 server_delegate_impl.WaitForConnection(1); |
| 453 HttpConnectionDelegateImpl& connection = |
| 454 *server_delegate_impl.connections()[0]; |
| 455 |
| 456 TestRequest request_data = { |
| 457 "Post", |
| 458 "/test", |
| 459 {{"Hello", "World"}, |
| 460 {"Content-Length", "23"}, |
| 461 {"Content-Type", "text/plain"}}, |
| 462 make_scoped_ptr(new std::string("This is a test request!"))}; |
| 463 client.Send(MakeRequestMessage(request_data)); |
| 464 |
| 465 connection.WaitForRequest(1); |
| 466 |
| 467 CheckRequest(request_data, connection.pending_requests()[0]->request.Pass()); |
| 468 |
| 469 TestResponse response_data = { |
| 470 200, |
| 471 {{"Content-Length", "26"}}, |
| 472 make_scoped_ptr(new std::string("This is a test response..."))}; |
| 473 connection.SendResponse(MakeResponseStruct(response_data)); |
| 474 |
| 475 std::string response_message; |
| 476 client.ReadResponse(&response_message); |
| 477 |
| 478 CheckResponse(response_data, response_message); |
| 479 } |
| 480 |
| 481 } // namespace mojo |
OLD | NEW |