ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
http_server.cpp
Go to the documentation of this file.
1
6#include "http_server.h"
7
8#include <algorithm>
9#include <cctype>
10#include <cstring>
11#include <sstream>
12
13// Platform-specific includes
14#if defined(_WIN32)
15#define WIN32_LEAN_AND_MEAN
16#include <winsock2.h>
17#include <ws2tcpip.h>
18#pragma comment(lib, "ws2_32.lib")
19using socket_t = SOCKET;
20using ssize_t = ptrdiff_t; // Windows doesn't define ssize_t
21#define INVALID_SOCKET_VAL INVALID_SOCKET
22#define SOCKET_ERROR_VAL SOCKET_ERROR
23#define close_socket closesocket
24#else
25#include <arpa/inet.h>
26#include <netinet/in.h>
27#include <sys/socket.h>
28#include <unistd.h>
29using socket_t = int;
30#define INVALID_SOCKET_VAL (-1)
31#define SOCKET_ERROR_VAL (-1)
32#define close_socket close
33#endif
34
35namespace ascii_query {
36
37// =============================================================================
38// HttpRequest
39// =============================================================================
40
41std::string HttpRequest::header(const std::string &name, const std::string &default_value) const {
42 // Case-insensitive header lookup
43 std::string lower_name = name;
44 std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(),
45 [](unsigned char c) { return std::tolower(c); });
46
47 for (const auto &[key, value] : headers) {
48 std::string lower_key = key;
49 std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(),
50 [](unsigned char c) { return std::tolower(c); });
51 if (lower_key == lower_name) {
52 return value;
53 }
54 }
55 return default_value;
56}
57
58// =============================================================================
59// HttpResponse
60// =============================================================================
61
62std::string HttpResponse::serialize() const {
63 std::ostringstream oss;
64
65 // Status line
66 oss << "HTTP/1.1 " << status_code << " " << status_text << "\r\n";
67
68 // Headers
69 bool has_content_length = false;
70 bool has_connection = false;
71
72 for (const auto &[key, value] : headers) {
73 oss << key << ": " << value << "\r\n";
74 std::string lower_key = key;
75 std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(),
76 [](unsigned char c) { return std::tolower(c); });
77 if (lower_key == "content-length")
78 has_content_length = true;
79 if (lower_key == "connection")
80 has_connection = true;
81 }
82
83 // Add Content-Length if not present
84 if (!has_content_length) {
85 oss << "Content-Length: " << body.size() << "\r\n";
86 }
87
88 // Add Connection: close if not present
89 if (!has_connection) {
90 oss << "Connection: close\r\n";
91 }
92
93 // CORS headers for browser access
94 oss << "Access-Control-Allow-Origin: *\r\n";
95 oss << "Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS\r\n";
96 oss << "Access-Control-Allow-Headers: Content-Type\r\n";
97
98 // End headers
99 oss << "\r\n";
100
101 // Body
102 oss << body;
103
104 return oss.str();
105}
106
107// =============================================================================
108// HttpServer
109// =============================================================================
110
112#if defined(_WIN32)
113 // Initialize Winsock
114 WSADATA wsa_data;
115 WSAStartup(MAKEWORD(2, 2), &wsa_data);
116#endif
117
118 // Default 404 handler
119 default_handler_ = [](const HttpRequest &) { return HttpResponse::notFound(); };
120}
121
123 stop();
124
125#if defined(_WIN32)
126 WSACleanup();
127#endif
128}
129
130void HttpServer::addRoute(const std::string &method, const std::string &path, RouteHandler handler) {
131 routes_.push_back({method, path, std::move(handler)});
132}
133
134void HttpServer::setDefaultHandler(RouteHandler handler) { default_handler_ = std::move(handler); }
135
136bool HttpServer::start(uint16_t port) {
137 if (running_) {
138 last_error_ = "Server already running";
139 return false;
140 }
141
142 // Create socket
143 server_socket_ = static_cast<int>(socket(AF_INET, SOCK_STREAM, 0));
144 if (server_socket_ == INVALID_SOCKET_VAL) {
145 last_error_ = "Failed to create socket";
146 return false;
147 }
148
149 // Set SO_REUSEADDR
150 int opt = 1;
151#if defined(_WIN32)
152 setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&opt), sizeof(opt));
153#else
154 setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
155#endif
156
157 // Bind
158 struct sockaddr_in addr {};
159 addr.sin_family = AF_INET;
160 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // localhost only for security
161 addr.sin_port = htons(port);
162
163 if (bind(server_socket_, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0) {
164 last_error_ = "Failed to bind to port " + std::to_string(port);
165 close_socket(server_socket_);
166 server_socket_ = -1;
167 return false;
168 }
169
170 // Listen
171 if (listen(server_socket_, 10) < 0) {
172 last_error_ = "Failed to listen on socket";
173 close_socket(server_socket_);
174 server_socket_ = -1;
175 return false;
176 }
177
178 port_ = port;
179 running_ = true;
180
181 // Start server thread
182 server_thread_ = std::thread(&HttpServer::serverLoop, this);
183
184 return true;
185}
186
188 if (!running_) {
189 return;
190 }
191
192 running_ = false;
193
194 // Close server socket to unblock accept()
195 if (server_socket_ >= 0) {
196#if defined(_WIN32)
197 shutdown(server_socket_, SD_BOTH);
198#else
199 shutdown(server_socket_, SHUT_RDWR);
200#endif
201 close_socket(server_socket_);
202 server_socket_ = -1;
203 }
204
205 // Wait for server thread
206 if (server_thread_.joinable()) {
207 server_thread_.join();
208 }
209}
210
211void HttpServer::serverLoop() {
212 while (running_) {
213 struct sockaddr_in client_addr {};
214 socklen_t client_len = sizeof(client_addr);
215
216 socket_t client_socket = accept(server_socket_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);
217
218 if (client_socket == INVALID_SOCKET_VAL) {
219 if (running_) {
220 // Real error, not just shutdown
221 // Continue anyway
222 }
223 continue;
224 }
225
226 handleClient(static_cast<int>(client_socket));
227 }
228}
229
231 if (server_socket_ < 0) {
232 return false;
233 }
234
235 struct sockaddr_in client_addr {};
236 socklen_t client_len = sizeof(client_addr);
237
238 socket_t client_socket = accept(server_socket_, reinterpret_cast<struct sockaddr *>(&client_addr), &client_len);
239
240 if (client_socket == INVALID_SOCKET_VAL) {
241 return false;
242 }
243
244 handleClient(static_cast<int>(client_socket));
245 return true;
246}
247
248void HttpServer::handleClient(int client_socket) {
249 // Read request (up to 64KB)
250 char buffer[65536];
251 ssize_t bytes_read;
252
253#if defined(_WIN32)
254 bytes_read = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
255#else
256 bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
257#endif
258
259 if (bytes_read <= 0) {
260 close_socket(client_socket);
261 return;
262 }
263 buffer[bytes_read] = '\0';
264
265 // Parse request
266 HttpRequest request;
267 if (!parseRequest(buffer, request)) {
268 HttpResponse response = HttpResponse::badRequest("Invalid HTTP request");
269 std::string raw = response.serialize();
270 send(client_socket, raw.c_str(), static_cast<int>(raw.size()), 0);
271 close_socket(client_socket);
272 return;
273 }
274
275 // Handle CORS preflight
276 if (request.method == "OPTIONS") {
277 HttpResponse response = HttpResponse::noContent();
278 std::string raw = response.serialize();
279 send(client_socket, raw.c_str(), static_cast<int>(raw.size()), 0);
280 close_socket(client_socket);
281 return;
282 }
283
284 // Find handler
285 RouteHandler handler = findHandler(request.method, request.path);
286
287 // Call handler
288 HttpResponse response;
289 try {
290 response = handler(request);
291 } catch (const std::exception &e) {
292 response = HttpResponse::serverError(e.what());
293 } catch (...) {
294 response = HttpResponse::serverError("Unknown error");
295 }
296
297 // Send response
298 std::string raw = response.serialize();
299 send(client_socket, raw.c_str(), static_cast<int>(raw.size()), 0);
300
301 close_socket(client_socket);
302}
303
304bool HttpServer::parseRequest(const std::string &raw, HttpRequest &request) {
305 // Find end of request line
306 size_t line_end = raw.find("\r\n");
307 if (line_end == std::string::npos) {
308 return false;
309 }
310
311 // Parse request line: METHOD PATH HTTP/1.x
312 std::string request_line = raw.substr(0, line_end);
313 std::istringstream iss(request_line);
314 std::string http_version;
315 std::string full_path;
316
317 if (!(iss >> request.method >> full_path >> http_version)) {
318 return false;
319 }
320
321 // Split path and query string
322 size_t query_pos = full_path.find('?');
323 if (query_pos != std::string::npos) {
324 request.path = full_path.substr(0, query_pos);
325 request.query_string = full_path.substr(query_pos + 1);
326 parseQueryString(request.query_string, request.params);
327 } else {
328 request.path = full_path;
329 }
330
331 // Parse headers
332 size_t pos = line_end + 2;
333 while (pos < raw.size()) {
334 size_t next_line = raw.find("\r\n", pos);
335 if (next_line == std::string::npos) {
336 break;
337 }
338
339 std::string header_line = raw.substr(pos, next_line - pos);
340 if (header_line.empty()) {
341 // Empty line = end of headers
342 pos = next_line + 2;
343 break;
344 }
345
346 size_t colon = header_line.find(':');
347 if (colon != std::string::npos) {
348 std::string name = header_line.substr(0, colon);
349 std::string value = header_line.substr(colon + 1);
350
351 // Trim leading whitespace from value
352 size_t value_start = value.find_first_not_of(" \t");
353 if (value_start != std::string::npos) {
354 value = value.substr(value_start);
355 }
356
357 request.headers[name] = value;
358 }
359
360 pos = next_line + 2;
361 }
362
363 // Body is everything after headers
364 if (pos < raw.size()) {
365 request.body = raw.substr(pos);
366 }
367
368 return true;
369}
370
371void HttpServer::parseQueryString(const std::string &query, std::unordered_map<std::string, std::string> &params) {
372 size_t pos = 0;
373 while (pos < query.size()) {
374 // Find next parameter
375 size_t amp = query.find('&', pos);
376 if (amp == std::string::npos) {
377 amp = query.size();
378 }
379
380 std::string param = query.substr(pos, amp - pos);
381
382 // Split key=value
383 size_t eq = param.find('=');
384 if (eq != std::string::npos) {
385 std::string key = urlDecode(param.substr(0, eq));
386 std::string value = urlDecode(param.substr(eq + 1));
387 params[key] = value;
388 } else {
389 // Flag parameter (no value)
390 std::string key = urlDecode(param);
391 if (!key.empty()) {
392 params[key] = "";
393 }
394 }
395
396 pos = amp + 1;
397 }
398}
399
400std::string HttpServer::urlDecode(const std::string &str) {
401 std::string result;
402 result.reserve(str.size());
403
404 for (size_t i = 0; i < str.size(); i++) {
405 if (str[i] == '%' && i + 2 < str.size()) {
406 int val = 0;
407 char hex[3] = {str[i + 1], str[i + 2], '\0'};
408 if (sscanf(hex, "%2x", &val) == 1) {
409 result += static_cast<char>(val);
410 i += 2;
411 } else {
412 result += str[i];
413 }
414 } else if (str[i] == '+') {
415 result += ' ';
416 } else {
417 result += str[i];
418 }
419 }
420
421 return result;
422}
423
424RouteHandler HttpServer::findHandler(const std::string &method, const std::string &path) {
425 for (const auto &route : routes_) {
426 if (route.method == method && route.path == path) {
427 return route.handler;
428 }
429 }
430 return default_handler_;
431}
432
433} // namespace ascii_query
uint16_t port() const
Get the port the server is listening on.
void addRoute(const std::string &method, const std::string &path, RouteHandler handler)
Add a route handler.
void setDefaultHandler(RouteHandler handler)
Set a default handler for unmatched routes.
bool processOneRequest()
Process one request (blocking)
bool start(uint16_t port)
Start the server.
void stop()
Stop the server.
#define INVALID_SOCKET_VAL
int socket_t
#define close_socket
Minimal single-threaded HTTP/1.1 server.
std::function< HttpResponse(const HttpRequest &)> RouteHandler
Route handler function type.
Parsed HTTP request.
Definition http_server.h:37
std::unordered_map< std::string, std::string > headers
Request headers.
Definition http_server.h:41
std::string header(const std::string &name, const std::string &default_value="") const
Get a header value.
std::unordered_map< std::string, std::string > headers
Definition http_server.h:97
std::string serialize() const
Serialize response to HTTP/1.1 format.
static HttpResponse noContent()
static HttpResponse notFound(const std::string &message="Not Found")
static HttpResponse serverError(const std::string &message="Internal Server Error")
static HttpResponse badRequest(const std::string &message="Bad Request")