| // Copyright 2018 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 "services/network/public/cpp/server/http_server.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "mojo/public/cpp/system/data_pipe_utils.h" |
| #include "net/base/address_list.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/log/net_log_source.h" |
| #include "net/test/gtest_util.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_fetcher_delegate.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "services/network/public/cpp/server/http_connection.h" |
| #include "services/network/public/cpp/server/http_server_request_info.h" |
| #include "services/network/socket_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsOk; |
| |
| namespace { |
| |
| class TestHttpClient { |
| public: |
| TestHttpClient() : factory_(nullptr, &url_request_context_) {} |
| |
| int ConnectAndWait(const net::IPEndPoint& address) { |
| net::AddressList addresses(address); |
| base::RunLoop run_loop; |
| int net_error = net::ERR_FAILED; |
| factory_.CreateTCPConnectedSocket( |
| base::nullopt /* local address */, addresses, |
| nullptr /* tcp_connected_socket_options */, |
| TRAFFIC_ANNOTATION_FOR_TESTS, mojo::MakeRequest(&socket_), |
| nullptr /* observer */, |
| base::BindOnce( |
| [](base::RunLoop* run_loop, int* result_out, |
| mojo::ScopedDataPipeConsumerHandle* receive_pipe_handle_out, |
| mojo::ScopedDataPipeProducerHandle* send_pipe_handle_out, |
| int result, const base::Optional<net::IPEndPoint>& local_addr, |
| const base::Optional<net::IPEndPoint>& peer_addr, |
| mojo::ScopedDataPipeConsumerHandle receive_pipe_handle, |
| mojo::ScopedDataPipeProducerHandle send_pipe_handle) { |
| *receive_pipe_handle_out = std::move(receive_pipe_handle); |
| *send_pipe_handle_out = std::move(send_pipe_handle); |
| *result_out = result; |
| run_loop->Quit(); |
| }, |
| base::Unretained(&run_loop), base::Unretained(&net_error), |
| &receive_pipe_handle_, &send_pipe_handle_)); |
| run_loop.Run(); |
| return net_error; |
| } |
| |
| void Send(const std::string& message) { |
| size_t index = 0; |
| uint32_t write_size = message.size(); |
| while (write_size > 0) { |
| base::RunLoop().RunUntilIdle(); |
| MojoResult result = send_pipe_handle_->WriteData( |
| message.data() + index, &write_size, MOJO_WRITE_DATA_FLAG_NONE); |
| if (result == MOJO_RESULT_SHOULD_WAIT) |
| continue; |
| if (result != MOJO_RESULT_OK) |
| return; |
| index += write_size; |
| write_size = message.size() - index; |
| } |
| } |
| |
| bool Read(std::string* data, size_t num_bytes) { |
| while (data->size() < num_bytes) { |
| base::RunLoop().RunUntilIdle(); |
| std::vector<char> buffer(num_bytes); |
| uint32_t read_size = num_bytes; |
| MojoResult result = receive_pipe_handle_->ReadData( |
| buffer.data(), &read_size, MOJO_READ_DATA_FLAG_NONE); |
| if (result == MOJO_RESULT_SHOULD_WAIT) |
| continue; |
| if (result != MOJO_RESULT_OK) |
| return false; |
| data->append(buffer.data(), read_size); |
| } |
| return true; |
| } |
| |
| bool ReadResponse(std::string* message) { |
| if (!Read(message, 1)) |
| return false; |
| while (!IsCompleteResponse(*message)) { |
| std::string chunk; |
| if (!Read(&chunk, 1)) |
| return false; |
| message->append(chunk); |
| } |
| return true; |
| } |
| |
| void ExpectUsedThenDisconnectedWithNoData() { |
| // Check that the socket was closed when the server disconnected. Verify |
| // that the socket was closed by checking that a Read() fails. |
| std::string response; |
| ASSERT_FALSE(Read(&response, 1u)); |
| ASSERT_TRUE(response.empty()); |
| } |
| |
| void Close() { socket_.reset(); } |
| |
| private: |
| bool IsCompleteResponse(const std::string& response) { |
| // Check end of headers first. |
| int end_of_headers = |
| net::HttpUtil::LocateEndOfHeaders(response.data(), response.size()); |
| if (end_of_headers < 0) |
| return false; |
| |
| // Return true if response has data equal to or more than content length. |
| int64_t body_size = static_cast<int64_t>(response.size()) - end_of_headers; |
| DCHECK_LE(0, body_size); |
| scoped_refptr<net::HttpResponseHeaders> headers( |
| new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders( |
| response.data(), end_of_headers))); |
| return body_size >= headers->GetContentLength(); |
| } |
| |
| net::TestURLRequestContext url_request_context_; |
| network::SocketFactory factory_; |
| scoped_refptr<net::IOBufferWithSize> read_buffer_; |
| scoped_refptr<net::DrainableIOBuffer> write_buffer_; |
| |
| mojo::ScopedDataPipeConsumerHandle receive_pipe_handle_; |
| mojo::ScopedDataPipeProducerHandle send_pipe_handle_; |
| |
| network::mojom::TCPConnectedSocketPtr socket_; |
| }; |
| |
| } // namespace |
| |
| namespace network { |
| |
| namespace server { |
| |
| class HttpServerTest : public testing::Test, public HttpServer::Delegate { |
| public: |
| HttpServerTest() |
| : scoped_task_environment_( |
| base::test::ScopedTaskEnvironment::MainThreadType::IO), |
| quit_after_request_count_(0), |
| quit_on_close_connection_(-1), |
| factory_(nullptr, &url_request_context_) {} |
| |
| void SetUp() override { |
| int net_error = net::ERR_FAILED; |
| base::RunLoop run_loop; |
| factory_.CreateTCPServerSocket( |
| net::IPEndPoint(net::IPAddress::IPv6Localhost(), 0), 1 /* backlog */, |
| TRAFFIC_ANNOTATION_FOR_TESTS, mojo::MakeRequest(&server_socket_), |
| base::BindOnce( |
| [](base::RunLoop* run_loop, int* result_out, |
| net::IPEndPoint* local_addr_out, int result, |
| const base::Optional<net::IPEndPoint>& local_addr) { |
| *result_out = result; |
| if (local_addr) |
| *local_addr_out = local_addr.value(); |
| run_loop->Quit(); |
| }, |
| base::Unretained(&run_loop), base::Unretained(&net_error), |
| base::Unretained(&server_address_))); |
| run_loop.Run(); |
| EXPECT_EQ(net::OK, net_error); |
| |
| server_.reset(new HttpServer(std::move(server_socket_), this)); |
| } |
| |
| void OnConnect(int connection_id) override { |
| DCHECK(connection_map_.find(connection_id) == connection_map_.end()); |
| connection_map_[connection_id] = true; |
| } |
| |
| void OnHttpRequest(int connection_id, |
| const HttpServerRequestInfo& info) override { |
| requests_.push_back(std::make_pair(info, connection_id)); |
| if (requests_.size() == quit_after_request_count_) { |
| run_loop_quit_func_.Run(); |
| } |
| } |
| |
| void OnWebSocketRequest(int connection_id, |
| const HttpServerRequestInfo& info) override { |
| NOTREACHED(); |
| } |
| |
| void OnWebSocketMessage(int connection_id, const std::string& data) override { |
| NOTREACHED(); |
| } |
| |
| void OnClose(int connection_id) override { |
| DCHECK(connection_map_.find(connection_id) != connection_map_.end()); |
| connection_map_[connection_id] = false; |
| if (connection_id == quit_on_close_connection_) |
| run_loop_quit_func_.Run(); |
| } |
| |
| // This waits until the _total_ requests received is equal to |count|. This |
| // means that, after waiting for one request, in order to wait for a single |
| // additional request, |count| must be 2, not 1. |
| void RunUntilRequestsReceived(size_t count) { |
| quit_after_request_count_ = count; |
| if (requests_.size() == count) |
| return; |
| |
| base::RunLoop run_loop; |
| run_loop_quit_func_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| run_loop_quit_func_.Reset(); |
| } |
| |
| void RunUntilConnectionIdClosed(int connection_id) { |
| quit_on_close_connection_ = connection_id; |
| auto iter = connection_map_.find(connection_id); |
| if (iter != connection_map_.end() && !iter->second) { |
| // Already disconnected. |
| return; |
| } |
| |
| base::RunLoop run_loop; |
| run_loop_quit_func_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| run_loop_quit_func_.Reset(); |
| } |
| |
| HttpServerRequestInfo GetRequest(size_t request_index) { |
| return requests_[request_index].first; |
| } |
| |
| size_t num_requests() const { return requests_.size(); } |
| |
| int GetConnectionId(size_t request_index) { |
| return requests_[request_index].second; |
| } |
| |
| HttpConnection* FindConnection(int connection_id) { |
| return server_->FindConnection(connection_id); |
| } |
| |
| std::unordered_map<int, bool>& connection_map() { return connection_map_; } |
| |
| protected: |
| std::unique_ptr<HttpServer> server_; |
| net::IPEndPoint server_address_; |
| base::RepeatingClosure run_loop_quit_func_; |
| std::vector<std::pair<HttpServerRequestInfo, int>> requests_; |
| std::unordered_map<int /* connection_id */, bool /* connected */> |
| connection_map_; |
| |
| private: |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| size_t quit_after_request_count_; |
| int quit_on_close_connection_; |
| |
| net::TestURLRequestContext url_request_context_; |
| SocketFactory factory_; |
| mojom::TCPServerSocketPtr server_socket_; |
| }; |
| |
| class WebSocketTest : public HttpServerTest { |
| void OnHttpRequest(int connection_id, |
| const HttpServerRequestInfo& info) override { |
| NOTREACHED(); |
| } |
| |
| void OnWebSocketRequest( |
| int connection_id, |
| const network::server::HttpServerRequestInfo& info) override { |
| HttpServerTest::OnHttpRequest(connection_id, info); |
| } |
| |
| void OnWebSocketMessage(int connection_id, const std::string& data) override { |
| } |
| }; |
| |
| TEST_F(HttpServerTest, SetNonexistingConnectionBuffer) { |
| EXPECT_FALSE(server_->SetReceiveBufferSize(1, 1000)); |
| EXPECT_FALSE(server_->SetSendBufferSize(1, 1000)); |
| } |
| |
| TEST_F(HttpServerTest, Request) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send("GET /test HTTP/1.1\r\n\r\n"); |
| |
| int connection_id = connection_map_.begin()->first; |
| HttpConnection* conn = FindConnection(connection_id); |
| EXPECT_TRUE(server_->SetReceiveBufferSize(connection_id, 5u * 1024 * 1024)); |
| EXPECT_TRUE(server_->SetSendBufferSize(connection_id, 5u * 1024 * 1024)); |
| EXPECT_EQ(conn->ReadBufferSize(), 5u * 1024u * 1024u); |
| EXPECT_EQ(conn->WriteBufferSize(), 5u * 1024u * 1024u); |
| |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ("GET", GetRequest(0).method); |
| ASSERT_EQ("/test", GetRequest(0).path); |
| ASSERT_EQ("", GetRequest(0).data); |
| ASSERT_EQ(0u, GetRequest(0).headers.size()); |
| ASSERT_EQ(GetRequest(0).peer.address(), net::IPAddress::IPv6Localhost()); |
| } |
| |
| TEST_F(HttpServerTest, RequestBrokenTermination) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send("GET /test HTTP/1.1\r\n\r)"); |
| RunUntilConnectionIdClosed(1); |
| EXPECT_EQ(0u, num_requests()); |
| client.ExpectUsedThenDisconnectedWithNoData(); |
| } |
| |
| TEST_F(HttpServerTest, RequestWithHeaders) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| const char* const kHeaders[][3] = { |
| {"Header", ": ", "1"}, |
| {"HeaderWithNoWhitespace", ":", "1"}, |
| {"HeaderWithWhitespace", " : \t ", "1 1 1 \t "}, |
| {"HeaderWithColon", ": ", "1:1"}, |
| {"EmptyHeader", ":", ""}, |
| {"EmptyHeaderWithWhitespace", ": \t ", ""}, |
| {"HeaderWithNonASCII", ": ", "\xf7"}, |
| }; |
| std::string headers; |
| for (size_t i = 0; i < base::size(kHeaders); ++i) { |
| headers += |
| std::string(kHeaders[i][0]) + kHeaders[i][1] + kHeaders[i][2] + "\r\n"; |
| } |
| |
| client.Send("GET /test HTTP/1.1\r\n" + headers + "\r\n"); |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ("", GetRequest(0).data); |
| |
| for (size_t i = 0; i < base::size(kHeaders); ++i) { |
| std::string field = base::ToLowerASCII(std::string(kHeaders[i][0])); |
| std::string value = kHeaders[i][2]; |
| ASSERT_EQ(1u, GetRequest(0).headers.count(field)) << field; |
| ASSERT_EQ(value, GetRequest(0).headers[field]) << kHeaders[i][0]; |
| } |
| } |
| |
| TEST_F(HttpServerTest, RequestWithDuplicateHeaders) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| const char* const kHeaders[][3] = { |
| {"FirstHeader", ": ", "1"}, {"DuplicateHeader", ": ", "2"}, |
| {"MiddleHeader", ": ", "3"}, {"DuplicateHeader", ": ", "4"}, |
| {"LastHeader", ": ", "5"}, |
| }; |
| std::string headers; |
| for (size_t i = 0; i < base::size(kHeaders); ++i) { |
| headers += |
| std::string(kHeaders[i][0]) + kHeaders[i][1] + kHeaders[i][2] + "\r\n"; |
| } |
| |
| client.Send("GET /test HTTP/1.1\r\n" + headers + "\r\n"); |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ("", GetRequest(0).data); |
| |
| for (size_t i = 0; i < base::size(kHeaders); ++i) { |
| std::string field = base::ToLowerASCII(std::string(kHeaders[i][0])); |
| std::string value = (field == "duplicateheader") ? "2,4" : kHeaders[i][2]; |
| ASSERT_EQ(1u, GetRequest(0).headers.count(field)) << field; |
| ASSERT_EQ(value, GetRequest(0).headers[field]) << kHeaders[i][0]; |
| } |
| } |
| |
| TEST_F(HttpServerTest, HasHeaderValueTest) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| const char* const kHeaders[] = { |
| "Header: Abcd", |
| "HeaderWithNoWhitespace:E", |
| "HeaderWithWhitespace : \t f \t ", |
| "DuplicateHeader: g", |
| "HeaderWithComma: h, i ,j", |
| "DuplicateHeader: k", |
| "EmptyHeader:", |
| "EmptyHeaderWithWhitespace: \t ", |
| "HeaderWithNonASCII: \xf7", |
| }; |
| std::string headers; |
| for (size_t i = 0; i < base::size(kHeaders); ++i) { |
| headers += std::string(kHeaders[i]) + "\r\n"; |
| } |
| |
| client.Send("GET /test HTTP/1.1\r\n" + headers + "\r\n"); |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ("", GetRequest(0).data); |
| |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("header", "abcd")); |
| ASSERT_FALSE(GetRequest(0).HasHeaderValue("header", "bc")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithnowhitespace", "e")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithwhitespace", "f")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("duplicateheader", "g")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithcomma", "h")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithcomma", "i")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithcomma", "j")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("duplicateheader", "k")); |
| ASSERT_FALSE(GetRequest(0).HasHeaderValue("emptyheader", "x")); |
| ASSERT_FALSE(GetRequest(0).HasHeaderValue("emptyheaderwithwhitespace", "x")); |
| ASSERT_TRUE(GetRequest(0).HasHeaderValue("headerwithnonascii", "\xf7")); |
| } |
| |
| TEST_F(HttpServerTest, RequestWithBody) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| std::string body = "a" + std::string(1 << 10, 'b') + "c"; |
| client.Send( |
| base::StringPrintf("GET /test HTTP/1.1\r\n" |
| "SomeHeader: 1\r\n" |
| "Content-Length: %" PRIuS "\r\n\r\n%s", |
| body.length(), body.c_str())); |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ(2u, GetRequest(0).headers.size()); |
| ASSERT_EQ(body.length(), GetRequest(0).data.length()); |
| ASSERT_EQ('a', body[0]); |
| ASSERT_EQ('c', *body.rbegin()); |
| } |
| |
| // Tests that |HttpServer:OnReadable()| will notice the closure of the connected |
| // socket and not try to read from an invalid pipe. |
| TEST_F(WebSocketTest, PipeClosed) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send( |
| "GET /test HTTP/1.1\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: SomethingElse, Upgrade\r\n" |
| "Sec-WebSocket-Version: 8\r\n" |
| "Sec-WebSocket-Key: key\r\n" |
| "\r\n"); |
| RunUntilRequestsReceived(1); |
| client.Close(); |
| RunUntilConnectionIdClosed(1); |
| } |
| |
| TEST_F(WebSocketTest, RequestWebSocket) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send( |
| "GET /test HTTP/1.1\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: SomethingElse, Upgrade\r\n" |
| "Sec-WebSocket-Version: 8\r\n" |
| "Sec-WebSocket-Key: key\r\n" |
| "\r\n"); |
| RunUntilRequestsReceived(1); |
| } |
| |
| TEST_F(WebSocketTest, RequestWebSocketTrailingJunk) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send( |
| "GET /test HTTP/1.1\r\n" |
| "Upgrade: WebSocket\r\n" |
| "Connection: SomethingElse, Upgrade\r\n" |
| "Sec-WebSocket-Version: 8\r\n" |
| "Sec-WebSocket-Key: key\r\n" |
| "\r\nHello? Anyone"); |
| RunUntilConnectionIdClosed(1); |
| client.ExpectUsedThenDisconnectedWithNoData(); |
| } |
| |
| TEST_F(HttpServerTest, RequestWithTooLargeBody) { |
| class TestURLFetcherDelegate : public net::URLFetcherDelegate { |
| public: |
| TestURLFetcherDelegate(const base::RepeatingClosure& quit_loop_func) |
| : quit_loop_func_(quit_loop_func) {} |
| ~TestURLFetcherDelegate() override = default; |
| |
| void OnURLFetchComplete(const net::URLFetcher* source) override { |
| EXPECT_EQ(net::HTTP_INTERNAL_SERVER_ERROR, source->GetResponseCode()); |
| quit_loop_func_.Run(); |
| } |
| |
| private: |
| base::RepeatingClosure quit_loop_func_; |
| }; |
| |
| base::RunLoop run_loop; |
| TestURLFetcherDelegate delegate(run_loop.QuitClosure()); |
| |
| scoped_refptr<net::URLRequestContextGetter> request_context_getter( |
| new net::TestURLRequestContextGetter( |
| base::ThreadTaskRunnerHandle::Get())); |
| std::unique_ptr<net::URLFetcher> fetcher = net::URLFetcher::Create( |
| GURL(base::StringPrintf("http://[::1]:%d/test", server_address_.port())), |
| net::URLFetcher::GET, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); |
| fetcher->SetRequestContext(request_context_getter.get()); |
| fetcher->AddExtraRequestHeader( |
| base::StringPrintf("content-length:%d", 1 << 30)); |
| fetcher->Start(); |
| |
| run_loop.Run(); |
| ASSERT_EQ(0u, requests_.size()); |
| } |
| |
| TEST_F(HttpServerTest, Send200) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send("GET /test HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(1); |
| server_->Send200(GetConnectionId(0), "Response!", "text/plain", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| std::string response; |
| ASSERT_TRUE(client.ReadResponse(&response)); |
| ASSERT_TRUE(base::StartsWith(response, "HTTP/1.1 200 OK", |
| base::CompareCase::SENSITIVE)); |
| ASSERT_TRUE( |
| base::EndsWith(response, "Response!", base::CompareCase::SENSITIVE)); |
| } |
| |
| TEST_F(HttpServerTest, SendRaw) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send("GET /test HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(1); |
| server_->SendRaw(GetConnectionId(0), "Raw Data ", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| server_->SendRaw(GetConnectionId(0), "More Data", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| server_->SendRaw(GetConnectionId(0), "Third Piece of Data", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| const std::string expected_response("Raw Data More DataThird Piece of Data"); |
| std::string response; |
| ASSERT_TRUE(client.Read(&response, expected_response.length())); |
| ASSERT_EQ(expected_response, response); |
| } |
| |
| TEST_F(HttpServerTest, SendRawOverTwoConnections) { |
| TestHttpClient client1, client2; |
| |
| // Requests are staggered so that their order is deterministic - otherwise the |
| // test has no way of associating the response with the client object. |
| ASSERT_THAT(client1.ConnectAndWait(server_address_), IsOk()); |
| client1.Send("GET /test1 HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(1); |
| |
| ASSERT_THAT(client2.ConnectAndWait(server_address_), IsOk()); |
| client2.Send("GET /test2 HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(2); |
| |
| server_->SendRaw(GetConnectionId(0), "Raw Data ", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| server_->SendRaw(GetConnectionId(1), "More Data", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| server_->SendRaw(GetConnectionId(0), "Third Piece of Data", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| server_->SendRaw(GetConnectionId(1), "And #4", TRAFFIC_ANNOTATION_FOR_TESTS); |
| |
| const std::string expected_response1("Raw Data Third Piece of Data"); |
| const std::string expected_response2("More DataAnd #4"); |
| std::string response1, response2; |
| |
| ASSERT_TRUE(client1.Read(&response1, expected_response1.length())); |
| ASSERT_TRUE(client2.Read(&response2, expected_response2.length())); |
| ASSERT_EQ(expected_response1, response1); |
| ASSERT_EQ(expected_response2, response2); |
| } |
| |
| TEST_F(HttpServerTest, WrongProtocolRequest) { |
| const char* const kBadProtocolRequests[] = { |
| "GET /test HTTP/1.0\r\n\r\n", "GET /test foo\r\n\r\n", |
| "GET /test \r\n\r\n", |
| }; |
| |
| for (size_t i = 0; i < base::size(kBadProtocolRequests); ++i) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| |
| client.Send(kBadProtocolRequests[i]); |
| client.ExpectUsedThenDisconnectedWithNoData(); |
| |
| // Assert that the delegate was updated properly. |
| ASSERT_EQ(1u, connection_map().size()); |
| ASSERT_FALSE(connection_map().begin()->second); |
| EXPECT_EQ(0ul, requests_.size()); |
| |
| // Reset the state of the connection map. |
| connection_map().clear(); |
| } |
| } |
| |
| TEST_F(HttpServerTest, RequestWithBodySplitAcrossPackets) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| |
| std::string body("body"); |
| std::string request_text = base::StringPrintf( |
| "GET /test HTTP/1.1\r\n" |
| "SomeHeader: 1\r\n" |
| "Content-Length: %" PRIuS "\r\n\r\n%s", |
| body.length(), body.c_str()); |
| |
| std::string packet1(request_text.c_str(), request_text.length() - 2); |
| std::string packet2(request_text, request_text.length() - 2); |
| |
| client.Send(packet1); |
| base::RunLoop().RunUntilIdle(); |
| client.Send(packet2); |
| RunUntilRequestsReceived(1); |
| |
| ASSERT_EQ(1u, requests_.size()); |
| ASSERT_EQ(body, GetRequest(0).data); |
| } |
| |
| TEST_F(HttpServerTest, MultipleRequestsOnSameConnection) { |
| // The idea behind this test is that requests with or without bodies should |
| // not break parsing of the next request. |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| std::string body = "body"; |
| client.Send( |
| base::StringPrintf("GET /test HTTP/1.1\r\n" |
| "Content-Length: %" PRIuS "\r\n\r\n%s", |
| body.length(), body.c_str())); |
| RunUntilRequestsReceived(1); |
| ASSERT_EQ(body, GetRequest(0).data); |
| |
| int client_connection_id = GetConnectionId(0); |
| server_->Send200(client_connection_id, "Content for /test", "text/plain", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| std::string response1; |
| ASSERT_TRUE(client.ReadResponse(&response1)); |
| ASSERT_TRUE(base::StartsWith(response1, "HTTP/1.1 200 OK", |
| base::CompareCase::SENSITIVE)); |
| ASSERT_TRUE(base::EndsWith(response1, "Content for /test", |
| base::CompareCase::SENSITIVE)); |
| |
| client.Send("GET /test2 HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(2); |
| ASSERT_EQ("/test2", GetRequest(1).path); |
| |
| ASSERT_EQ(client_connection_id, GetConnectionId(1)); |
| server_->Send404(client_connection_id, TRAFFIC_ANNOTATION_FOR_TESTS); |
| std::string response2; |
| ASSERT_TRUE(client.ReadResponse(&response2)); |
| ASSERT_TRUE(base::StartsWith(response2, "HTTP/1.1 404 Not Found", |
| base::CompareCase::SENSITIVE)); |
| |
| client.Send("GET /test3 HTTP/1.1\r\n\r\n"); |
| RunUntilRequestsReceived(3); |
| ASSERT_EQ("/test3", GetRequest(2).path); |
| |
| ASSERT_EQ(client_connection_id, GetConnectionId(2)); |
| server_->Send200(client_connection_id, "Content for /test3", "text/plain", |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| std::string response3; |
| ASSERT_TRUE(client.ReadResponse(&response3)); |
| ASSERT_TRUE(base::StartsWith(response3, "HTTP/1.1 200 OK", |
| base::CompareCase::SENSITIVE)); |
| ASSERT_TRUE(base::EndsWith(response3, "Content for /test3", |
| base::CompareCase::SENSITIVE)); |
| } |
| |
| class CloseOnConnectHttpServerTest : public HttpServerTest { |
| public: |
| void OnConnect(int connection_id) override { |
| HttpServerTest::OnConnect(connection_id); |
| connection_ids_.push_back(connection_id); |
| server_->Close(connection_id); |
| } |
| |
| protected: |
| std::vector<int> connection_ids_; |
| }; |
| |
| TEST_F(CloseOnConnectHttpServerTest, ServerImmediatelyClosesConnection) { |
| TestHttpClient client; |
| ASSERT_THAT(client.ConnectAndWait(server_address_), IsOk()); |
| client.Send("GET / HTTP/1.1\r\n\r\n"); |
| |
| // The server should close the socket without responding. |
| client.ExpectUsedThenDisconnectedWithNoData(); |
| |
| // Run any tasks the TestServer posted. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(1ul, connection_ids_.size()); |
| // OnHttpRequest() should never have been called, since the connection was |
| // closed without reading from it. |
| EXPECT_EQ(0ul, requests_.size()); |
| } |
| |
| } // namespace server |
| |
| } // namespace network |