#include "common.hh" #include "config.hh" #include "file_test.hh" #include "http_protocol.hh" #include "io.hh" #include "logger.hh" #include "looper.hh" #include "socket_test.hh" #include "str_buffer.hh" #include "strutil.hh" #include "task_runner.hh" #include "transport_http.hh" #include namespace { class TransportHttpTest : public SocketTest, public Transport::Handler { public: ~TransportHttpTest() override { if (!path_.empty()) { std::error_code err; std::filesystem::remove(path_, err); } } void SetUp() override { fd_ = FileTest::create_temp_file(std::string(), &path_); auto config = Config::create_empty(); auto factory = create_transport_factory_http(); handler_ = Transport::create_default_handler(logger_, this); transport_ = factory->create(logger_, looper(), runner_, logger_.get(), config.get(), handler_.get()); } void TearDown() override { transport_.reset(); handler_.reset(); runner_.reset(); } void write_file(std::string_view content) { ASSERT_TRUE(fd_); auto buffer = make_strbuffer(content); while (!buffer->empty()) { ASSERT_TRUE(io::drain(buffer.get(), fd_.get())); } ASSERT_TRUE(io::close(fd_.release())); } std::unique_ptr request( Transport* transport, Transport::Request const* request) override { if (request->method() == "GET") { if (request->path() == "/hello_world") return transport->create_ok_data("Hello World!"); if (request->path() == "/file") return transport->create_ok_file(path_); } return transport->create_not_found();; } Transport* transport() { return transport_.get(); } Logger* logger() { return logger_.get(); } private: std::shared_ptr logger_ = Logger::create_null(); std::shared_ptr runner_ = TaskRunner::create(looper()); std::unique_ptr handler_; std::unique_ptr transport_; std::filesystem::path path_; unique_fd fd_; }; void make_request(SocketTest::Client* cli, std::string const& path, bool keep_conn) { auto builder = HttpRequestBuilder::create( "GET", path, "HTTP", Version(1, 1)); if (!keep_conn) { builder->add_header("Connection", "close"); } cli->write([&] (Buffer* buf) { ASSERT_TRUE(builder->build(buf)); }); } struct Response { std::unique_ptr response; std::string content; bool ended{false}; void reset() { response.reset(); content.clear(); ended = false; } }; void read_response_content(SocketTest::Client* cli, Response* response) { assert(!response->ended); assert(response->response); auto len_str = response->response->first_header("content-length"); auto size = str::parse_uint64(len_str); if (size) { size_t need = *size - response->content.size(); if (need == 0) { response->ended = true; return; } auto received = cli->received(); if (received.size() < need) { response->content.append(received); cli->forget(received.size()); return; } response->ended = true; response->content.append(received.substr(0, need)); cli->forget(need); return; } size_t old = response->content.size(); response->content.append(cli->received()); cli->forget(response->content.size() - old); if (cli->closed()) response->ended = true; } void read_response(SocketTest::Client* cli, Response* response, bool* need_more) { if (!response->response) { cli->read([&] (RoBuffer* buf) { response->response = HttpResponse::parse(buf); }); if (!response->response) { ASSERT_FALSE(cli->closed()); *need_more = true; return; } ASSERT_TRUE(response->response->good()); } read_response_content(cli, response); if (!response->ended) { ASSERT_FALSE(cli->closed()); *need_more = true; return; } *need_more = false; } } // namespace TEST_F(TransportHttpTest, sanity) { auto pair = create_pair(); ASSERT_TRUE(pair.first && pair.second); transport()->add_client(std::move(pair.first)); auto cli = create_client(std::move(pair.second)); make_request(cli.get(), "/hello_world", false); Response response; while (true) { bool need_more; read_response(cli.get(), &response, &need_more); if (need_more) { cli->wait(logger()); continue; } else { break; } } ASSERT_TRUE(response.response); EXPECT_EQ(200, response.response->status_code()); EXPECT_EQ("OK", response.response->status_message()); EXPECT_EQ("HTTP", response.response->proto()); EXPECT_EQ(1, response.response->proto_version().major); EXPECT_EQ(1, response.response->proto_version().minor); EXPECT_EQ("Hello World!", response.content); if (!cli->closed()) cli->wait(logger()); EXPECT_TRUE(cli->closed()); } TEST_F(TransportHttpTest, reuse_conn) { write_file("foobar"); auto pair = create_pair(); ASSERT_TRUE(pair.first && pair.second); transport()->add_client(std::move(pair.first)); auto cli = create_client(std::move(pair.second)); make_request(cli.get(), "/hello_world", true); Response response; while (true) { bool need_more; read_response(cli.get(), &response, &need_more); if (need_more) { cli->wait(logger()); continue; } else { break; } } ASSERT_TRUE(response.response); EXPECT_EQ(200, response.response->status_code()); EXPECT_EQ("OK", response.response->status_message()); EXPECT_EQ("HTTP", response.response->proto()); EXPECT_EQ(1, response.response->proto_version().major); EXPECT_EQ(1, response.response->proto_version().minor); EXPECT_EQ("Hello World!", response.content); make_request(cli.get(), "/file", true); response.reset(); while (true) { bool need_more; read_response(cli.get(), &response, &need_more); if (need_more) { cli->wait(logger()); continue; } else { break; } } ASSERT_TRUE(response.response); EXPECT_EQ(200, response.response->status_code()); EXPECT_EQ("OK", response.response->status_message()); EXPECT_EQ("HTTP", response.response->proto()); EXPECT_EQ(1, response.response->proto_version().major); EXPECT_EQ(1, response.response->proto_version().minor); EXPECT_EQ("foobar", response.content); }