this repo has no description
1// Project Includes
2#include "client.hpp"
3#include "error.hpp"
4#include "file_descriptor.hpp"
5#include "server.hpp"
6#include "uring_context.hpp"
7// Standard Library Includes
8#include <cstdint>
9#include <exception>
10#include <expected>
11#include <netinet/in.h>
12#include <print>
13// Third-Party Library Includes
14#include <argparse/argparse.hpp>
15#include <exec/async_scope.hpp>
16#include <exec/repeat_effect_until.hpp>
17#include <exec/static_thread_pool.hpp>
18#include <stdexec/execution.hpp>
19
20std::expected<uint32_t, Error> parse_port(int argc, char *argv[])
21{
22 if (argc < 2)
23 {
24 return std::unexpected(Error("Port number not provided"));
25 }
26 try
27 {
28 int port = std::stoi(argv[1]);
29 if (port < 1 || port > 65535)
30 {
31 return std::unexpected(Error("Port number must be between 1 and 65535"));
32 }
33 return static_cast<uint16_t>(port);
34 }
35 catch (std::exception const &ex)
36 {
37 auto message = std::string("Invalid port number: ") + ex.what();
38 return std::unexpected(Error(message));
39 }
40}
41
42kev::task<void> listen(Server &server, UringContext &uring_ctx)
43{
44 exec::async_scope scope{};
45 while (true)
46 {
47 RawFileDescriptor fd{-1};
48 try
49 {
50 fd = co_await uring_ctx.async_accept(server.m_server_fd.get());
51 }
52 catch (const std::exception &e)
53 {
54 std::println(std::cerr, "Error accepting connection: {}", e.what());
55 break;
56 }
57 auto client_pipeline = handle_connection_coroutine(FileDescriptor(fd), server.m_context, uring_ctx) |
58 stdexec::upon_error([](std::exception_ptr ptr) {
59 try
60 {
61 if (ptr)
62 {
63 std::rethrow_exception(ptr);
64 }
65 }
66 catch (const std::exception &e)
67 {
68 std::println(std::cerr, "Error in client connection: {}", e.what());
69 }
70 });
71 scope.spawn(std::move(client_pipeline));
72 }
73}
74
75int main(int argc, char *argv[])
76{
77 argparse::ArgumentParser program("webserver");
78 uint16_t port{};
79 std::string root;
80 program.add_argument("--port", "-p")
81 .default_value<uint16_t>(8080)
82 .help("Port number to listen on")
83 .store_into(port);
84 program.add_argument("--root", "-r")
85 .default_value(std::string("."))
86 .help("Root directory for serving files")
87 .store_into(root);
88 try
89 {
90 program.parse_args(argc, argv);
91 }
92 catch (const std::exception &e)
93 {
94 std::println(std::cerr, "Error parsing arguments: {}", e.what());
95 return 1;
96 }
97 // Validate that the root directory exists
98 if (!std::filesystem::exists(root) || !std::filesystem::is_directory(root))
99 {
100 std::println(std::cerr, "Error: Root directory '{}' does not exist or is not a directory.", root);
101 return 1;
102 }
103
104 UringContext uring_ctx = UringContext(1024);
105 auto server = Server(port, root);
106 uring_ctx.run(listen(server, uring_ctx));
107}