diff --git a/app.c b/app.c index 6627536..6972acd 100644 --- a/app.c +++ b/app.c @@ -18,6 +18,12 @@ #define DEFAULT_MAX_CONNS 10000 #define DEFAULT_MAX_EVENTS 4096 +// Global app instance for simplified macro API +app_t *_global_app = NULL; + +// Global route list for inline route definitions (type defined in app_macros.h) +void *_route_list = NULL; + // Default 404 handler static void default_not_found(context_t *ctx) { // send_text(ctx, 404, "404 Not Found"); @@ -70,8 +76,10 @@ static void route_request(conn_t *c) { // 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 + // Set route parameters on the request + if (count > 0 && params && names) { + request_set_params(ctx->request, names, params, count); + } ((app_handler_t)handler)(ctx); context_free(ctx); } diff --git a/context.c b/context.c index 864b698..b70e5fb 100644 --- a/context.c +++ b/context.c @@ -1,7 +1,12 @@ #include "context.h" +#include #include #include +#include +#include +#include +#include // context_new creates a new context for a connection. context_t* context_new(conn_t *conn) { @@ -143,3 +148,96 @@ int ctx_send(context_t *ctx) { return 0; } + +// ctx_file sends a file as response. +void ctx_file(context_t *ctx, const char *path) { + int fd = open(path, O_RDONLY); + if (fd < 0) { + ctx_error(ctx, 404, "File not found"); + return; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + close(fd); + ctx_error(ctx, 500, "Cannot stat file"); + return; + } + + // 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"; + else if (strcmp(ext, ".txt") == 0) content_type = "text/plain"; + else if (strcmp(ext, ".xml") == 0) content_type = "application/xml"; + } + + // For small files, read and send directly + if (st.st_size < 1024 * 1024) { // 1MB threshold + char *buf = malloc(st.st_size); + if (buf) { + if (read(fd, buf, st.st_size) == st.st_size) { + ctx_status(ctx, 200); + ctx_header_set(ctx, "Content-Type", content_type); + ctx_write(ctx, buf, st.st_size); + ctx_send(ctx); + } + free(buf); + } + } else { + // File too large - send error for now + ctx_error(ctx, 413, "File too large"); + } + + close(fd); +} + +// ctx_write writes data to response body. +void ctx_write(context_t *ctx, const char *data, size_t len) { + if (!ctx->response) return; + + // If no body yet, allocate + if (!ctx->response->body) { + ctx->response->body = malloc(len); + if (ctx->response->body) { + memcpy(ctx->response->body, data, len); + ctx->response->body_len = len; + } + } else { + // Append to existing body + char *new_body = realloc(ctx->response->body, ctx->response->body_len + len); + if (new_body) { + memcpy(new_body + ctx->response->body_len, data, len); + ctx->response->body = new_body; + ctx->response->body_len += len; + } + } +} + +// ctx_printf writes formatted data to response body. +void ctx_printf(context_t *ctx, const char *fmt, ...) { + char buf[4096]; + va_list args; + va_start(args, fmt); + int len = vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + if (len > 0 && len < (int)sizeof(buf)) { + ctx_write(ctx, buf, len); + } +} + +// ctx_send_chunk sends a chunk of data (for streaming). +int ctx_send_chunk(context_t *ctx, const char *data, size_t len) { + // For now, just accumulate and send + ctx_write(ctx, data, len); + return 0; +} diff --git a/context.h b/context.h index accb17d..e6f43c5 100644 --- a/context.h +++ b/context.h @@ -49,10 +49,16 @@ void ctx_header_set(context_t *ctx, const char *name, const char *value); void ctx_text(context_t *ctx, int status, const char *text); void ctx_html(context_t *ctx, int status, const char *html); void ctx_json(context_t *ctx, int status, const char *json); +void ctx_file(context_t *ctx, const char *path); void ctx_redirect(context_t *ctx, const char *url); void ctx_error(context_t *ctx, int status, const char *message); +// Response building +void ctx_write(context_t *ctx, const char *data, size_t len); +void ctx_printf(context_t *ctx, const char *fmt, ...); + // Send response to client int ctx_send(context_t *ctx); +int ctx_send_chunk(context_t *ctx, const char *data, size_t len); #endif // CONTEXT_H diff --git a/macros.h b/macros.h new file mode 100644 index 0000000..99190b3 --- /dev/null +++ b/macros.h @@ -0,0 +1,179 @@ +#ifndef MACROS_H +#define MACROS_H + +#include "app.h" +#include "context.h" +#include +#include +#include + +// Global app instance for simplified API +extern app_t *_global_app; + +// Initialize the simplified API +#define APP_INIT() do { \ + _global_app = app_new(); \ + if (!_global_app) { \ + fprintf(stderr, "Failed to create app\n"); \ + exit(1); \ + } \ +} while(0) + +// Simple route registration without app parameter +#define GET(path, handler) app_get(_global_app, path, handler) +#define POST(path, handler) app_post(_global_app, path, handler) +#define PUT(path, handler) app_put(_global_app, path, handler) +#define DELETE(path, handler) app_delete(_global_app, path, handler) + +// Configuration +#define WORKERS(n) app_set_workers(_global_app, n) +#define PORT(p) app_listen(_global_app, p) +#define RUN() app_run(_global_app) +#define SERVE(port_num) do { \ + PORT(port_num); \ + printf("🚀 Server running on http://localhost:%d\n", port_num); \ + RUN(); \ +} while(0) + +// Route parameter syntax helpers - make parameters explicit +// Usage: GET(app, ROUTE_PATH("/users/" ROUTE_PARAM("id") "/posts/" ROUTE_PARAM("post_id")), handler) +#define ROUTE_PARAM(name) ":" name +#define ROUTE_PATH(...) __VA_ARGS__ +#define ROUTE_WILDCARD "*" + +// Alternative clearer syntax for routes with parameters +#define GET_PARAM(app, base_path, param_name, handler) \ + GET(app, base_path ":" param_name, handler) + +#define GET_PARAMS(app, base_path, param1, param2, handler) \ + GET(app, base_path ":" param1 "/" ":" param2, handler) + +// Builder-style route definitions for maximum clarity +#define ROUTE_WITH_PARAMS(method, app, ...) method(app, __VA_ARGS__) +#define STATIC_PATH(path) path +#define PARAM_PATH(name) ":" name +#define ANY_PATH "*" + +// Examples: +// ROUTE_WITH_PARAMS(GET, app, STATIC_PATH("/users/"), PARAM_PATH("id"), handler) +// ROUTE_WITH_PARAMS(GET, app, STATIC_PATH("/api/"), PARAM_PATH("version"), STATIC_PATH("/users"), handler) + +// Documentation macros for route definitions +#define ROUTE_DOC(description) /* description */ +#define PARAMS(...) /* Parameters: __VA_ARGS__ */ +#define RETURNS(type) /* Returns: type */ + +// Handler definition macros +#define HANDLER(name) void name(context_t *ctx) +#define ASYNC_HANDLER(name) void name(context_t *ctx, void (*done)(context_t*)) + +// Response macros - simplified (ctx is implicit in handlers) +#define OK(text) ctx_text(ctx, 200, text) +#define CREATED(text) ctx_text(ctx, 201, text) +#define JSON(json) ctx_json(ctx, 200, json) +#define HTML(html) ctx_html(ctx, 200, html) +#define ERROR(code, msg) ctx_error(ctx, code, msg) +#define FILE(path) ctx_file(ctx, path) + +// Error responses +#define BAD_REQUEST(msg) ctx_error(ctx, 400, msg) +#define UNAUTHORIZED(msg) ctx_error(ctx, 401, msg) +#define FORBIDDEN(msg) ctx_error(ctx, 403, msg) +#define NOT_FOUND(msg) ctx_error(ctx, 404, msg) +#define SERVER_ERROR(msg) ctx_error(ctx, 500, msg) + +// JSON response macros +#define JSON_OK(ctx, ...) ctx_json(ctx, 200, __VA_ARGS__) +#define JSON_CREATED(ctx, ...) ctx_json(ctx, 201, __VA_ARGS__) +#define JSON_ERROR(ctx, code, msg) ctx_json(ctx, code, "{\"error\":\"" msg "\"}") + +// HTML response macros +#define HTML_OK(ctx, ...) ctx_html(ctx, 200, __VA_ARGS__) +#define HTML_PAGE(ctx, title, body) \ + ctx_html(ctx, 200, \ + "" title "" \ + "" body "") + +// Redirect macros +#define REDIRECT(ctx, url) ctx_redirect(ctx, url) +#define REDIRECT_PERMANENT(ctx, url) do { \ + ctx_status(ctx, 301); \ + ctx_header_set(ctx, "Location", url); \ + ctx_send(ctx); \ +} while(0) + +// Request accessor macros - simplified (ctx is implicit in handlers) +#define PARAM(name) ctx_param(ctx, name) +#define QUERY(name) ctx_query(ctx, name) +#define HEADER(name) ctx_header(ctx, name) +#define COOKIE(name) ctx_cookie(ctx, name) +#define BODY() ctx_body(ctx) +#define METHOD() ((ctx)->request ? (ctx)->request->method : NULL) +#define PATH() ((ctx)->request ? (ctx)->request->path : NULL) + +// Middleware-style macros +#define NEXT(ctx, next) next(ctx) +#define ABORT(ctx, code, msg) do { \ + ctx_error(ctx, code, msg); \ + return; \ +} while(0) + +// Common patterns - simplified (ctx is implicit) +#define REQUIRE_AUTH() do { \ + if (!HEADER("Authorization")) { \ + UNAUTHORIZED("Authentication required"); \ + return; \ + } \ +} while(0) + +#define REQUIRE_JSON() do { \ + const char *ct = HEADER("Content-Type"); \ + if (!ct || strstr(ct, "application/json") == NULL) { \ + BAD_REQUEST("Content-Type must be application/json"); \ + return; \ + } \ +} while(0) + +#define CORS() do { \ + ctx_header_set(ctx, "Access-Control-Allow-Origin", "*"); \ + ctx_header_set(ctx, "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); \ + ctx_header_set(ctx, "Access-Control-Allow-Headers", "Content-Type, Authorization"); \ +} while(0) + +// Utility macros for building responses +#define JSON_BEGIN(buf, size) char buf[size]; int _json_pos = 0; _json_pos += snprintf(buf, size, "{") +#define JSON_FIELD(buf, size, key, fmt, val) \ + _json_pos += snprintf(buf + _json_pos, size - _json_pos, \ + "%s\"%s\":" fmt, (_json_pos > 1 ? "," : ""), key, val) +#define JSON_STRING(buf, size, key, val) JSON_FIELD(buf, size, key, "\"%s\"", val) +#define JSON_NUMBER(buf, size, key, val) JSON_FIELD(buf, size, key, "%d", val) +#define JSON_BOOL(buf, size, key, val) JSON_FIELD(buf, size, key, "%s", (val) ? "true" : "false") +#define JSON_END(buf, size) snprintf(buf + _json_pos, size - _json_pos, "}") + +// App configuration macros +#define APP_CONFIG(app, ...) do { \ + struct { \ + int workers; \ + int max_conns; \ + size_t read_buf; \ + size_t write_buf; \ + int cpu_affinity; \ + } config = {0, 10000, 8192, 8192, 1, ##__VA_ARGS__}; \ + if (config.workers > 0) app_set_workers(app, config.workers); \ + app_set_max_conns(app, config.max_conns); \ + app_set_buffer_sizes(app, config.read_buf, config.write_buf); \ + app_set_cpu_affinity(app, config.cpu_affinity); \ +} while(0) + +// Quick app setup macro +#define QUICK_APP(port, ...) ({ \ + app_t *_app = app_new(); \ + if (_app) { \ + APP_CONFIG(_app, ##__VA_ARGS__); \ + __VA_ARGS__; \ + app_listen(_app, port); \ + } \ + _app; \ +}) + +#endif // MACROS_H