From 6232d13f5321b87ddf12a1aa36b4545da45f173d Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Wed, 17 Nov 2021 22:34:57 +0100 Subject: Travel3: Simple image and video display site Reads the images and videos from filesystem and builds a site in memroy. --- test/test_transport_fcgi.cc | 490 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 test/test_transport_fcgi.cc (limited to 'test/test_transport_fcgi.cc') diff --git a/test/test_transport_fcgi.cc b/test/test_transport_fcgi.cc new file mode 100644 index 0000000..7ef1bde --- /dev/null +++ b/test/test_transport_fcgi.cc @@ -0,0 +1,490 @@ +#include "common.hh" + +#include "config.hh" +#include "fcgi_protocol.hh" +#include "file_test.hh" +#include "io.hh" +#include "logger.hh" +#include "looper.hh" +#include "socket_test.hh" +#include "str_buffer.hh" +#include "task_runner.hh" +#include "transport_fastcgi.hh" + +#include +#include + +namespace { + +class TransportFcgiTest : public SocketTest, public Transport::Handler { +public: + ~TransportFcgiTest() 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_fastcgi(); + 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, uint16_t request_id, + std::string const& path, uint8_t flags, + size_t step) { + switch (step) { + case 0: { + auto builder = fcgi::RecordBuilder::create_begin_request( + request_id, fcgi::Role::Responder, flags); + cli->write([&] (Buffer* buf) { + ASSERT_TRUE(builder->build(buf)); + }); + break; + } + case 1: { + auto pair_builder = fcgi::PairBuilder::create(); + pair_builder->add("REQUEST_METHOD", "GET"); + pair_builder->add("REQUEST_URI", path); + auto builder = fcgi::RecordBuilder::create(fcgi::RecordType::Params, + request_id, + pair_builder->size()); + cli->write([&] (Buffer* buf) { + ASSERT_TRUE(builder->build(buf)); + ASSERT_TRUE(pair_builder->build(buf)); + ASSERT_TRUE(builder->padding(buf)); + }); + break; + } + case 2: { + auto builder = fcgi::RecordBuilder::create(fcgi::RecordType::Params, + request_id, + std::string()); + cli->write([&] (Buffer* buf) { + ASSERT_TRUE(builder->build(buf)); + }); + break; + } + case 3: { + auto builder = fcgi::RecordBuilder::create(fcgi::RecordType::Stdin, + request_id, + std::string()); + cli->write([&] (Buffer* buf) { + ASSERT_TRUE(builder->build(buf)); + }); + break; + } + default: + FAIL(); + } +} + +void make_request(SocketTest::Client* cli, uint16_t request_id, + std::string const& path, uint8_t flags) { + for (size_t step = 0; step < 4; ++step) { + make_request(cli, request_id, path, flags, step); + } +} + +struct Response { + std::unique_ptr record; + std::unique_ptr stdout_stream; + std::string stdout; + std::optional ended; + + void reset() { + record.reset(); + stdout_stream.reset(); + stdout.clear(); + ended.reset(); + } +}; + +void read_response_content(SocketTest::Client* cli, uint16_t request_id, + Response* response, bool* need_more) { + assert(!response->ended.has_value()); + while (true) { + assert(response->record); + ASSERT_EQ(request_id, response->record->request_id()); + if (response->record->type() == fcgi::RecordType::EndRequest) { + if (cli->received().size() < + response->record->content_length() + + response->record->padding_length()) { + ASSERT_FALSE(cli->closed()); + *need_more = true; + return; + } + EXPECT_EQ(8, response->record->content_length()); + response->ended = cli->received().substr( + 0, response->record->content_length()); + cli->forget(response->record->content_length() + + response->record->padding_length()); + response->record.reset(); + *need_more = false; + return; + } + ASSERT_EQ(fcgi::RecordType::Stdout, response->record->type()); + cli->read([&] (RoBuffer* buf) { + size_t avail; + auto* ptr = response->stdout_stream->rbuf(buf, 1, avail); + response->stdout.append(ptr, avail); + response->stdout_stream->rcommit(buf, avail); + }); + if (response->stdout_stream->end_of_record()) { + response->record.reset(); + *need_more = false; + return; + } + } +} + +void read_responses(SocketTest::Client* cli, + std::mapconst& responses, + bool* need_more) { + *need_more = false; + auto it = responses.begin(); + while (it != responses.end()) { + if (it->second->record) { + read_response_content(cli, it->first, it->second, need_more); + if (*need_more) + return; + } else { + ++it; + } + } + + while (true) { + bool all_ended = true; + for (auto& pair : responses) { + if (!pair.second->ended.has_value()) { + all_ended = false; + break; + } + } + if (all_ended) { + *need_more = false; + return; + } + + std::unique_ptr record; + cli->read([&] (RoBuffer* buf) { + record = fcgi::Record::parse(buf); + }); + if (!record) { + ASSERT_FALSE(cli->closed()); + *need_more = true; + return; + } + ASSERT_TRUE(record->good()); + it = responses.find(record->request_id()); + if (it == responses.end() || it->second->ended.has_value()) { + FAIL(); + return; + } + auto* response = it->second; + response->record = std::move(record); + if (response->record->type() == fcgi::RecordType::Stdout) { + if (!response->stdout_stream) { + response->stdout_stream = + fcgi::RecordStream::create_stream(response->record.get()); + } else { + ASSERT_FALSE(response->stdout_stream->end_of_stream()); + response->stdout_stream->add(response->record.get()); + } + } + + do { + *need_more = false; + read_response_content(cli, it->first, response, need_more); + if (*need_more) + return; + } while (response->record); + } +} + +void read_response(SocketTest::Client* cli, uint16_t request_id, + Response* response, bool* need_more) { + std::map responses; + responses[request_id] = response; + read_responses(cli, responses, need_more); +} + +struct GetValuesResponse { + std::unique_ptr record; + std::unique_ptr stream; + std::unique_ptr pair; + std::vector> values; +}; + +void read_response(SocketTest::Client* cli, GetValuesResponse* response, + bool* need_more) { + while (true) { + if (!response->record) { + cli->read([&] (RoBuffer* buf) { + response->record = fcgi::Record::parse(buf); + }); + if (!response->record) { + ASSERT_FALSE(cli->closed()); + *need_more = true; + return; + } + ASSERT_TRUE(response->record->good()); + ASSERT_EQ(fcgi::RecordType::GetValuesResult, response->record->type()); + if (!response->stream) { + response->stream = + fcgi::RecordStream::create_single(response->record.get()); + } + if (!response->pair) { + cli->read([&] (RoBuffer* buf) { + response->pair = fcgi::Pair::start(response->stream.get(), buf); + }); + if (!response->pair) { + ASSERT_TRUE(response->stream->end_of_record()); + response->record.reset(); + *need_more = true; + return; + } + ASSERT_TRUE(response->pair->good()); + response->values.emplace_back(response->pair->name(), + response->pair->value()); + } + } + + ASSERT_TRUE(response->pair); + + while (true) { + bool next; + cli->read([&] (RoBuffer* buf) { + next = response->pair->next(response->stream.get(), buf); + }); + if (next) { + response->values.emplace_back(response->pair->name(), + response->pair->value()); + } else { + ASSERT_TRUE(response->pair->good()); + response->record.reset(); + if (response->stream->end_of_stream()) { + *need_more = false; + return; + } + *need_more = true; + return; + } + } + } +} + +} // namespace + +TEST_F(TransportFcgiTest, 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(), 1, "/hello_world", 0); + + Response response; + + while (true) { + bool need_more; + read_response(cli.get(), 1, &response, &need_more); + if (need_more) { + cli->wait(logger()); + continue; + } else { + break; + } + } + + ASSERT_TRUE(response.stdout_stream); + EXPECT_TRUE(response.stdout_stream->end_of_stream()); + EXPECT_EQ("Status: 200\r\nContent-Length: 12\r\n\r\nHello World!", + response.stdout); + EXPECT_EQ(std::string_view("\0\0\0\0\0\0\0\0", 8), response.ended); + if (!cli->closed()) + cli->wait(logger()); + EXPECT_TRUE(cli->closed()); +} + +TEST_F(TransportFcgiTest, 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(), 1, "/hello_world", fcgi::KeepConn); + + Response response; + + while (true) { + bool need_more; + read_response(cli.get(), 1, &response, &need_more); + if (need_more) { + cli->wait(logger()); + continue; + } else { + break; + } + } + + ASSERT_TRUE(response.stdout_stream); + EXPECT_TRUE(response.stdout_stream->end_of_stream()); + EXPECT_EQ("Status: 200\r\nContent-Length: 12\r\n\r\nHello World!", + response.stdout); + EXPECT_EQ(std::string_view("\0\0\0\0\0\0\0\0", 8), response.ended); + + make_request(cli.get(), 2, "/file", fcgi::KeepConn); + + response.reset(); + + while (true) { + bool need_more; + read_response(cli.get(), 2, &response, &need_more); + if (need_more) { + cli->wait(logger()); + continue; + } else { + break; + } + } + + ASSERT_TRUE(response.stdout_stream); + EXPECT_TRUE(response.stdout_stream->end_of_stream()); + EXPECT_EQ("Status: 200\r\n\r\nfoobar", response.stdout); + EXPECT_EQ(std::string_view("\0\0\0\0\0\0\0\0", 8), response.ended); +} + +TEST_F(TransportFcgiTest, multiplexed) { + 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)); + + for (size_t step = 0; step < 4; ++step) { + make_request(cli.get(), 1, "/hello_world", fcgi::KeepConn, step); + make_request(cli.get(), 2, "/file", fcgi::KeepConn, step); + } + + Response response1; + Response response2; + std::map responses; + responses[1] = &response1; + responses[2] = &response2; + + while (true) { + bool need_more; + read_responses(cli.get(), responses, &need_more); + if (need_more) { + cli->wait(logger()); + continue; + } else { + break; + } + } + + ASSERT_TRUE(response1.stdout_stream); + EXPECT_TRUE(response1.stdout_stream->end_of_stream()); + EXPECT_EQ("Status: 200\r\nContent-Length: 12\r\n\r\nHello World!", + response1.stdout); + EXPECT_EQ(std::string_view("\0\0\0\0\0\0\0\0", 8), response1.ended); + + ASSERT_TRUE(response2.stdout_stream); + EXPECT_TRUE(response2.stdout_stream->end_of_stream()); + EXPECT_EQ("Status: 200\r\n\r\nfoobar", response2.stdout); + EXPECT_EQ(std::string_view("\0\0\0\0\0\0\0\0", 8), response2.ended); +} + +TEST_F(TransportFcgiTest, get_values) { + 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)); + + auto pair_builder = fcgi::PairBuilder::create(); + pair_builder->add("FCGI_MAX_CONNS", ""); + pair_builder->add("FCGI_MAX_REQS", ""); + pair_builder->add("foobar", ""); + auto builder = fcgi::RecordBuilder::create(fcgi::RecordType::GetValues, + 0, + pair_builder->size()); + cli->write([&] (Buffer* buf) { + ASSERT_TRUE(builder->build(buf)); + ASSERT_TRUE(pair_builder->build(buf)); + ASSERT_TRUE(builder->padding(buf)); + }); + + GetValuesResponse 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.stream); + EXPECT_TRUE(response.stream->end_of_stream()); + EXPECT_EQ(2, response.values.size()); + for (auto const& value_pair : response.values) { + if (value_pair.first == "FCGI_MAX_REQS") { + EXPECT_EQ("20", value_pair.second); + } else if (value_pair.first == "FCGI_MAX_CONNS") { + EXPECT_EQ("10", value_pair.second); + } else { + EXPECT_EQ("FCGI_MAX_REQS", value_pair.first); + } + } +} -- cgit v1.2.3-70-g09d2