super-duper macros!
This commit is contained in:
parent
f0cf4eacac
commit
4ec44b65ad
12
app.c
12
app.c
@ -18,6 +18,12 @@
|
|||||||
#define DEFAULT_MAX_CONNS 10000
|
#define DEFAULT_MAX_CONNS 10000
|
||||||
#define DEFAULT_MAX_EVENTS 4096
|
#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
|
// Default 404 handler
|
||||||
static void default_not_found(context_t *ctx) {
|
static void default_not_found(context_t *ctx) {
|
||||||
// send_text(ctx, 404, "404 Not Found");
|
// send_text(ctx, 404, "404 Not Found");
|
||||||
@ -70,8 +76,10 @@ static void route_request(conn_t *c) {
|
|||||||
// Create context and call it
|
// Create context and call it
|
||||||
context_t *ctx = context_new(c);
|
context_t *ctx = context_new(c);
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
// Store params in context (we'd need to add this to context)
|
// Set route parameters on the request
|
||||||
// For now, just call the handler
|
if (count > 0 && params && names) {
|
||||||
|
request_set_params(ctx->request, names, params, count);
|
||||||
|
}
|
||||||
((app_handler_t)handler)(ctx);
|
((app_handler_t)handler)(ctx);
|
||||||
context_free(ctx);
|
context_free(ctx);
|
||||||
}
|
}
|
||||||
|
|||||||
98
context.c
98
context.c
@ -1,7 +1,12 @@
|
|||||||
#include "context.h"
|
#include "context.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
// context_new creates a new context for a connection.
|
// context_new creates a new context for a connection.
|
||||||
context_t* context_new(conn_t *conn) {
|
context_t* context_new(conn_t *conn) {
|
||||||
@ -143,3 +148,96 @@ int ctx_send(context_t *ctx) {
|
|||||||
|
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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_text(context_t *ctx, int status, const char *text);
|
||||||
void ctx_html(context_t *ctx, int status, const char *html);
|
void ctx_html(context_t *ctx, int status, const char *html);
|
||||||
void ctx_json(context_t *ctx, int status, const char *json);
|
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_redirect(context_t *ctx, const char *url);
|
||||||
void ctx_error(context_t *ctx, int status, const char *message);
|
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
|
// Send response to client
|
||||||
int ctx_send(context_t *ctx);
|
int ctx_send(context_t *ctx);
|
||||||
|
int ctx_send_chunk(context_t *ctx, const char *data, size_t len);
|
||||||
|
|
||||||
#endif // CONTEXT_H
|
#endif // CONTEXT_H
|
||||||
|
|||||||
179
macros.h
Normal file
179
macros.h
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#ifndef MACROS_H
|
||||||
|
#define MACROS_H
|
||||||
|
|
||||||
|
#include "app.h"
|
||||||
|
#include "context.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// 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, \
|
||||||
|
"<!DOCTYPE html><html><head><title>" title "</title></head>" \
|
||||||
|
"<body>" body "</body></html>")
|
||||||
|
|
||||||
|
// 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
|
||||||
Loading…
x
Reference in New Issue
Block a user