14#include <linux/stat.h>
15#include <netinet/in.h>
16#include <sys/socket.h>
19static std::string bind_address =
"0.0.0.0";
20static std::string serve_directory =
".";
21static uint16_t port = 8080;
23static const char HTTP_200_TEMPLATE[] =
"HTTP/1.1 200 OK\r\n"
24 "Content-Length: %d\r\n"
27static const char HTTP_400[] =
"HTTP/1.1 400 Bad Request\r\n"
28 "Content-Length: 0\r\n"
31static const char HTTP_404[] =
"HTTP/1.1 404 Not Found\r\n"
32 "Content-Length: 0\r\n"
35static const char HTTP_500[] =
"HTTP/1.1 500 Internal Server Error\r\n"
36 "Content-Length: 0\r\n"
39constexpr size_t BACKLOG = 128;
42 constexpr size_t CHUNK_SIZE = 8192;
45 if (pipe(pipe_fds) != 0) {
46 std::perror(
"Failed to create pipe");
57 std::cerr << std::format(
"Error during splice operation: {}\n", n);
64 std::cerr << std::format(
"Error during splice operation: {}\n", n);
73std::string parse_request(
const std::string &request) {
74 size_t pos = request.find(
"\r\n");
75 if (pos == std::string::npos) {
79 std::string request_line = request.substr(0, pos);
80 size_t method_end = request_line.find(
' ');
81 if (method_end == std::string::npos) {
85 size_t path_start = method_end + 1;
86 size_t path_end = request_line.find(
' ', path_start);
87 if (path_end == std::string::npos) {
91 return request_line.substr(path_start, path_end - path_start);
98 std::cerr << std::format(
"Read error: {}\n", n);
103 std::string request(buffer, n);
106 std::string path = parse_request(request);
109 client_fd,
condy::buffer(HTTP_400,
sizeof(HTTP_400) - 1), 0);
114 path =
"/index.html";
116 std::string file_path = serve_directory + path;
120 client_fd,
condy::buffer(HTTP_404,
sizeof(HTTP_404) - 1), 0);
125 struct statx statx_buf;
127 file_fd,
"", AT_EMPTY_PATH | AT_STATX_SYNC_AS_STAT,
128 STATX_SIZE | STATX_MODE, &statx_buf);
130 std::cerr << std::format(
"Failed to statx file: {}\n", r_stat);
132 client_fd,
condy::buffer(HTTP_500,
sizeof(HTTP_500) - 1), 0);
137 if (!S_ISREG(statx_buf.stx_mode)) {
139 client_fd,
condy::buffer(HTTP_404,
sizeof(HTTP_404) - 1), 0);
145 char header_buffer[128];
147 std::snprintf(header_buffer,
sizeof(header_buffer), HTTP_200_TEMPLATE,
148 static_cast<int>(statx_buf.stx_size));
151 co_await sendfile(client_fd, file_fd);
158 sockaddr_in client_addr;
159 socklen_t client_len =
sizeof(client_addr);
161 server_fd, (
struct sockaddr *)&client_addr, &client_len, 0);
163 std::cerr << std::format(
"Failed to accept connection: {}\n",
172void prepare_address(
const std::string &host, uint16_t port,
174 memset(&addr, 0,
sizeof(addr));
175 addr.sin_family = AF_INET;
176 addr.sin_port = htons(port);
177 inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
180void usage(
const char *prog_name) {
181 std::cerr << std::format(
182 "Usage: {} [-h] [-b <address>] [-d <directory>] [-p <port>]\n"
183 " -h Show this help message\n"
184 " -b <address> Bind to the specified address (default: 0.0.0.0)\n"
185 " -d <directory> Serve directory (default: current directory)\n"
186 " -p <port> Port number to listen on (default: 8080)\n",
190int main(
int argc,
char **argv)
noexcept(
false) {
192 while ((opt = getopt(argc, argv,
"hb:d:p:")) != -1) {
198 bind_address = optarg;
201 serve_directory = optarg;
204 port =
static_cast<uint16_t
>(std::stoi(optarg));
212 sockaddr_in server_addr;
213 prepare_address(bind_address, port, server_addr);
215 int server_fd = socket(AF_INET, SOCK_STREAM, 0);
217 std::perror(
"Failed to create socket");
222 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
223 sizeof(optval)) < 0) {
224 std::perror(
"Failed to set socket options");
229 if (bind(server_fd, (
struct sockaddr *)&server_addr,
sizeof(server_addr)) <
231 std::perror(
"Failed to bind socket");
236 if (listen(server_fd, BACKLOG) < 0) {
237 std::perror(
"Failed to listen on socket");
242 std::cout << std::format(
"Serving HTTP on port {} (http://{}:{}/) ...\n",
243 port, bind_address, port);
Basic buffer types and conversion utilities.
Coroutine type used to define a coroutine function.
Main include file for the Condy library.
auto async_splice(Fd1 fd_in, int64_t off_in, Fd2 fd_out, int64_t off_out, unsigned int nbytes, unsigned int splice_flags)
See io_uring_prep_splice.
T sync_wait(Runtime &runtime, Coro< T, Allocator > coro)
Synchronously wait for a coroutine to complete in the given runtime.
auto async_statx(int dfd, const char *path, int flags, unsigned mask, struct statx *statxbuf)
See io_uring_prep_statx.
auto async_recv(Fd sockfd, Buffer &&buf, int flags)
See io_uring_prep_recv.
MutableBuffer buffer(void *data, size_t size)
Create a buffer object from various data sources.
auto async_close(int fd)
See io_uring_prep_close.
auto async_open(const char *path, int flags, mode_t mode)
See io_uring_prep_openat.
auto async_send(Fd sockfd, Buffer &&buf, int flags)
See io_uring_prep_send.
Task< T, Allocator > co_spawn(Runtime &runtime, Coro< T, Allocator > coro)
Spawn a coroutine as a task in the given runtime.
auto async_accept(Fd fd, struct sockaddr *addr, socklen_t *addrlen, int flags)
See io_uring_prep_accept.