blob: b77d00f9e69691f02668b341ba03ddaabf939d4d [file] [log] [blame]
// 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](zx_status_t status) { 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);
};
class TestZxHandleWatcher : public base::MessagePumpFuchsia::ZxHandleWatcher {
public:
explicit TestZxHandleWatcher(base::OnceClosure on_signaled)
: on_signaled_(std::move(on_signaled)) {}
~TestZxHandleWatcher() override = default;
// ZxHandleWatcher implementation.
void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override {
signals_ = signals;
std::move(on_signaled_).Run();
}
zx_signals_t signals() { return signals_; }
protected:
base::OnceClosure on_signaled_;
zx_signals_t signals_ = 0;
};
// Runs MessageLoop until one of the specified |signals| is signaled on the
// |handle|. Return observed signals.
zx_signals_t RunLoopUntilSignal(zx_handle_t handle, zx_signals_t signals) {
base::RunLoop run_loop;
TestZxHandleWatcher watcher(run_loop.QuitClosure());
base::MessagePumpForIO::ZxHandleWatchController watch_contoller(FROM_HERE);
base::MessageLoopCurrentForIO::Get()->WatchZxHandle(
handle, /*persistent=*/false, signals, &watch_contoller, &watcher);
run_loop.Run();
return watcher.signals();
}
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 signals = RunLoopUntilSignal(
stream.get(), ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED);
if (signals & ZX_SOCKET_READABLE) {
// Attempt to read again now that the socket is readable.
continue;
} else if (signals & 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