#include "../beaker.h" #include "beaker_globals.h" #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_PENDING_CONNECTIONS 128 static volatile sig_atomic_t g_shutdown_requested = 0; static void signal_handler(int sig) { (void)sig; g_shutdown_requested = 1; } typedef struct { int client_sockets[MAX_PENDING_CONNECTIONS]; int count; int front; int rear; pthread_mutex_t mutex; pthread_cond_t cond; int shutdown; } WorkQueue; static WorkQueue g_work_queue; static void work_queue_init(WorkQueue *queue) { queue->count = 0; queue->front = 0; queue->rear = 0; queue->shutdown = 0; pthread_mutex_init(&queue->mutex, NULL); pthread_cond_init(&queue->cond, NULL); } static void work_queue_destroy(WorkQueue *queue) { pthread_mutex_destroy(&queue->mutex); pthread_cond_destroy(&queue->cond); } static int work_queue_push(WorkQueue *queue, int client_socket) { pthread_mutex_lock(&queue->mutex); if (queue->count >= MAX_PENDING_CONNECTIONS) { pthread_mutex_unlock(&queue->mutex); return -1; } queue->client_sockets[queue->rear] = client_socket; queue->rear = (queue->rear + 1) % MAX_PENDING_CONNECTIONS; queue->count++; pthread_cond_signal(&queue->cond); pthread_mutex_unlock(&queue->mutex); return 0; } static int work_queue_pop(WorkQueue *queue) { pthread_mutex_lock(&queue->mutex); while (queue->count == 0 && !queue->shutdown) { pthread_cond_wait(&queue->cond, &queue->mutex); } if (queue->shutdown && queue->count == 0) { pthread_mutex_unlock(&queue->mutex); return -1; } int client_socket = queue->client_sockets[queue->front]; queue->front = (queue->front + 1) % MAX_PENDING_CONNECTIONS; queue->count--; pthread_mutex_unlock(&queue->mutex); return client_socket; } static int get_optimal_thread_count(void) { long cores = sysconf(_SC_NPROCESSORS_ONLN); if (cores < 1) cores = 1; return (int)(cores * 2); } void handle_client_connection(int new_socket); static void *worker_thread(void *arg) { (void)arg; while (1) { int client_socket = work_queue_pop(&g_work_queue); if (client_socket < 0) { break; } handle_client_connection(client_socket); } return NULL; } static int initialize_server_socket(const char *ip, int port, int *server_fd_out, struct sockaddr_in *address_out) { if ((*server_fd_out = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket failed"); fprintf(stderr, "[ERROR] initialize_server_socket: Failed to create socket.\n"); return -1; } int opt = 1; if (setsockopt(*server_fd_out, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt SO_REUSEADDR failed"); } //Needed for FreeBSD Support #ifdef SO_REUSEPORT if (setsockopt(*server_fd_out, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) { perror("setsockopt SO_REUSEPORT failed"); fprintf(stderr, "[ERROR] initialize_server_socket: Failed to set SO_REUSEPORT.\n"); close(*server_fd_out); return -1; } #endif address_out->sin_family = AF_INET; address_out->sin_addr.s_addr = inet_addr(ip); address_out->sin_port = htons(port); if (bind(*server_fd_out, (struct sockaddr *)address_out, sizeof(*address_out)) < 0) { perror("bind failed"); fprintf(stderr, "[ERROR] initialize_server_socket: Failed to bind socket to %s:%d.\n", ip, port); close(*server_fd_out); return -1; } if (listen(*server_fd_out, 10) < 0) { perror("listen failed"); fprintf(stderr, "[ERROR] initialize_server_socket: Failed to listen on socket.\n"); close(*server_fd_out); return -1; } printf("Beaker server listening on %s:%d\n", ip, port); return 0; } void handle_client_connection(int new_socket) { current_client_socket = new_socket; char buffer[BUFFER_SIZE] = {0}; ssize_t bytes_read = read(new_socket, buffer, BUFFER_SIZE - 1); if (bytes_read < 0) { perror("read failed"); fprintf(stderr, "[ERROR] handle_client_connection: Failed to read from client socket.\n"); close(new_socket); return; } buffer[bytes_read] = '\0'; strncpy(current_request_buffer, buffer, BUFFER_SIZE - 1); current_request_buffer[BUFFER_SIZE - 1] = '\0'; char request_line[MAX_PATH_LEN + 64]; char *first_line_end = strstr(buffer, "\r\n"); if (first_line_end == NULL) { fprintf(stderr, "[ERROR] handle_client_connection: Invalid HTTP request: No CRLF found.\n"); const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; send(new_socket, bad_request, strlen(bad_request), 0); close(new_socket); return; } size_t request_line_len = first_line_end - buffer; if (request_line_len >= sizeof(request_line)) { fprintf(stderr, "[ERROR] handle_client_connection: Request line too long.\n"); const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; send(new_socket, bad_request, strlen(bad_request), 0); close(new_socket); return; } strncpy(request_line, buffer, request_line_len); request_line[request_line_len] = '\0'; UrlParams request_params; char *requested_path = parse_request_url(request_line, &request_params); if (requested_path == NULL) { fprintf(stderr, "[ERROR] handle_client_connection: Could not parse request path. Sending 400 Bad Request.\n"); const char *bad_request = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"; send(new_socket, bad_request, strlen(bad_request), 0); close(new_socket); return; } printf("Accessing: %s\n", requested_path); bool handled = false; if (strncmp(requested_path, "/static/", strlen("/static/")) == 0) { if (serve_static_file(requested_path + strlen("/static/"))) { handled = true; } } if (!handled) { int best_match_handler_index = -1; size_t best_match_len = 0; for (int i = 0; i < handler_count; i++) { size_t handler_path_len = strlen(handlers[i].path); if (strncmp(requested_path, handlers[i].path, handler_path_len) == 0) { if (handler_path_len == strlen(requested_path) || requested_path[handler_path_len] == '/') { if (handler_path_len > best_match_len) { best_match_len = handler_path_len; best_match_handler_index = i; } } } } if (best_match_handler_index != -1) { handlers[best_match_handler_index].handler(&request_params); handled = true; } } if (!handled) { fprintf(stderr, "[WARNING] handle_client_connection: No handler or static file found for path '%s'. Sending 404 Not Found.\n", requested_path); const char *not_found_html = "

