Condy v1.3.0
C++ Asynchronous System Call Layer for Linux
Loading...
Searching...
No Matches
file-server.cpp
Go to the documentation of this file.
1
5
6#include "condy/buffers.hpp"
7#include <arpa/inet.h>
8#include <condy.hpp>
9#include <cstdint>
10#include <cstdio>
11#include <cstring>
12#include <linux/stat.h>
13#include <netinet/in.h>
14#include <sys/socket.h>
15#include <unistd.h>
16
17static std::string bind_address = "0.0.0.0";
18static std::string serve_directory = ".";
19static uint16_t port = 8080;
20
21static const char HTTP_200_TEMPLATE[] = "HTTP/1.1 200 OK\r\n"
22 "Content-Length: %d\r\n"
23 "\r\n";
24
25static const char HTTP_400[] = "HTTP/1.1 400 Bad Request\r\n"
26 "Content-Length: 0\r\n"
27 "\r\n";
28
29static const char HTTP_404[] = "HTTP/1.1 404 Not Found\r\n"
30 "Content-Length: 0\r\n"
31 "\r\n";
32
33static const char HTTP_500[] = "HTTP/1.1 500 Internal Server Error\r\n"
34 "Content-Length: 0\r\n"
35 "\r\n";
36
37constexpr size_t BACKLOG = 128;
38
39condy::Coro<void> sendfile(int out_fd, int in_fd) {
40 constexpr size_t CHUNK_SIZE = 8192;
41
42 int pipe_fds[2];
43 if (pipe(pipe_fds) != 0) {
44 std::fprintf(stderr, "Failed to create pipe: %d\n", errno);
45 exit(1);
46 }
47
48 while (true) {
49 int n = co_await condy::async_splice(in_fd, -1, pipe_fds[1], -1,
50 CHUNK_SIZE, 0);
51 if (n == 0) { // End of file
52 co_return;
53 }
54 if (n < 0) {
55 std::fprintf(stderr, "Error during splice operation: %d\n", n);
56 exit(1);
57 }
58
59 n = co_await condy::async_splice(pipe_fds[0], -1, out_fd, -1,
60 CHUNK_SIZE, 0);
61 if (n < 0) {
62 std::fprintf(stderr, "Error during splice operation: %d\n", n);
63 exit(1);
64 }
65 }
66
67 co_await condy::async_close(pipe_fds[0]);
68 co_await condy::async_close(pipe_fds[1]);
69}
70
71std::string parse_request(const std::string &request) {
72 size_t pos = request.find("\r\n");
73 if (pos == std::string::npos) {
74 return "";
75 }
76
77 std::string request_line = request.substr(0, pos);
78 size_t method_end = request_line.find(' ');
79 if (method_end == std::string::npos) {
80 return "";
81 }
82
83 size_t path_start = method_end + 1;
84 size_t path_end = request_line.find(' ', path_start);
85 if (path_end == std::string::npos) {
86 return "";
87 }
88
89 return request_line.substr(path_start, path_end - path_start);
90}
91
92condy::Coro<void> session(int client_fd) {
93 char buffer[1024];
94 int n = co_await condy::async_recv(client_fd, condy::buffer(buffer), 0);
95 if (n < 0) {
96 std::fprintf(stderr, "Read error: %d\n", n);
97 co_await condy::async_close(client_fd);
98 co_return;
99 }
100
101 std::string request(buffer, n);
102
103 // Just for simplicity, we only partially parse the HTTP request here.
104 std::string path = parse_request(request);
105 if (path.empty()) {
106 co_await condy::async_send(
107 client_fd, condy::buffer(HTTP_400, sizeof(HTTP_400) - 1), 0);
108 co_await condy::async_close(client_fd);
109 co_return;
110 }
111 if (path == "/") {
112 path = "/index.html";
113 }
114 std::string file_path = serve_directory + path;
115 int file_fd = co_await condy::async_open(file_path.c_str(), O_RDONLY, 0);
116 if (file_fd < 0) {
117 co_await condy::async_send(
118 client_fd, condy::buffer(HTTP_404, sizeof(HTTP_404) - 1), 0);
119 co_await condy::async_close(client_fd);
120 co_return;
121 }
122
123 struct statx statx_buf;
124 int r_stat = co_await condy::async_statx(
125 file_fd, "", AT_EMPTY_PATH | AT_STATX_SYNC_AS_STAT,
126 STATX_SIZE | STATX_MODE, &statx_buf);
127 if (r_stat < 0) {
128 std::fprintf(stderr, "Failed to statx file: %d\n", r_stat);
129 co_await condy::async_send(
130 client_fd, condy::buffer(HTTP_500, sizeof(HTTP_500) - 1), 0);
131 co_await condy::async_close(file_fd);
132 co_await condy::async_close(client_fd);
133 co_return;
134 }
135 if (!S_ISREG(statx_buf.stx_mode)) {
136 co_await condy::async_send(
137 client_fd, condy::buffer(HTTP_404, sizeof(HTTP_404) - 1), 0);
138 co_await condy::async_close(file_fd);
139 co_await condy::async_close(client_fd);
140 co_return;
141 }
142
143 char header_buffer[128];
144 int header_length =
145 std::snprintf(header_buffer, sizeof(header_buffer), HTTP_200_TEMPLATE,
146 static_cast<int>(statx_buf.stx_size));
147 co_await condy::async_send(client_fd,
148 condy::buffer(header_buffer, header_length), 0);
149 co_await sendfile(client_fd, file_fd);
150 co_await condy::async_close(file_fd);
151 co_await condy::async_close(client_fd);
152}
153
154condy::Coro<int> co_main(int server_fd) {
155 while (true) {
156 sockaddr_in client_addr;
157 socklen_t client_len = sizeof(client_addr);
158 int client_fd = co_await condy::async_accept(
159 server_fd, (struct sockaddr *)&client_addr, &client_len, 0);
160 if (client_fd < 0) {
161 std::fprintf(stderr, "Failed to accept connection: %d\n",
162 client_fd);
163 co_return 1;
164 }
165
166 condy::co_spawn(session(client_fd)).detach();
167 }
168}
169
170void prepare_address(const std::string &host, uint16_t port,
171 sockaddr_in &addr) {
172 memset(&addr, 0, sizeof(addr));
173 addr.sin_family = AF_INET;
174 addr.sin_port = htons(port);
175 inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
176}
177
178void usage(const char *prog_name) {
179 std::printf(
180 "Usage: %s [-h] [-b <address>] [-d <directory>] [-p <port>]\n"
181 " -h Show this help message\n"
182 " -b <address> Bind to the specified address (default: 0.0.0.0)\n"
183 " -d <directory> Serve directory (default: current directory)\n"
184 " -p <port> Port number to listen on (default: 8080)\n",
185 prog_name);
186}
187
188int main(int argc, char **argv) noexcept(false) {
189 int opt;
190 while ((opt = getopt(argc, argv, "hb:d:p:")) != -1) {
191 switch (opt) {
192 case 'h':
193 usage(argv[0]);
194 return 0;
195 case 'b':
196 bind_address = optarg;
197 break;
198 case 'd':
199 serve_directory = optarg;
200 break;
201 case 'p':
202 port = static_cast<uint16_t>(std::stoi(optarg));
203 break;
204 default:
205 usage(argv[0]);
206 return 1;
207 }
208 }
209
210 sockaddr_in server_addr;
211 prepare_address(bind_address, port, server_addr);
212
213 int server_fd = socket(AF_INET, SOCK_STREAM, 0);
214 if (server_fd < 0) {
215 std::perror("Failed to create socket");
216 return 1;
217 }
218
219 int optval = 1;
220 if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
221 sizeof(optval)) < 0) {
222 std::perror("Failed to set socket options");
223 close(server_fd);
224 return 1;
225 }
226
227 if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) <
228 0) {
229 std::perror("Failed to bind socket");
230 close(server_fd);
231 return 1;
232 }
233
234 if (listen(server_fd, BACKLOG) < 0) {
235 std::perror("Failed to listen on socket");
236 close(server_fd);
237 return 1;
238 }
239
240 std::printf("Serving HTTP on port %d (http://%s:%d/) ...\n", port,
241 bind_address.c_str(), port);
242
243 return condy::sync_wait(co_main(server_fd));
244}
Basic buffer types and conversion utilities.
Coroutine type used to define a coroutine function.
Definition coro.hpp:26
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.
Definition sync_wait.hpp:24
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.
Definition buffers.hpp:84
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.
Definition task.hpp:240
auto async_accept(Fd fd, struct sockaddr *addr, socklen_t *addrlen, int flags)
See io_uring_prep_accept.