// uvco (c) 2025 Lewin Bormann. See LICENSE for specific terms. #include "uvco/combinators.h" #include "uvco/examples/http_server.h" #include "uvco/name_resolution.h" #include "uvco/promise/promise.h" #include "uvco/run.h" #include "uvco/tcp.h" #include #include #include #include #include #include #include #include #include #include using namespace uvco; using namespace uvco::examples; namespace { static constexpr std::string_view serverHost = "127.0.0.1"; Promise> bindServer(const Loop &loop) { Resolver resolver(loop); auto address = co_await resolver.gai(serverHost, 0, AF_INET); TcpServer server(loop, address); const uint16_t boundPort = server.getSockname().port(); co_return {std::move(server), boundPort}; } Router defaultRoutes() { Router router; router.addRoute("/test", [](const HttpRequest &) -> Promise { HttpResponse res; res.body = "Test Response"; co_return res; }); return router; } TEST(HttpServerTest, SimpleRequest) { runMain([](const Loop &loop) -> Promise { auto [server, boundPort] = co_await bindServer(loop); auto clientLogic = [&]() -> Promise { TcpClient client(loop, std::string{serverHost}, boundPort); auto stream = co_await client.connect(); auto paths = std::to_array({"/", "/test", "/notfound"}); for (const std::string &path : paths) { std::string req = fmt::format("GET {} HTTP/1.1\r\nHost: localhost\r\nConnection: " "keep-alive\r\n\r\n", path); co_await stream.write(req); std::array buffer{}; const size_t bytesRead = co_await stream.read(std::span(buffer)); const std::string_view response(buffer.data(), bytesRead); EXPECT_TRUE(response.find("HTTP/1.1") != std::string::npos); } // Last request, send with connection-close co_await stream.write( "GET /test HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); std::array buffer{}; const size_t bytesRead = co_await stream.read(buffer); const std::string_view response(buffer.data(), bytesRead); EXPECT_TRUE(response.find("Test Response") != std::string::npos); // Check that connection is closed EXPECT_EQ(0, co_await stream.read(buffer)); stream.close(); }; co_await raceIgnore(httpServer(server, defaultRoutes()), clientLogic()); }); } TEST(HttpServerTest, invalidRequest) { runMain([](const Loop &loop) -> Promise { auto [server, boundPort] = co_await bindServer(loop); auto clientLogic = [&]() -> Promise { std::array invalidRequests = std::to_array( {"\r\n\r\n", "GET\r\n\r\n", "GET \r\n\r\n", " \r\n\r\n", "GET\r\nHost: localhost\r\n\r\n"}); for (const std::string &invalidRequest : invalidRequests) { TcpClient client(loop, std::string{serverHost}, boundPort); auto stream = co_await client.connect(); co_await stream.write(invalidRequest); std::array buffer{}; const size_t bytesRead = co_await stream.read(buffer); const std::string_view response(buffer.data(), bytesRead); EXPECT_TRUE(response.find("400 Bad Request") != std::string::npos); // Check what happens if we write to the (remotely) closed stream co_await stream.write( "GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n"); EXPECT_EQ(0, co_await stream.read(buffer)); stream.close(); } }; co_await raceIgnore(httpServer(server, defaultRoutes()), clientLogic()); }); } } // namespace