diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b8ab9ee..0bf800c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,7 +1,8 @@ name: Publish Docker Images on: push: - branches: ['main'] + tags: + - '*' jobs: push-image: diff --git a/README.md b/README.md index af62247..b31ec5b 100644 --- a/README.md +++ b/README.md @@ -50,26 +50,26 @@ make && sudo make install ### Getting started #### Hello world application ```c -#include +#include #include #include // this function handles GET request that go to '/' void hello_world(req_t* req, res_t* res){ // just send the string 'hello world!' - res_send(res, "hello world!"); + RES_SEND("hello world!"); } // main function (entrypoint) int main(){ // init the application - app_init(); + app_t *app = app_new(); // setup a GET route for '/' GET("/", hello_world); // start the application on port 8080 - app_run("127.0.0.1:8080"); + APP_RUN("127.0.0.1:8080"); } ``` diff --git a/benchmark/ctorm/main.c b/benchmark/ctorm/main.c index 059598a..396da9e 100644 --- a/benchmark/ctorm/main.c +++ b/benchmark/ctorm/main.c @@ -1,14 +1,15 @@ -#include +#include #include #include void hello_world(req_t* req, res_t* res){ - res_send(res, "hello world!"); + RES_SEND("hello world!"); } int main(){ log_set(false); - app_init(); + app_t *app = app_new(); + GET("/", hello_world); - app_run("127.0.0.1:8080"); + APP_RUN("127.0.0.1:8080"); } diff --git a/docs/app.md b/docs/app.md index 9fbc686..ba55d50 100644 --- a/docs/app.md +++ b/docs/app.md @@ -24,12 +24,18 @@ GETR("123$", 123_route) // handle routes that end with 123 ```c // static files be served at '/static' path, // from the './files' direcotry -app_static("/static", "./files"); +app_static(app, "/static", "./files"); + +// if your app_t object is called 'app', then +// you can use the macro +APP_STATIC("/static", "./files"); ``` ### Setup 404 (all) route By default, routes that does not match with any other will be redirected to a 404 page, you set a custom route for this: ```c -app_all(all_route); +app_all(app, all_route); +// or you can use the macro +APP_ALL(all_route); ``` diff --git a/docs/log.md b/docs/log.md index e566d1a..772676a 100644 --- a/docs/log.md +++ b/docs/log.md @@ -4,7 +4,7 @@ general logging usage. ### General logging functions ```c -info("lalala some information"); +info("some information"); warn("there may be something wrong"); error("PANIC!!"); ``` diff --git a/docs/render.md b/docs/render.md index 6f87f52..0f96492 100644 --- a/docs/render.md +++ b/docs/render.md @@ -8,5 +8,11 @@ Ctorm supports simple and limited dynamic rendering. To render this template, you can use the request render method: ```c req_render_add(req, "msg", "hello world!"); +// or if the req_t object is called 'req', then you +// can use the macro +REQ_RENDER_ADD("msg", "hello world!"); + req_render(req, "templates/example.html"); +// again, you can also use the macro +REQ_RENDER("templates/example.html"); ``` diff --git a/docs/req.md b/docs/req.md index 605a8bd..94aebbf 100644 --- a/docs/req.md +++ b/docs/req.md @@ -45,15 +45,25 @@ char* method = req_method(req); ### Get a header of a request ```c char* agent = req_header(req, "User-Agent"); -if(NULL == agent) +if(NULL == agent){ // user-agent header is not set + return; +} + +// or you can use the macro +char* agent = REQ_HEADER("User-Agent"); ``` ### Get request URL query/parameter ```c char* msg = req_query(req, "msg"); -if(NULL == msg) - // msg parameter is specified +if(NULL == msg){ + // msg parameter is specified + return; +} + +// or you can use the macro +char* agent = REQ_QUERY("msg"); ``` ### Parse URL encoded form data diff --git a/docs/res.md b/docs/res.md index fa1c49b..01891c0 100644 --- a/docs/res.md +++ b/docs/res.md @@ -13,15 +13,24 @@ res->code = 403; // you can use local data, it will be copied to heap // when you call res_send() res_send(res, "hello world!"); + +// or you can use the macro +RES_SEND("hello world!"); ``` ### Sending a file ```c res_sendfile(res, "files/index.html"); + +// or you can use the macro +RES_SENDFILE("files/index.html"); ``` ### Set a header ```c // again, you can use local data res_set(res, "Cool", "yes"); + +// or you can use the macro +RES_SET("Cool", "yes"); ``` diff --git a/example/main.c b/example/main.c index 661d4ad..13f7477 100644 --- a/example/main.c +++ b/example/main.c @@ -1,40 +1,40 @@ -#include "../include/ctorm.h" +#include "../include/macros.h" void handle_notfound(req_t *req, res_t *res) { res->code = 404; - res_sendfile(res, "./example/404.html"); + RES_SENDFILE("./example/404.html"); } void handle_post(req_t *req, res_t *res) { body_t *body = req_body_parse(req); if (NULL == body) - return res_send(res, "bad body"); + return RES_SEND("bad body"); char *msg = req_body_get(body, "msg"); if (NULL == msg) - return res_send(res, "bad body"); + return RES_SEND("bad body"); - res_render_add(res, "msg", msg); - res_render(res, "./example/template/post.html"); + RES_RENDER_ADD("msg", msg); + RES_RENDER("./example/template/post.html"); info("Message: %s", msg); - res_set(res, "Cool", "yes"); + RES_SET("Cool", "yes"); req_body_free(body); } void handle_get(req_t *req, res_t *res) { - res_render(res, "./example/template/index.html"); + RES_RENDER("./example/template/index.html"); } int main() { - app_init(); + app_t *app = app_new(); GET("/", handle_get); POST("/post", handle_post); - app_static("/static", "./example/static"); - app_all(handle_notfound); + APP_STATIC("/static", "./example/static"); + APP_ALL(handle_notfound); - if (!app_run("0.0.0.0:8080")) + if (!APP_RUN("0.0.0.0:8080")) error("app failed: %s\n", geterror()); } diff --git a/include/ctorm.h b/include/ctorm.h index 060b9f8..5719165 100644 --- a/include/ctorm.h +++ b/include/ctorm.h @@ -24,24 +24,24 @@ typedef struct app_t { int socket; } app_t; -void app_init(); -bool app_run(const char *); -bool app_add(char *, bool, char *, route_t); -void app_route(req_t *, res_t *); -void app_static(char *, char *); +app_t *app_new(); +bool app_run(app_t*, const char *); +bool app_add(app_t *, char *, bool, char *, route_t); +void app_route(app_t *, req_t *, res_t *); +void app_static(app_t *, char *, char *); void app_404(req_t *, res_t *); -void app_all(route_t); +void app_all(app_t *, route_t); -#define GET(path, func) app_add("GET", false, path, func) -#define PUT(path, func) app_add("PUT", false, path, func) -#define HEAD(path, func) app_add("HEAD", false, path, func) -#define POST(path, func) app_add("POST", false, path, func) -#define DELETE(path, func) app_add("DELETE", false, path, func) -#define OPTIONS(path, func) app_add("OPTIONS", false, path, func) +#define GET(path, func) app_add(app, "GET", false, path, func) +#define PUT(path, func) app_add(app, "PUT", false, path, func) +#define HEAD(path, func) app_add(app, "HEAD", false, path, func) +#define POST(path, func) app_add(app, "POST", false, path, func) +#define DELETE(path, func) app_add(app, "DELETE", false, path, func) +#define OPTIONS(path, func) app_add(app, "OPTIONS", false, path, func) -#define GETR(path, func) app_add("GET", true, path, func) -#define PUTR(path, func) app_add("PUT", true, path, func) -#define HEADR(path, func) app_add("HEAD", true, path, func) -#define POSTR(path, func) app_add("POST", true, path, func) -#define DELETER(path, func) app_add("DELETE", true, path, func) -#define OPTIONSR(path, func) app_add("OPTIONS", true, path, func) +#define GETR(path, func) app_add(app, "GET", true, path, func) +#define PUTR(path, func) app_add(app, "PUT", true, path, func) +#define HEADR(path, func) app_add(app, "HEAD", true, path, func) +#define POSTR(path, func) app_add(app, "POST", true, path, func) +#define DELETER(path, func) app_add(app, "DELETE", true, path, func) +#define OPTIONSR(path, func) app_add(app, "OPTIONS", true, path, func) diff --git a/include/http.h b/include/http.h index f6c51c2..0bd2326 100644 --- a/include/http.h +++ b/include/http.h @@ -2,21 +2,22 @@ #include #include -typedef enum t_method { +typedef enum method_t{ METHOD_GET = 0, METHOD_HEAD = 1, METHOD_POST = 2, METHOD_PUT = 3, METHOD_DELETE = 4, METHOD_OPTIONS = 5, -} t_method; +} method_t; -typedef struct t_method_map { - t_method code; +typedef struct method_map_t { + method_t code; char *name; bool body; -} t_method_map; +} method_map_t; -extern t_method_map http_method_map[]; +extern method_map_t http_method_map[]; extern size_t http_method_sz; + int http_methodid(char *); diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..bebf2aa --- /dev/null +++ b/include/macros.h @@ -0,0 +1,17 @@ +#pragma once + +#include "ctorm.h" + +#define APP_RUN(host) app_run(app, host) +#define APP_ALL(route) app_all(app, route) +#define APP_STATIC(path, dir) app_static(app, path, dir) + +#define REQ_HEADER(header) req_header(req, header) +#define REQ_QUERY(query) req_query(req, query) + +#define RES_SEND(text) res_send(res, text) +#define RES_SENDFILE(path) res_sendfile(res, path) +#define RES_SET(header, value) res_set(res, header, value) + +#define RES_RENDER_ADD(key, value) res_render_add(res, key, value) +#define RES_RENDER(path) res_render(res, path) diff --git a/include/parse.h b/include/parse.h index 16b4150..b8dbac3 100644 --- a/include/parse.h +++ b/include/parse.h @@ -3,18 +3,18 @@ #define BUF_MAX 8192 typedef enum parse_res { - P_CONT = 0, - P_FAIL = 1, - P_OK = 2, + RES_CONT = 0, + RES_FAIL = 1, + RES_OK = 2, } parse_res; -typedef enum handle_st { - H_CONFAIL = 0, - H_BADREQ = 1, - H_OK = 2, -} handle_st; +typedef enum parse_ret { + RET_CONFAIL = 0, + RET_BADREQ = 1, + RET_OK = 2, +} parse_ret; -enum parse_st { +enum parse_state { METHOD = 0, SPACE = 1, PATH = 2, @@ -25,8 +25,7 @@ enum parse_st { BODY = 7, }; -handle_st handle_test(req_t *, int); -handle_st handle_request(req_t *, int); +parse_ret parse_request(req_t *, int); parse_res parse_method(req_t *, int, char *); parse_res parse_path(req_t *, int, char *); diff --git a/include/req.h b/include/req.h index 5f5958c..274171b 100644 --- a/include/req.h +++ b/include/req.h @@ -3,7 +3,7 @@ #include "table.h" typedef struct req_t { - t_method method; + method_t method; char *fullpath; char *encpath; char *path; diff --git a/include/socket.h b/include/socket.h index 68d9312..1240304 100644 --- a/include/socket.h +++ b/include/socket.h @@ -4,6 +4,10 @@ #include #include -#define THREADS 50 -typedef int t_socketop[2]; -bool start_socket(app_t *, char *, int); +#define THREADS 30 +typedef struct socket_args_t { + app_t *app; + int socket; +} socket_args_t; + +bool socket_start(app_t *, char *, int); diff --git a/src/ctorm.c b/src/ctorm.c index 49fae79..e59f648 100644 --- a/src/ctorm.c +++ b/src/ctorm.c @@ -33,17 +33,17 @@ #include #include -app_t *app; -void app_init() { - app = malloc(sizeof(app_t)); +app_t *app_new() { + app_t *app = malloc(sizeof(app_t)); app->allroute = app_404; app->staticdir = NULL; app->staticpath = NULL; app->maps = NULL; setbuf(stdout, NULL); + return app; } -bool app_run(const char *addr) { +bool app_run(app_t *app, const char *addr) { char *save, *ip = NULL, *ports = NULL; char *addrcpy = strdup(addr); int port = -1; @@ -67,7 +67,7 @@ bool app_run(const char *addr) { } info("Starting the application on http://%s:%d", ip, port); - if (start_socket(app, ip, port)) { + if (socket_start(app, ip, port)) { free(addrcpy); return true; } @@ -77,14 +77,14 @@ bool app_run(const char *addr) { return false; } -void app_static(char *path, char *dir) { +void app_static(app_t *app, char *path, char *dir) { app->staticpath = path; - app->staticdir = dir; + app->staticdir = dir; } -void app_all(route_t handler) { app->allroute = handler; } +void app_all(app_t *app, route_t handler) { app->allroute = handler; } -bool app_add(char *method, bool regex, char *path, route_t handler) { +bool app_add(app_t *app, char *method, bool regex, char *path, route_t handler) { routemap_t *new = malloc(sizeof(routemap_t)); if (NULL == new) { errno = AllocFailed; @@ -118,7 +118,7 @@ void app_404(req_t *req, res_t *res) { res->code = 404; } -void app_route(req_t *req, res_t *res) { +void app_route(app_t *app, req_t *req, res_t *res) { bool found = false; regex_t regex; @@ -162,7 +162,7 @@ void app_route(req_t *req, res_t *res) { if (found) goto DONE; - if (NULL == app->staticpath || NULL == app->staticdir) + if (NULL == app->staticpath || NULL == app->staticdir || METHOD_GET != req->method) goto DONE; char *staticpath, *realpath = strdup(req->path); diff --git a/src/parse.c b/src/parse.c index a9d67c7..8ce6280 100644 --- a/src/parse.c +++ b/src/parse.c @@ -21,15 +21,15 @@ char name_valid[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; char *versions[] = {"HTTP/1.1", "HTTP/1.0"}; -enum parse_st parse_order[] = { +enum parse_state parse_order[] = { METHOD, SPACE, PATH, VERSION, NEWLINE, NAME, VALUE, NEWLINE, BODY, }; -handle_st handle_request(req_t *req, int s) { +parse_ret parse_request(req_t *req, int s) { size_t i = 0, indx = 0, - parse_max = sizeof(parse_order) / sizeof(enum parse_st) - 1; - enum parse_st *order = parse_order; - parse_res r = P_FAIL; + parse_max = sizeof(parse_order) / sizeof(enum parse_state) - 1; + enum parse_state *order = parse_order; + parse_res r = RES_FAIL; char cur[1] = "0"; bool done = false; char buf[BUF_MAX + 1]; @@ -39,10 +39,10 @@ handle_st handle_request(req_t *req, int s) { continue; if (BODY != order[i] && cur[0] == '\0') - return H_BADREQ; + return RET_BADREQ; if (indx >= BUF_MAX) - return H_BADREQ; + return RET_BADREQ; buf[indx] = cur[0]; buf[indx + 1] = '\0'; @@ -52,9 +52,9 @@ handle_st handle_request(req_t *req, int s) { r = parse_method(req, indx, buf); break; case SPACE: - r = P_FAIL; + r = RES_FAIL; if (eq(buf, " ")) - r = P_OK; + r = RES_OK; break; case PATH: r = parse_path(req, indx, buf); @@ -63,14 +63,14 @@ handle_st handle_request(req_t *req, int s) { r = parse_version(req, indx, buf); break; case NEWLINE: - r = P_FAIL; + r = RES_FAIL; if (eq(buf, "\n")) { - r = P_OK; + r = RES_OK; break; } if (i - 1 == VALUE) { - r = P_CONT; + r = RES_CONT; i -= 2; } break; @@ -91,12 +91,12 @@ handle_st handle_request(req_t *req, int s) { break; } - if (P_FAIL == r) - return H_BADREQ; + if (RES_FAIL == r) + return RET_BADREQ; - if (P_OK == r) { + if (RES_OK == r) { debug("(Socket %d) Parsing the next section (%d/%d)", s, i, parse_max); - r = P_FAIL; + r = RES_FAIL; indx = 0; i++; @@ -122,7 +122,7 @@ handle_st handle_request(req_t *req, int s) { if (!done) { debug("(Socket %d) %s, most likely the connection died", s, strerror(errno)); - return H_CONFAIL; + return RET_CONFAIL; } debug("Decoding URL"); @@ -142,7 +142,7 @@ handle_st handle_request(req_t *req, int s) { free(pathdup); req->path = strdup(req->encpath); urldecode(req->path); - return H_OK; + return RET_OK; } parse_res parse_urldata(table_t *data, char *rest, int size) { @@ -186,26 +186,26 @@ parse_res parse_urldata(table_t *data, char *rest, int size) { } } - return P_OK; + return RES_OK; } parse_res parse_body(req_t *req, int i, char *buf) { int size = req_body_size(req); if (i + 1 != size) - return P_CONT; + return RES_CONT; req->body = malloc(i + 1); memcpy(req->body, buf, i + 1); req->bodysz = i + 1; - return P_OK; + return RES_OK; } parse_res parse_header_value(req_t *req, int i, char *buf) { if (buf[i] != '\n') - return P_CONT; + return RES_CONT; if (!validate(buf, value_valid, '\n')) - return P_FAIL; + return RES_FAIL; char val[i + 1]; for (int c = 0; c < i + 1; c++) { @@ -213,14 +213,14 @@ parse_res parse_header_value(req_t *req, int i, char *buf) { } val[i] = '\0'; req_add_header_value(req, val); - return P_OK; + return RES_OK; } parse_res parse_header_name(req_t *req, int i, char *buf) { size_t namel = strlen(url_valid); if (i + 1 < 3) - return P_CONT; + return RES_CONT; for (int c = 0; c < i + 1; c++) { int valid = 0; @@ -241,11 +241,11 @@ parse_res parse_header_name(req_t *req, int i, char *buf) { valid = 1; if (!valid) - return P_FAIL; + return RES_FAIL; } if (buf[i] != ' ' || buf[i - 1] != ':') - return P_CONT; + return RES_CONT; char name[i]; for (int c = 0; c < i; c++) @@ -253,31 +253,31 @@ parse_res parse_header_name(req_t *req, int i, char *buf) { name[i - 1] = '\0'; req_add_header(req, name); - return P_OK; + return RES_OK; } parse_res parse_version(req_t *req, int i, char *buf) { for (int v = 0; v < sizeof(versions) / sizeof(char *); v++) { if (eq(versions[v], buf)) { req->version = versions[v]; - return P_OK; + return RES_OK; } if (startswith(versions[v], buf)) - return P_CONT; + return RES_CONT; } - return P_FAIL; + return RES_FAIL; } parse_res parse_path(req_t *req, int i, char *buf) { if (i + 1 == 1) - return P_CONT; + return RES_CONT; if (!validate(buf, url_valid, ' ')) - return P_FAIL; + return RES_FAIL; if (buf[i] != ' ') - return P_CONT; + return RES_CONT; req->fullpath = malloc(i + 1); for (int c = 0; c < i + 1; c++) { @@ -285,7 +285,7 @@ parse_res parse_path(req_t *req, int i, char *buf) { } req->fullpath[i] = '\0'; - return P_OK; + return RES_OK; } parse_res parse_method(req_t *req, int i, char *buf) { @@ -293,7 +293,7 @@ parse_res parse_method(req_t *req, int i, char *buf) { for (int s = 0; s < http_method_sz; s++) { if (eq(http_method_map[s].name, buf)) { req->method = http_method_map[s].code; - return P_OK; + return RES_OK; } if (startswith(http_method_map[s].name, buf)) { @@ -303,6 +303,6 @@ parse_res parse_method(req_t *req, int i, char *buf) { } if (!prefix) - return P_FAIL; - return P_CONT; + return RES_FAIL; + return RES_CONT; } diff --git a/src/socket.c b/src/socket.c index bc5ff4b..19656cc 100644 --- a/src/socket.c +++ b/src/socket.c @@ -19,9 +19,9 @@ #include pool_t *pool; -void handle_con(int *sa) { - int s = *(int *)sa; - debug("(Socket %d) Created thread", s); +void socket_handle(socket_args_t *_args) { + socket_args_t *args = (socket_args_t*)_args; + debug("(Socket %d) Created thread", args->socket); req_t req; req_init(&req); @@ -33,42 +33,44 @@ void handle_con(int *sa) { // to complete the request clock_t t = clock(); bool badreq = false; - handle_st ret = handle_request(&req, s); - debug("Parsed request"); + parse_ret ret = parse_request(&req, args->socket); + debug("(Socket %d) Parsed request", args->socket); char *buf; int bufsz; - if (H_BADREQ == ret) { + if (RET_BADREQ == ret) { badreq = true; res.code = 400; goto CLOSE; } - if (H_CONFAIL == ret) { + if (RET_CONFAIL == ret) { res_free(&res); req_free(&req); - free(sa); - close(s); + + close(args->socket); + free(args); + return; } if (DEBUG) { buf = malloc(req_size(&req)); req_tostr(&req, buf); - debug("\n%s", buf); + debug("(Socket %d)\n%s", args->socket, buf); free(buf); } res_set_version(&res, req.version); - app_route(&req, &res); + app_route(args->app, &req, &res); CLOSE: bufsz = res_size(&res); buf = malloc(bufsz); res_tostr(&res, buf); - send(s, buf, bufsz, 0); + send(args->socket, buf, bufsz, 0); // finish the measurement and print out // the result @@ -82,31 +84,27 @@ void handle_con(int *sa) { res_free(&res); free(buf); - free(sa); - close(s); + close(args->socket); + free(args); } -bool set_opts(int socket) { +bool socket_set_opts(int socket) { int flag = 1; - t_socketop options[] = { - // TCP delayed acknowledgment, buffers and combines multiple ACKs to - // reduce overhead - // it may delay the ACK response by up to 500ms, and we don't want that - // because - // slow bad fast good - {IPPROTO_TCP, TCP_QUICKACK}, - - // nagle's algorithm buffers and combines outgoing packets to solve the - // "small-packet problem", - // we want to send all the packets as fast as possible so we can disable - // buffering with TCP_NODELAY - {IPPROTO_TCP, TCP_NODELAY}, - }; - - for (int i = 0; i < sizeof(options) / sizeof(t_socketop); i++) - if (setsockopt(socket, options[i][0], options[i][1], &flag, sizeof(flag)) < - 0) - return false; + + // TCP delayed acknowledgment, buffers and combines multiple ACKs to + // reduce overhead + // it may delay the ACK response by up to 500ms, and we don't want that + // because + // slow bad fast good + if (setsockopt(socket, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) < 0) + return false; + + // nagle's algorithm buffers and combines outgoing packets to solve the + // "small-packet problem", + // we want to send all the packets as fast as possible so we can disable + // buffering with TCP_NODELAY + if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) < 0) + return false; struct timeval timeout; timeout.tv_sec = 10; @@ -119,11 +117,12 @@ bool set_opts(int socket) { void socket_con(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *address, int socklen, void *ctx) { + socket_args_t *args = malloc(sizeof(socket_args_t)); + args->socket = fd; + args->app = ctx; - set_opts(fd); - int *sock = malloc(sizeof(int)); - memcpy(sock, &fd, sizeof(int)); - pool_add(pool, (void *)handle_con, (void *)sock); + socket_set_opts(fd); + pool_add(pool, (void *)socket_handle, (void *)args); } void socket_err(struct evconnlistener *listener, void *ctx) { @@ -133,7 +132,7 @@ void socket_err(struct evconnlistener *listener, void *ctx) { event_base_loopexit(base, NULL); } -bool start_socket(app_t *app, char *addr, int port) { +bool socket_start(app_t *app, char *addr, int port) { struct sockaddr_in address; bzero(&address, sizeof(address)); pool = pool_init(THREADS); @@ -150,9 +149,10 @@ bool start_socket(app_t *app, char *addr, int port) { } struct evconnlistener *listener = evconnlistener_new_bind( - base, socket_con, NULL, + base, socket_con, app, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE | LEV_OPT_THREADSAFE, -1, (struct sockaddr *)&address, sizeof(address)); + if (NULL == listener) { errno = ListenFailed; return ret;