Web/app.c
2025-09-15 12:57:54 -05:00

319 lines
7.6 KiB
C

#include "app.h"
#include "reactor/pool.h"
#include "reactor/epoll.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#define DEFAULT_MAX_CONNS 10000
#define DEFAULT_MAX_EVENTS 4096
// Default 404 handler
static void default_not_found(context_t *ctx) {
// send_text(ctx, 404, "404 Not Found");
}
// Accept handler for new connections
static void accept_handler(runtime_t *rt, int fd, uint32_t events, void *data) {
app_t *a = (app_t*)data;
// Accept multiple connections in a loop for edge-triggered mode
// Limit to prevent starvation of other events
int accepted = 0;
const int max_accept = 64;
while (accepted < max_accept) {
int ret = conn_accept(a->pool, fd);
if (ret < 0) break;
accepted++;
}
}
// Global app pointer (temporary solution)
static app_t *g_app = NULL;
// Wrapper to convert between handler types
static void handler_wrapper(conn_t *c) {
app_handler_t app_handler = (app_handler_t)c->user_data;
if (app_handler) {
context_t *ctx = context_new(c);
if (ctx) {
app_handler(ctx);
context_free(ctx);
}
}
}
// Request router
static void route_request(conn_t *c) {
app_t *a = g_app; // Use global for now
if (!a || !a->router) return;
// Use the router to find handler
char **params = NULL;
char **names = NULL;
int count = 0;
handler_t handler = router_lookup(a->router, c->method, c->path, &params, &names, &count);
if (handler) {
// The handler is actually an app_handler_t cast to handler_t
// Create context and call it
context_t *ctx = context_new(c);
if (ctx) {
// Store params in context (we'd need to add this to context)
// For now, just call the handler
((app_handler_t)handler)(ctx);
context_free(ctx);
}
} else {
// Create a context and call not_found
context_t *ctx = context_new(c);
if (ctx) {
a->not_found(ctx);
context_free(ctx);
}
}
}
// app_new creates a new web application.
app_t* app_new(void) {
app_t *a = calloc(1, sizeof(app_t));
if (!a) return NULL;
// Default configuration
a->max_conns = DEFAULT_MAX_CONNS;
a->read_buf_size = 8192;
a->write_buf_size = 8192;
a->arena_size = 4096;
a->not_found = default_not_found;
// Create router
a->router = router_new();
if (!a->router) {
free(a);
return NULL;
}
// Store global reference (temporary solution)
g_app = a;
return a;
}
// app_free destroys the application.
void app_free(app_t *a) {
if (!a) return;
// Free router
if (a->router) {
router_free(a->router);
}
// Close listener
if (a->listen_fd > 0) {
runtime_del(a->rt, a->listen_fd);
close(a->listen_fd);
}
// Free pool and runtime
pool_free(a->pool);
runtime_free(a->rt);
free(a);
}
// app_listen sets up the listening socket.
int app_listen(app_t *a, int port) {
// Create runtime and pool if not already done
if (!a->rt) {
a->rt = runtime_new(DEFAULT_MAX_EVENTS);
if (!a->rt) return -1;
}
if (!a->pool) {
a->pool = pool_new(a->rt, a->max_conns);
if (!a->pool) return -1;
// Configure pool
a->pool->read_buf_size = a->read_buf_size;
a->pool->write_buf_size = a->write_buf_size;
a->pool->arena_size = a->arena_size;
// Set router as request handler
a->pool->on_request = route_request;
}
// Create socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return -1;
// Allow reuse
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// Enable SO_REUSEPORT for better load distribution across CPU cores
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
// Disable Nagle's algorithm on listener
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
// Bind
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr.s_addr = INADDR_ANY
};
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(fd);
return -1;
}
// Listen with larger backlog for better performance
if (listen(fd, 4096) < 0) {
close(fd);
return -1;
}
// Make non-blocking
set_nonblocking(fd);
// Add to epoll
runtime_add(a->rt, fd, EPOLLIN | EPOLLET, accept_handler, a);
a->listen_fd = fd;
a->port = port;
printf("Server listening on :%d\n", port);
return 0;
}
// app_run starts the event loop.
int app_run(app_t *a) {
if (!a->rt || a->listen_fd <= 0) {
return -1;
}
// Override pool callback with router
a->pool->on_request = route_request;
return runtime_run(a->rt);
}
// app_route registers a route.
void app_route(app_t *a, const char *method, const char *path, app_handler_t handler) {
if (!a || !a->router) return;
// For now, we need to bridge between app_handler_t and handler_t
// The router expects handler_t (conn_t*) but we have app_handler_t (context_t*)
// We'll need to wrap it somehow - for now just cast (not ideal)
router_add(a->router, method, path, (handler_t)handler);
}
// Convenience route methods
void app_get(app_t *a, const char *path, app_handler_t handler) {
app_route(a, "GET", path, handler);
}
void app_post(app_t *a, const char *path, app_handler_t handler) {
app_route(a, "POST", path, handler);
}
void app_put(app_t *a, const char *path, app_handler_t handler) {
app_route(a, "PUT", path, handler);
}
void app_delete(app_t *a, const char *path, app_handler_t handler) {
app_route(a, "DELETE", path, handler);
}
// Configuration setters
void app_set_max_conns(app_t *a, int max) {
a->max_conns = max;
}
void app_set_buffer_sizes(app_t *a, size_t read_size, size_t write_size) {
a->read_buf_size = read_size;
a->write_buf_size = write_size;
}
void app_set_arena_size(app_t *a, size_t size) {
a->arena_size = size;
}
void app_set_not_found(app_t *a, app_handler_t handler) {
a->not_found = handler;
}
// Helper response functions
static void send_text(conn_t *c, int status, const char *text) {
conn_write_status(c, status);
conn_write_header(c, "Content-Type", "text/plain");
conn_write_body(c, text, strlen(text));
}
void send_json(conn_t *c, int status, const char *json) {
conn_write_status(c, status);
conn_write_header(c, "Content-Type", "application/json");
conn_write_body(c, json, strlen(json));
}
void send_html(conn_t *c, int status, const char *html) {
conn_write_status(c, status);
conn_write_header(c, "Content-Type", "text/html");
conn_write_body(c, html, strlen(html));
}
void send_file(conn_t *c, const char *path) {
// Simple file serving (no caching, no range support)
FILE *f = fopen(path, "rb");
if (!f) {
send_text(c, 404, "File not found");
return;
}
// Get file size
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
// Determine content type from extension
const char *content_type = "application/octet-stream";
const char *ext = strrchr(path, '.');
if (ext) {
if (strcmp(ext, ".html") == 0) content_type = "text/html";
else if (strcmp(ext, ".css") == 0) content_type = "text/css";
else if (strcmp(ext, ".js") == 0) content_type = "application/javascript";
else if (strcmp(ext, ".json") == 0) content_type = "application/json";
else if (strcmp(ext, ".png") == 0) content_type = "image/png";
else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) content_type = "image/jpeg";
else if (strcmp(ext, ".gif") == 0) content_type = "image/gif";
else if (strcmp(ext, ".svg") == 0) content_type = "image/svg+xml";
}
// Send headers
conn_write_status(c, 200);
conn_write_header(c, "Content-Type", content_type);
// For small files, read and send directly
if (size < c->wsize - c->wlen - 100) {
char *buf = malloc(size);
if (buf) {
fread(buf, 1, size, f);
conn_write_body(c, buf, size);
free(buf);
}
} else {
// File too large for buffer
conn_write_status(c, 500);
conn_end_response(c);
}
fclose(f);
}