| // 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 <fuchsia/net/oldhttp/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding.h> |
| |
| #include "base/fuchsia/component_context.h" |
| #include "base/fuchsia/scoped_service_binding.h" |
| #include "base/fuchsia/service_directory.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "net/base/net_errors.h" |
| #include "net/test/embedded_test_server/default_handlers.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "webrunner/net_http/http_service_impl.h" |
| #include "webrunner/net_http/url_loader_impl.h" |
| |
| namespace net_http { |
| |
| namespace oldhttp = ::fuchsia::net::oldhttp; |
| |
| namespace { |
| |
| const base::FilePath::CharType kTestFilePath[] = |
| FILE_PATH_LITERAL("webrunner/net_http/testdata"); |
| |
| // Capacity, in bytes, for buffers used to read data off the URLResponse. |
| const size_t kBufferCapacity = 1024; |
| |
| using ResponseHeaders = std::multimap<std::string, std::string>; |
| |
| class HttpServiceTest : public ::testing::Test { |
| public: |
| HttpServiceTest() |
| : task_environment_( |
| base::test::ScopedTaskEnvironment::MainThreadType::IO), |
| binding_(&http_service_server_) { |
| // Initialize the test server. |
| test_server_.AddDefaultHandlers( |
| base::FilePath(FILE_PATH_LITERAL(kTestFilePath))); |
| net::test_server::RegisterDefaultHandlers(&test_server_); |
| } |
| |
| protected: |
| base::test::ScopedTaskEnvironment task_environment_; |
| |
| void SetUp() override { |
| ASSERT_TRUE(test_server_.Start()); |
| |
| // Bind the service with the client-side interface. |
| binding_.Bind(http_service_interface_.NewRequest()); |
| } |
| |
| void TearDown() override { |
| // Disconnect the client and wait for the service to shut down. |
| base::RunLoop run_loop; |
| binding_.set_error_handler([&run_loop]() { run_loop.Quit(); }); |
| http_service_interface_.Unbind(); |
| run_loop.Run(); |
| binding_.set_error_handler(nullptr); |
| |
| // Check there are no pending requests. |
| EXPECT_EQ(URLLoaderImpl::GetNumActiveRequestsForTests(), 0); |
| } |
| |
| // Helper method to start |request| on |url_loader| |
| void ExecuteRequest(const oldhttp::URLLoaderPtr& url_loader, |
| oldhttp::URLRequest request) { |
| base::RunLoop run_loop; |
| |
| url_loader->Start(std::move(request), |
| [this, &run_loop](oldhttp::URLResponse response) { |
| run_loop.Quit(); |
| url_response_ = std::move(response); |
| }); |
| run_loop.Run(); |
| } |
| |
| net::EmbeddedTestServer* http_test_server() { return &test_server_; } |
| oldhttp::HttpServicePtr& http_service() { return http_service_interface_; } |
| oldhttp::URLResponse& url_response() { return url_response_; } |
| |
| private: |
| net::EmbeddedTestServer test_server_; |
| |
| HttpServiceImpl http_service_server_; |
| oldhttp::HttpServicePtr http_service_interface_; |
| fidl::Binding<oldhttp::HttpService> binding_; |
| oldhttp::URLResponse url_response_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HttpServiceTest); |
| }; |
| |
| void CheckResponseStream(const oldhttp::URLResponse& response, |
| const std::string& expected_response) { |
| EXPECT_TRUE(response.body->is_stream()); |
| |
| zx::socket stream = std::move(response.body->stream()); |
| size_t offset = 0; |
| |
| while (true) { |
| std::array<char, kBufferCapacity> buffer; |
| size_t size = 0; |
| zx_status_t result = stream.read(0, buffer.data(), kBufferCapacity, &size); |
| |
| if (result == ZX_ERR_SHOULD_WAIT) { |
| zx_signals_t observed; |
| stream.wait_one(ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED, |
| zx::time::infinite(), &observed); |
| if (observed & ZX_SOCKET_READABLE) { |
| // Attempt to read again now that the socket is readable. |
| continue; |
| } else if (observed & ZX_SOCKET_PEER_CLOSED) { |
| // Done reading. |
| break; |
| } else { |
| NOTREACHED(); |
| } |
| } else if (result == ZX_ERR_PEER_CLOSED) { |
| // Done reading. |
| break; |
| } |
| EXPECT_EQ(result, ZX_OK); |
| |
| EXPECT_TRUE(std::equal(buffer.begin(), buffer.begin() + size, |
| expected_response.begin() + offset)); |
| offset += size; |
| } |
| |
| EXPECT_EQ(offset, expected_response.length()); |
| } |
| |
| void CheckResponseBuffer(const oldhttp::URLResponse& response, |
| const std::string& expected_response) { |
| EXPECT_TRUE(response.body->is_buffer()); |
| |
| fuchsia::mem::Buffer mem_buffer = std::move(response.body->buffer()); |
| size_t response_size = mem_buffer.size; |
| EXPECT_EQ(mem_buffer.size, expected_response.length()); |
| |
| std::array<char, kBufferCapacity> buffer; |
| size_t offset = 0; |
| while (offset != mem_buffer.size) { |
| size_t length = std::min(response_size - offset, kBufferCapacity); |
| zx_status_t result = mem_buffer.vmo.read(buffer.data(), offset, length); |
| EXPECT_EQ(result, ZX_OK); |
| |
| EXPECT_TRUE(std::equal(buffer.begin(), buffer.begin() + response_size, |
| expected_response.begin() + offset)); |
| offset += response_size; |
| } |
| |
| EXPECT_EQ(offset, expected_response.length()); |
| } |
| |
| void CheckResponseHeaders(const oldhttp::URLResponse& response, |
| ResponseHeaders* expected_headers) { |
| for (auto& header : response.headers.get()) { |
| const std::string header_name = header.name->data(); |
| const std::string header_value = header.value->data(); |
| auto iter = std::find_if(expected_headers->begin(), expected_headers->end(), |
| [&header_name, &header_value](auto& elt) -> bool { |
| return elt.first.compare(header_name) == 0 && |
| elt.second.compare(header_value) == 0; |
| }); |
| EXPECT_NE(iter, expected_headers->end()) |
| << "Unexpected header: \"" << header_name << "\" with value: \"" |
| << header_value << "\"."; |
| if (iter != expected_headers->end()) { |
| expected_headers->erase(iter); |
| } |
| } |
| EXPECT_TRUE(expected_headers->empty()); |
| } |
| |
| } // namespace |
| |
| // Check a basic end-to-end request resolution with the response being streamed |
| // is handled properly. |
| TEST_F(HttpServiceTest, BasicRequestStream) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/simple.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| CheckResponseStream(url_response(), "hello"); |
| } |
| |
| // Check a basic end-to-end request resolution with the response being |
| // buffered is handled properly. |
| TEST_F(HttpServiceTest, BasicRequestBuffer) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/simple.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::BUFFER; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| CheckResponseBuffer(url_response(), "hello"); |
| } |
| |
| // Check network request headers are received properly. |
| TEST_F(HttpServiceTest, RequestWithHeaders) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/with-headers.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| CheckResponseStream( |
| url_response(), |
| "This file is boring; all the action's in the .mock-http-headers.\n"); |
| ResponseHeaders expected_headers = { |
| {"Cache-Control", "private"}, |
| {"Content-Type", "text/html; charset=ISO-8859-1"}, |
| {"X-Multiple-Entries", "a"}, |
| {"X-Multiple-Entries", "b"}, |
| }; |
| CheckResponseHeaders(url_response(), &expected_headers); |
| } |
| |
| // Check duplicate network request headers are received properly. |
| TEST_F(HttpServiceTest, RequestWithDuplicateHeaders) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = |
| http_test_server()->GetURL("/with-duplicate-headers.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| CheckResponseStream( |
| url_response(), |
| "This file is boring; all the action's in the .mock-http-headers.\n"); |
| ResponseHeaders expected_headers = { |
| {"Cache-Control", "private"}, |
| {"Content-Type", "text/html; charset=ISO-8859-1"}, |
| {"X-Multiple-Entries", "a"}, |
| {"X-Multiple-Entries", "a"}, |
| {"X-Multiple-Entries", "b"}, |
| }; |
| CheckResponseHeaders(url_response(), &expected_headers); |
| } |
| |
| // Check a request with automatic redirect resolution is handled properly. |
| TEST_F(HttpServiceTest, AutoRedirect) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/redirect-test.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| request.auto_follow_redirects = true; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| EXPECT_EQ(url_response().url, |
| http_test_server()->GetURL("/with-headers.html").spec()); |
| } |
| |
| // Check a request with manual redirect resolution is handled properly. |
| TEST_F(HttpServiceTest, ManualRedirect) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| std::string request_url = |
| http_test_server()->GetURL("/redirect-test.html").spec(); |
| |
| oldhttp::URLRequest request; |
| request.url = request_url; |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| request.auto_follow_redirects = false; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| std::string final_url = |
| http_test_server()->GetURL("/with-headers.html").spec(); |
| EXPECT_EQ(url_response().status_code, 302u); |
| EXPECT_EQ(url_response().url, request_url); |
| EXPECT_EQ(url_response().redirect_url, final_url); |
| |
| base::RunLoop run_loop; |
| url_loader->FollowRedirect( |
| [&run_loop, &final_url](oldhttp::URLResponse response) { |
| EXPECT_EQ(response.status_code, 200u); |
| EXPECT_EQ(response.url, final_url); |
| run_loop.Quit(); |
| }); |
| run_loop.Run(); |
| } |
| |
| // Check HTTP error codes are properly populated. |
| TEST_F(HttpServiceTest, HttpErrorCode) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server() |
| ->base_url() |
| .Resolve("/non_existent_cooper.html") |
| .spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 404u); |
| } |
| |
| // Check network error codes are properly populated. |
| TEST_F(HttpServiceTest, InvalidURL) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = "ht\\tp://test.test/"; |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().error->code, net::ERR_INVALID_URL); |
| } |
| |
| // Ensure the service can handle multiple concurrent requests. |
| TEST_F(HttpServiceTest, MultipleRequests) { |
| oldhttp::URLLoaderPtr url_loaders[100]; |
| for (int i = 0; i < 100; i++) { |
| http_service()->CreateURLLoader(url_loaders[i].NewRequest()); |
| } |
| |
| base::RunLoop run_loop; |
| int requests_done = 0; |
| for (int i = 0; i < 100; i++) { |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/simple.html").spec(); |
| request.method = "GET"; |
| request.response_body_mode = (i % 2) == 0 |
| ? oldhttp::ResponseBodyMode::STREAM |
| : oldhttp::ResponseBodyMode::BUFFER; |
| url_loaders[i]->Start( |
| std::move(request), |
| [&requests_done, &run_loop](oldhttp::URLResponse response) { |
| EXPECT_EQ(response.status_code, 200u); |
| if (response.body->is_buffer()) { |
| CheckResponseBuffer(response, "hello"); |
| } else { |
| CheckResponseStream(response, "hello"); |
| } |
| requests_done++; |
| if (requests_done == 100) { |
| // Last request signals the run_loop to exit. |
| run_loop.Quit(); |
| } |
| }); |
| } |
| run_loop.Run(); |
| } |
| |
| // Check QueryStatus works as expected when a request is loading. |
| // Also checks the request is properly deleted after the binding is destroyed. |
| TEST_F(HttpServiceTest, QueryStatus) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/hung-after-headers").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| // In socket mode, we should still get the response headers. |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().status_code, 200u); |
| |
| base::RunLoop run_loop; |
| url_loader->QueryStatus([&run_loop](oldhttp::URLLoaderStatus status) { |
| EXPECT_TRUE(status.is_loading); |
| run_loop.Quit(); |
| }); |
| run_loop.Run(); |
| } |
| |
| // Check the response error is properly set if the server disconnects early. |
| TEST_F(HttpServiceTest, CloseSocket) { |
| oldhttp::URLLoaderPtr url_loader; |
| http_service()->CreateURLLoader(url_loader.NewRequest()); |
| |
| oldhttp::URLRequest request; |
| request.url = http_test_server()->GetURL("/close-socket").spec(); |
| request.method = "GET"; |
| request.response_body_mode = oldhttp::ResponseBodyMode::STREAM; |
| |
| ExecuteRequest(url_loader, std::move(request)); |
| EXPECT_EQ(url_response().error->code, net::ERR_EMPTY_RESPONSE); |
| } |
| |
| } // namespace net_http |