404 Not Found

The requested URL " "was not located on this server.

"; char not_found_response[BUFFER_SIZE]; snprintf(not_found_response, sizeof(not_found_response), "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Content-Length: %zu\r\n" "Connection: close\r\n" "\r\n%s", strlen(not_found_html), not_found_html); send(new_socket, not_found_response, strlen(not_found_response), 0); } free(requested_path); close(new_socket); current_client_socket = -1; } int beaker_run(const char *ip, int port) { beaker_run_with_threads(ip, port, 0); return 0; } void beaker_run_with_threads(const char *ip, int port, int num_workers) { int server_fd; struct sockaddr_in address; int addrlen = sizeof(address); g_shutdown_requested = 0; struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); if (num_workers <= 0) { num_workers = get_optimal_thread_count(); } if (initialize_server_socket(ip, port, &server_fd, &address) != 0) { return; } work_queue_init(&g_work_queue); pthread_t threads[num_workers]; for (int i = 0; i < num_workers; i++) { pthread_create(&threads[i], NULL, worker_thread, NULL); } printf("Beaker server started with %d worker threads\n", num_workers); while (!g_shutdown_requested) { int new_socket; if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) { if (g_shutdown_requested) { break; } perror("accept failed"); fprintf(stderr, "[ERROR] beaker_run_with_threads: Failed to accept connection.\n"); continue; } if (work_queue_push(&g_work_queue, new_socket) < 0) { fprintf(stderr, "[WARNING] Work queue full, closing connection\n"); const char *busy_response = "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"; send(new_socket, busy_response, strlen(busy_response), 0); close(new_socket); } } printf("Shutting down server...\n"); pthread_mutex_lock(&g_work_queue.mutex); g_work_queue.shutdown = 1; pthread_mutex_unlock(&g_work_queue.mutex); pthread_cond_broadcast(&g_work_queue.cond); for (int i = 0; i < num_workers; i++) { pthread_join(threads[i], NULL); } work_queue_destroy(&g_work_queue); close(server_fd); }