319 lines
7.6 KiB
C
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, ¶ms, &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);
|
|
}
|