From 4e37a994cacba36d0dbe9ac832d4034aab4a4f45 Mon Sep 17 00:00:00 2001 From: ngn Date: Mon, 25 Nov 2024 01:51:38 +0300 Subject: [PATCH 1/5] move to hashtables for header, query and form data --- .clang-format | 15 ++- .github/workflows/docker.yml | 3 +- Makefile | 25 ++-- README.md | 15 ++- example/echo/main.c | 13 +- example/middleware/main.c | 3 +- include/all.h | 52 ++++---- include/ctorm.h | 25 ++-- include/errors.h | 41 +++---- include/form.h | 10 ++ include/headers.h | 20 +++ include/http.h | 20 +-- include/log.h | 22 ++-- include/options.h | 13 ++ include/pool.h | 3 +- include/req.h | 41 ++++--- include/res.h | 8 +- include/socket.h | 10 +- include/table.h | 48 +++++--- include/util.h | 2 +- src/ctorm.c | 169 +++++++++++-------------- src/errors.c | 10 +- src/form.c | 9 ++ src/headers.c | 58 +++++++++ src/log.c | 24 ++-- src/parse.c | 181 +++++++++++++++------------ src/pool.c | 13 +- src/req.c | 153 ++++++++++------------- src/res.c | 67 +++++----- src/socket.c | 232 ++++++++++++++++++++++------------- src/table.c | 185 ++++++++++++++-------------- src/util.c | 10 -- 32 files changed, 839 insertions(+), 661 deletions(-) create mode 100644 include/form.h create mode 100644 include/headers.h create mode 100644 include/options.h create mode 100644 src/form.c create mode 100644 src/headers.c diff --git a/.clang-format b/.clang-format index 9fa0ea7..d6de530 100644 --- a/.clang-format +++ b/.clang-format @@ -3,15 +3,15 @@ Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign -AlignArrayOfStructures: Left +AlignArrayOfStructures: Left AlignConsecutiveAssignments: - Enabled: true + Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false PadOperators: true AlignConsecutiveBitFields: - Enabled: false + Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false @@ -23,7 +23,7 @@ AlignConsecutiveDeclarations: AlignCompound: false PadOperators: false AlignConsecutiveMacros: - Enabled: false + Enabled: true AcrossEmptyLines: false AcrossComments: false AlignCompound: false @@ -48,8 +48,8 @@ AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine AttributeMacros: - __capability -BinPackArguments: false -BinPackParameters: true +BinPackArguments: false +BinPackParameters: true BitFieldColonSpacing: Both BraceWrapping: AfterCaseLabel: false @@ -170,7 +170,7 @@ RequiresClausePosition: OwnLine RequiresExpressionIndentation: OuterScope SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 -SortIncludes: CaseSensitive +SortIncludes: false SortJavaStaticImport: Before SortUsingDeclarations: LexicographicNumeric SpaceAfterCStyleCast: false @@ -223,4 +223,3 @@ WhitespaceSensitiveMacros: - STRINGIZE ... - diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0bf800c..adced48 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,5 +25,6 @@ jobs: - name: 'Build Inventory Image' run: | - docker build . --tag ghcr.io/ngn13/ctorm:latest + docker build . --tag ghcr.io/ngn13/ctorm:latest ghcr.io/ngn13/ctorm:${GITHUB_REF##*/} + docker push ghcr.io/ngn13/ctorm:${GITHUB_REF##*/} docker push ghcr.io/ngn13/ctorm:latest diff --git a/Makefile b/Makefile index e67dc28..7ee3ee4 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,26 @@ PREFIX = /usr CC = gcc -DEBUG = 0 +# sources SRCS = $(wildcard src/*.c) OBJS = $(patsubst src/%.c,dist/%.o,$(SRCS)) -HDRS = $(wildcard include/*.h) +HDRS = $(wildcard include/*.h) + +# compiler flags CFLAGS = -O3 -march=native -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -LIBS = -lpthread -levent -lcjson +LIBS = -lpthread -lcjson + +# options +CTORM_DEBUG = 0 -dist/libctorm.so: $(OBJS) +dist/libctorm.so: $(OBJS) mkdir -p dist - $(CC) -shared -o $@ $^ $(LIBS) $(CFLAGS) + $(CC) -shared -o $@ $^ $(LIBS) $(CFLAGS) -dist/%.o: src/%.c +dist/%.o: src/%.c mkdir -p dist - $(CC) -c -Wall -fPIC -o $@ $^ $(LIBS) $(CFLAGS) -DDEBUG=${DEBUG} + $(CC) -c -Wall -fPIC -o $@ $^ $(LIBS) $(CFLAGS) \ + -DCTORM_DEBUG=$(CTORM_DEBUG) install: install -m755 dist/libctorm.so $(DESTDIR)$(PREFIX)/lib/libctorm.so @@ -28,7 +34,10 @@ uninstall: format: clang-format -i -style=file src/*.c include/*.h example/*/*.c +clean: + rm -rf dist + example: $(MAKE) -C $@ -.PHONY: test install uninstall format example +.PHONY: test install uninstall format clean example diff --git a/README.md b/README.md index 67e2d62..aa06cba 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ _____/ /__________ ____ ___ / ___/ __/ ___/ __ \/ __ `__ \ / /__/ /_/ / / /_/ / / / / / / -\___/\__/_/ \____/_/ /_/ /_/ 1.4 +\___/\__/_/ \____/_/ /_/ /_/ 1.5 ``` @@ -12,8 +12,8 @@ ![](https://img.shields.io/github/v/tag/ngn13/ctorm?label=version) ![](https://img.shields.io/github/license/ngn13/ctorm) -ctorm is a libevent based, multi-threaded HTTP server for `HTTP/1.1` and `HTTP/1.0`. -It has a (fairly) easy API for general web server applications. +ctorm is a multi-threaded HTTP server for `HTTP/1.1` and `HTTP/1.0`. +It has an easy API for general web server applications. ### Important!!! This software is pretty much in alpha state. I don't suggest you use ctorm on @@ -38,22 +38,21 @@ Benchmark results for hello world applications (see [benchmark](benchmark/)): | ---------------- | ------------- | ---------------- | | crow (C++) | v1.2.0 | ~4 ms | | fiber (Go) | v3.0.0-beta.1 | ~4 ms | -| **ctorm (C)** | **1.4** | **~5 ms** | +| **ctorm (C)** | **1.5** | **~4.5 ms** | | tide (Rust) | 0.16.0 | ~12 ms | | express (NodeJS) | 4.19.2 | ~21 ms | ### Installation You will need the following software in order to build and install ctorm: - GCC and other general build tools (`build-essential`) -- libevent and it's headers (`libevent`, `libevent-dev`) - cJSON and it's headers (`cjson`, `libcjson-dev`) - tar (to extract the release archive) First [download the latest release](https://github.com/ngn13/ctorm/tags) archive, **do not compile from the latest commit unless you are doing development**: ```bash -wget https://github.com/ngn13/ctorm/archive/refs/tags/1.3.tar.gz -tar xf 1.3.tar.gz && cd ctorm-1.3 +wget https://github.com/ngn13/ctorm/archive/refs/tags/1.5.tar.gz +tar xf 1.5.tar.gz && cd ctorm-1.5 ``` Then use the `make` command to build and install: @@ -121,7 +120,7 @@ CMD ["/app/server"] ### Development For development, you can compile the library with debug mode: ```bash -make DEBUG=1 +make CTORM_DEBUG=1 ``` then you can use the example applications for testing: ```bash diff --git a/example/echo/main.c b/example/echo/main.c index 68ef545..056d08e 100644 --- a/example/echo/main.c +++ b/example/echo/main.c @@ -7,16 +7,17 @@ void handle_notfound(req_t *req, res_t *res) { } void handle_post(req_t *req, res_t *res) { - form_t *form = REQ_FORM(); - if (NULL == form) { + char *msg = NULL; + form_t form; + + if (!REQ_FORM(&form)) { res->code = 400; return RES_SEND("bad body"); } - char *msg = req_form_get(form, "msg"); - if (NULL == msg) { + if (NULL == (msg = form_get(&form, "msg"))) { res->code = 400; - req_form_free(form); + req_form_free(&form); return RES_SEND("bad body"); } @@ -25,7 +26,7 @@ void handle_post(req_t *req, res_t *res) { RES_SET("Cool", "yes"); - req_form_free(form); + req_form_free(&form); } void handle_get(req_t *req, res_t *res) { diff --git a/example/middleware/main.c b/example/middleware/main.c index 7550800..aa1a6bc 100644 --- a/example/middleware/main.c +++ b/example/middleware/main.c @@ -16,7 +16,8 @@ void index_redirect(req_t *req, res_t *res) { } void user_auth(req_t *req, res_t *res) { - char *auth = REQ_HEADER("Authorization"); + char *auth = REQ_GET("Authorization"); + if (NULL != auth && strcmp(auth, "secretpassword") == 0) return; diff --git a/include/all.h b/include/all.h index 1ed1572..a8fe413 100644 --- a/include/all.h +++ b/include/all.h @@ -22,36 +22,36 @@ #include "ctorm.h" -#define ALL(app, path, func) app_add(app, "", false, path, func) -#define GET(app, path, func) app_add(app, "GET", false, path, func) -#define PUT(app, path, func) app_add(app, "PUT", false, path, func) -#define HEAD(app, path, func) app_add(app, "HEAD", false, path, func) -#define POST(app, path, func) app_add(app, "POST", false, path, func) -#define DELETE(app, path, func) app_add(app, "DELETE", false, path, func) +#define ALL(app, path, func) app_add(app, "", false, path, func) +#define GET(app, path, func) app_add(app, "GET", false, path, func) +#define PUT(app, path, func) app_add(app, "PUT", false, path, func) +#define HEAD(app, path, func) app_add(app, "HEAD", false, path, func) +#define POST(app, path, func) app_add(app, "POST", false, path, func) +#define DELETE(app, path, func) app_add(app, "DELETE", false, path, func) #define OPTIONS(app, path, func) app_add(app, "OPTIONS", false, path, func) -#define MIDDLEWARE_ALL(app, path, func) app_add(app, "", true, path, func) -#define MIDDLEWARE_GET(app, path, func) app_add(app, "GET", true, path, func) -#define MIDDLEWARE_PUT(app, path, func) app_add(app, "PUT", true, path, func) -#define MIDDLEWARE_HEAD(app, path, func) app_add(app, "HEAD", true, path, func) -#define MIDDLEWARE_POST(app, path, func) app_add(app, "POST", true, path, func) -#define MIDDLEWARE_DELETE(app, path, func) app_add(app, "DELETE", true, path, func) +#define MIDDLEWARE_ALL(app, path, func) app_add(app, "", true, path, func) +#define MIDDLEWARE_GET(app, path, func) app_add(app, "GET", true, path, func) +#define MIDDLEWARE_PUT(app, path, func) app_add(app, "PUT", true, path, func) +#define MIDDLEWARE_HEAD(app, path, func) app_add(app, "HEAD", true, path, func) +#define MIDDLEWARE_POST(app, path, func) app_add(app, "POST", true, path, func) +#define MIDDLEWARE_DELETE(app, path, func) app_add(app, "DELETE", true, path, func) #define MIDDLEWARE_OPTIONS(app, path, func) app_add(app, "OPTIONS", true, path, func) -#define REQ_METHOD() req_method(req) -#define REQ_BODY_SIZE() req_body_size(req) -#define REQ_BODY(data) req_body(req, data) -#define REQ_HEADER(header) req_header(req, header) +#define REQ_METHOD() req_method(req) +#define REQ_BODY_SIZE() req_body_size(req) +#define REQ_BODY(data) req_body(req, data) +#define REQ_GET(header) req_get(req, header) #define REQ_QUERY(query) req_query(req, query) -#define REQ_FORM() req_form_parse(req) -#define REQ_JSON() req_json_parse(req) +#define REQ_FORM(f) req_form_parse(req, f) +#define REQ_JSON() req_json_parse(req) -#define RES_SEND(text) res_send(res, text, 0) -#define RES_SENDFILE(path) res_sendfile(res, path) +#define RES_SEND(text) res_send(res, text, 0) +#define RES_SENDFILE(path) res_sendfile(res, path) #define RES_SET(header, value) res_set(res, header, value) -#define RES_DEL(header) res_del(res, header) -#define RES_JSON(json) res_json(res, json) -#define RES_CLEAR() res_clear(res) -#define RES_FMT(fmt, ...) res_fmt(res, fmt, __VA_ARGS__) -#define RES_ADD(fmt, ...) res_add(res, fmt, __VA_ARGS__) -#define RES_REDIRECT(url) res_redirect(res, url) +#define RES_DEL(header) res_del(res, header) +#define RES_JSON(json) res_json(res, json) +#define RES_CLEAR() res_clear(res) +#define RES_FMT(fmt, ...) res_fmt(res, fmt, __VA_ARGS__) +#define RES_ADD(fmt, ...) res_add(res, fmt, __VA_ARGS__) +#define RES_REDIRECT(url) res_redirect(res, url) diff --git a/include/ctorm.h b/include/ctorm.h index 50a6c60..a80d24d 100644 --- a/include/ctorm.h +++ b/include/ctorm.h @@ -1,8 +1,6 @@ #pragma once -#define CTORM_VERSION "1.4" - -#include +#define CTORM_VERSION "1.5" #include "errors.h" #include "http.h" @@ -32,12 +30,12 @@ typedef struct routemap_t { // ## app configuration ## // ####################### typedef struct app_config_t { + int max_connections; // max parallel connection count bool disable_logging; // disables request logging and the banner bool handle_signal; // disables SIGINT handler (which stops app_run()) bool server_header; // disable sending the "Server: ctorm" header in the response - bool lock_request; // locks threads until the request handler returns - long int tcp_timeout; // sets the TCP socket timeout for sending and receiving - int pool_size; // app threadpool size + __time_t tcp_timeout; // sets the TCP socket timeout for sending and receiving + uint64_t pool_size; // app threadpool size } app_config_t; void app_config_new(app_config_t *config); @@ -46,14 +44,13 @@ void app_config_new(app_config_t *config); // ## app structure ## // ################### typedef struct app_t { - struct event_base *base; // libevent event base - routemap_t *maps; // route map - char *staticpath; // static directory serving path - char *staticdir; // static directory - route_t allroute; // all handler route (see app_all()) - bool running; // is the app running? - pool_t *pool; // thread pool for the app - pthread_mutex_t request_mutex; // mutex used to lock request threads + routemap_t *middleware_maps; // middleware map + routemap_t *route_maps; // route map + char *staticpath; // static directory serving path + char *staticdir; // static directory + route_t allroute; // all handler route (see app_all()) + bool running; // is the app running? + pool_t *pool; // thread pool for the app app_config_t *config; // app configuration bool is_default_config; // using the default configuration? diff --git a/include/errors.h b/include/errors.h index d114792..d49f10b 100644 --- a/include/errors.h +++ b/include/errors.h @@ -3,27 +3,26 @@ typedef enum app_error_t { BadTcpTimeout = 9908, BadPoolSize = 9909, - EventFailed = 9910, - PoolFailed = 9911, - ListenFailed = 9912, - BadAddress = 9913, - BadPort = 9914, - OptFailed = 9915, - AllocFailed = 9917, - UnknownErr = 9918, - CantRead = 9919, - SizeFail = 9920, - BadReadPerm = 9921, - FileNotExists = 9922, - BadPath = 9923, - InvalidAppPointer = 9924, - BadUrlPointer = 9925, - BadJsonPointer = 9926, - BadFmtPointer = 9927, - BadPathPointer = 9928, - BadDataPointer = 9929, - BadHeaderPointer = 9930, - MutexFail = 9931, + PoolFailed = 9910, + ListenFailed = 9911, + BadAddress = 9912, + BadPort = 9913, + OptFailed = 9914, + AllocFailed = 9915, + UnknownErr = 9916, + CantRead = 9917, + SizeFail = 9918, + BadReadPerm = 9919, + FileNotExists = 9920, + BadPath = 9921, + InvalidAppPointer = 9922, + BadUrlPointer = 9923, + BadJsonPointer = 9924, + BadFmtPointer = 9925, + BadPathPointer = 9926, + BadDataPointer = 9927, + BadHeaderPointer = 9928, + BadMaxConnCount = 9929, } app_error_t; struct app_error_desc_t { diff --git a/include/form.h b/include/form.h new file mode 100644 index 0000000..9c7241a --- /dev/null +++ b/include/form.h @@ -0,0 +1,10 @@ +#pragma once + +#include "table.h" + +typedef table_t form_t; + +#define form_init(f) table_init(f, NULL, NULL) +#define form_free(f) table_free(f) + +char *form_get(form_t *f, char *k); diff --git a/include/headers.h b/include/headers.h new file mode 100644 index 0000000..945429b --- /dev/null +++ b/include/headers.h @@ -0,0 +1,20 @@ +#pragma once + +#include "table.h" + +typedef table_t headers_t; +typedef table_entry_t header_t; + +int headers_cmp(const char *s1, const char *s2); +uint64_t headers_hasher(const char *data); + +#define headers_init(h) table_init(h, headers_cmp, headers_hasher) +#define headers_free(h) table_free(h) + +#define headers_start(e) table_start(e) +#define headers_next(h, e) table_next(h, e) + +#define headers_add(h, k, v, a) table_add(h, k, v, a); +bool headers_set(headers_t *h, char *key, char *value, bool dup); +char *headers_get(headers_t *h, char *key); +#define headers_del(h, k) table_del(h, k) diff --git a/include/http.h b/include/http.h index a4f06d0..c734a02 100644 --- a/include/http.h +++ b/include/http.h @@ -3,7 +3,7 @@ #include // HTTP method enum -typedef enum method_t { +typedef enum { METHOD_GET = 0, METHOD_HEAD = 1, METHOD_POST = 2, @@ -13,7 +13,7 @@ typedef enum method_t { } method_t; // HTTP method map -typedef struct method_map_t { +typedef struct { method_t code; char *name; bool body; @@ -24,13 +24,17 @@ extern method_map_t http_method_map[]; // supported HTTP versions extern char *http_versions[]; -// static values that are only calculated once for optimization -// this calculation is made in http_static_load(), which is -// called by app_new() +/* -// most of these values are max sizes, which are used to allocate -// static buffers on stack for optimization -typedef struct http_static { + * static values that are only calculated once for optimization + * this calculation is made in http_static_load(), which is + * called by app_new() + + * most of these values are max sizes, which are used to allocate + * static buffers on stack for optimization + +*/ +typedef struct { size_t method_count; // stores the count of HTTP methods size_t method_max; // stores the longest HTTP method's length diff --git a/include/log.h b/include/log.h index 332de6a..2b83603 100644 --- a/include/log.h +++ b/include/log.h @@ -3,17 +3,23 @@ #include "req.h" #include "res.h" -#define COLOR_RED "\x1b[31m" -#define COLOR_BOLD "\x1b[1m" -#define COLOR_BLUE "\x1b[34m" -#define COLOR_CYAN "\x1b[36m" -#define COLOR_YELLO "\x1b[33m" -#define COLOR_GREEN "\x1b[32m" +#define COLOR_RED "\x1b[31m" +#define COLOR_BOLD "\x1b[1m" +#define COLOR_BLUE "\x1b[34m" +#define COLOR_CYAN "\x1b[36m" +#define COLOR_YELLO "\x1b[33m" +#define COLOR_GREEN "\x1b[32m" #define COLOR_MAGENTA "\x1b[35m" -#define COLOR_RESET "\x1b[0m" +#define COLOR_RESET "\x1b[0m" + +#if CTORM_DEBUG +#define debug(...) _debug(__VA_ARGS__) +#else +#define debug(...) asm("nop") +#endif void log_req(double, req_t *, res_t *); void info(const char *, ...); void warn(const char *, ...); void error(const char *, ...); -void debug(const char *, ...); +void _debug(const char *, ...); diff --git a/include/options.h b/include/options.h new file mode 100644 index 0000000..b41d039 --- /dev/null +++ b/include/options.h @@ -0,0 +1,13 @@ +#pragma once + +/* + + * these options are provided as arguments to GCC in the Makefile + * however in case the library gets compiled differently + * the default values are defined here as a backup + +*/ + +#ifndef CTORM_DEBUG +#define CTORM_DEBUG 0 +#endif diff --git a/include/pool.h b/include/pool.h index 061c8ba..946c2b3 100644 --- a/include/pool.h +++ b/include/pool.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include typedef void (*func_t)(void *arg); typedef struct work_t { @@ -23,6 +24,6 @@ typedef struct pool_t { bool stop; } pool_t; -pool_t *pool_init(int); +pool_t *pool_init(uint64_t); bool pool_add(pool_t *, func_t, void *); void pool_stop(pool_t *); diff --git a/include/req.h b/include/req.h index 9fc7564..7c4de19 100644 --- a/include/req.h +++ b/include/req.h @@ -1,42 +1,43 @@ #pragma once + +#include +#include + #include "http.h" #include "table.h" -#include +#include "form.h" +#include "headers.h" typedef struct req_t { method_t method; // HTTP method (GET, POST, PUT etc.) bool cancel; - char *encpath; // url encoded path (does include queries) - char *path; // url decoded path (does not include queries) - char *version; // HTTP version number (for example "HTTP/1.1") - char *addr; // TCP connection address + char *encpath; // url encoded path (does include queries) + char *path; // url decoded path (does not include queries) + char *version; // HTTP version number (for example "HTTP/1.1") + char addr[INET6_ADDRSTRLEN]; // TCP connection address - table_t headers; // HTTP headers - table_t query; // HTTP queries (for example "?key=1") + headers_t headers; // HTTP headers + table_t query; // HTTP queries (for example "?key=1") size_t bodysize; // size of the HTTP body char *body; // raw body, does NOT have a NULL terminator } req_t; -typedef table_t form_t; // parsed form body is just a table - void req_init(req_t *); // setup a request void req_free(req_t *); // cleanup a request size_t req_size(req_t *); // get the request size (used with req_tostr()) void req_tostr(req_t *, char *); // convert the request to string -char *req_method(req_t *); // get the request method (GET, POST, PUT etc.) -bool req_body(req_t *, char *); // get the request body, with a NULL terminator (printable) -size_t req_body_size(req_t *); // get the body size that will be returned with req_body() -char *req_query(req_t *, char *); // get a request URL query -char *req_header(req_t *, char *); // get a request header +char *req_method(req_t *); // get the request method (GET, POST, PUT etc.) +bool req_body(req_t *, char *); // get the request body, with a NULL terminator (printable) +size_t req_body_size(req_t *); // get the body size that will be returned with req_body() +char *req_query(req_t *, char *); // get a request URL query + +void req_set(req_t *req, char *name, char *value, bool dup); // set a request header +char *req_get(req_t *, char *); // get a request header -form_t *req_form_parse(req_t *); // parse the URL encoded form body -char *req_form_get(form_t *, char *); // get a key/value from the body -void req_form_free(form_t *); // free the parsed form body +bool req_form_parse(req_t *, form_t *); // parse the URL encoded form body +void req_form_free(form_t *); // free the parsed form body cJSON *req_json_parse(req_t *); // parse the JSON formatted body void req_json_free(cJSON *); // free the parsed JSON body - -void req_add_header(req_t *, char *); // add a new header to request (internal) -void req_add_header_value(req_t *, char *); // set the last header value (internal) diff --git a/include/res.h b/include/res.h index bff90a0..6141299 100644 --- a/include/res.h +++ b/include/res.h @@ -1,14 +1,16 @@ #pragma once -#include "http.h" -#include "table.h" + #include +#include "headers.h" +#include "http.h" + typedef struct res_t { unsigned short code; char *version; char *body; size_t bodysize; - table_t headers; + headers_t headers; } res_t; void res_init(res_t *); diff --git a/include/socket.h b/include/socket.h index a081c20..b02e76a 100644 --- a/include/socket.h +++ b/include/socket.h @@ -1,13 +1,15 @@ #pragma once #include "ctorm.h" #include "util.h" + #include #include +#include typedef struct socket_args_t { - app_t *app; - int socket; - struct sockaddr *address; + app_t *app; + int socket; + struct sockaddr addr; } socket_args_t; -bool socket_start(app_t *, char *, int); +bool socket_start(app_t *, char *, uint16_t); diff --git a/include/table.h b/include/table.h index 05c8158..ae2d7eb 100644 --- a/include/table.h +++ b/include/table.h @@ -1,23 +1,39 @@ #pragma once + #include +#include + +#define TABLE_SIZE 20 -typedef struct table_node_t { - struct table_node_t *next; - char *key; - char *value; - bool alloced; +typedef struct table_node { + struct table_node *next; + char *key; + char *value; + bool alloced; } table_node_t; -typedef struct table_t { - struct table_node_t *head; - struct table_node_t *tail; +typedef struct { + uint8_t _indx; + table_node_t *_node; + char *key; + char *value; +} table_entry_t; + +typedef int(table_cmp_t)(const char *s1, const char *s2); +typedef uint64_t(table_hash_t)(const char *data); + +typedef struct { + table_node_t *lists[TABLE_SIZE]; + table_cmp_t *comparer; + table_hash_t *hasher; } table_t; -void table_init(table_t *); -bool table_add(table_t *, char *, bool); -bool table_set(table_t *, char *); -bool table_update(table_t *, char *, char *); -char **table_next(table_t *, char **); -char *table_get(table_t *, char *); -void table_free(table_t *); -bool table_del(table_t *, char *); +void table_init(table_t *t, table_cmp_t *comparer, table_hash_t *hasher); +void table_free(table_t *t); + +bool table_add(table_t *t, char *key, char *value, bool alloced); +table_node_t *table_get(table_t *t, char *key); +void table_del(table_t *t, char *key); + +#define table_start(e) bzero(e, sizeof(table_entry_t)); +bool table_next(table_t *t, table_entry_t *cur); diff --git a/include/util.h b/include/util.h index e88f8ef..2d1bc75 100644 --- a/include/util.h +++ b/include/util.h @@ -2,7 +2,7 @@ #include #include -bool eq(char *, char *); +#define eq(s1, s2) (strcmp(s1, s2) == 0) bool startswith(char *, char *); bool endswith(char *, char *); diff --git a/src/ctorm.c b/src/ctorm.c index da43313..9b9a29e 100644 --- a/src/ctorm.c +++ b/src/ctorm.c @@ -18,31 +18,33 @@ */ -#include "../include/ctorm.h" #include "../include/errors.h" -#include "../include/log.h" +#include "../include/socket.h" + +#include "../include/ctorm.h" #include "../include/pool.h" + +#include "../include/log.h" #include "../include/req.h" #include "../include/res.h" -#include "../include/socket.h" -#include -#include #include -#include #include -#include + +#include #include #include #include +#include +#include + app_t *signal_app; void app_signal(int sig) { if (NULL == signal_app) return; - event_base_loopbreak(signal_app->base); signal_app->running = false; } @@ -68,13 +70,13 @@ app_t *app_new(app_config_t *_config) { } else if (config->tcp_timeout == 0) warn("Setting the TCP timeout to 0 may allow attackers to DoS your application"); - if (config->pool_size <= 0) { - errno = BadPoolSize; + if (config->max_connections <= 0) { + errno = BadMaxConnCount; goto fail; } - if (NULL == (app->base = event_base_new())) { - errno = EventFailed; + if (config->pool_size <= 0) { + errno = BadPoolSize; goto fail; } @@ -83,11 +85,6 @@ app_t *app_new(app_config_t *_config) { goto fail; } - if (config->lock_request && pthread_mutex_init(&app->request_mutex, NULL) != 0) { - errno = MutexFail; - goto fail; - } - http_static_load(); setbuf(stdout, NULL); return app; @@ -101,26 +98,28 @@ void app_free(app_t *app) { if (NULL == app) return; - if (NULL != app->base) - event_base_free(app->base); - if (NULL != app->pool) pool_stop(app->pool); // reset setbuf - int stdout_cp = dup(1); + routemap_t *cur = NULL, *prev = NULL; + int stdout_cp = dup(1); close(1); dup2(stdout_cp, 1); - routemap_t *cur = app->maps, *prev = NULL; - while (NULL != cur) { + cur = app->middleware_maps; + while (cur != NULL) { prev = cur; cur = cur->next; free(prev); } - if (app->config->lock_request) - pthread_mutex_destroy(&app->request_mutex); + cur = app->route_maps; + while (cur != NULL) { + prev = cur; + cur = cur->next; + free(prev); + } if (app->is_default_config) free(app->config); @@ -132,10 +131,10 @@ void app_config_new(app_config_t *config) { if (NULL == config) return; + config->max_connections = 1000; config->disable_logging = false; config->handle_signal = true; config->server_header = true; - config->lock_request = false; config->tcp_timeout = 10; config->pool_size = 30; } @@ -159,20 +158,19 @@ bool app_run(app_t *app, const char *addr) { memcpy(addrcpy, addr, addrsize); - ip = strtok_r(addrcpy, ":", &save); - if (NULL == ip) { + if (NULL == (ip = strtok_r(addrcpy, ":", &save))) { errno = BadAddress; return ret; } - ports = strtok_r(NULL, ":", &save); - if (NULL == ports) { + if (NULL == (ports = strtok_r(NULL, ":", &save))) { errno = BadAddress; return ret; } port = atoi(ports); - if (port > 65535 || port <= 0) { + + if (port > UINT16_MAX || port <= 0) { errno = BadPort; return ret; } @@ -182,15 +180,17 @@ bool app_run(app_t *app, const char *addr) { app->running = true; signal_app = app; - if (app->config->handle_signal) - signal(SIGINT, app_signal); + if (app->config->handle_signal) { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_handler = app_signal; + sa.sa_flags = 0; + sigaction(SIGINT, &sa, NULL); + } if (socket_start(app, ip, port)) ret = true; - if (app->config->handle_signal) - signal(SIGINT, SIG_DFL); - app->running = false; signal_app = NULL; @@ -228,8 +228,9 @@ bool app_add(app_t *app, char *method, bool is_middleware, char *path, route_t h return false; } - routemap_t *new = malloc(sizeof(routemap_t)); - if (NULL == new) { + routemap_t *new = NULL, *cur = NULL, **maps = is_middleware ? &app->middleware_maps : &app->route_maps; + + if ((new = malloc(sizeof(routemap_t))) == NULL) { errno = AllocFailed; return false; } @@ -245,21 +246,16 @@ bool app_add(app_t *app, char *method, bool is_middleware, char *path, route_t h else new->is_all = false; - if (NULL == app->maps) { - app->maps = new; + if (NULL == (cur = *maps)) { + *maps = new; return true; } - routemap_t *cur = app->maps; - while (NULL != cur) { - if (NULL == cur->next) { - cur->next = new; - return true; - } + while (NULL != cur->next) cur = cur->next; - } - return false; + cur->next = new; + return true; } void app_404(req_t *req, res_t *res) { @@ -269,66 +265,37 @@ void app_404(req_t *req, res_t *res) { } void app_route(app_t *app, req_t *req, res_t *res) { - // matches will be stored in a list, - // this way we can make sure that we call - // middlewares before the actual routes - routemap_t **middlewares = malloc(sizeof(routemap_t *)); - size_t mindex = 0; - - routemap_t **routes = malloc(sizeof(routemap_t *)); - size_t rindex = 0; - - middlewares[mindex] = NULL; - routes[rindex] = NULL; - - routemap_t *cur = app->maps; - while (NULL != cur) { - // if the method does not match and the route does not handle - // all the methods, continue + routemap_t *cur = NULL; + bool found_handler = false; + + // call the middlewares, stop if a middleware cancels the request + for (cur = app->middleware_maps; !req->cancel && cur != NULL; cur = cur->next) { if (cur->method != req->method && !cur->is_all) - goto cont; + continue; - // compare the paths if (!path_matches(cur->path, req->path)) - goto cont; - - // add it to the middleware list - if (cur->is_middleware) { - middlewares[mindex++] = cur; - middlewares = realloc(middlewares, sizeof(routemap_t *) * (mindex + 1)); - middlewares[mindex++] = NULL; - goto cont; - } - - // add it to the routes list - routes[rindex++] = cur; - routes = realloc(routes, sizeof(routemap_t *) * (rindex + 1)); - routes[rindex++] = NULL; + continue; - cont: - cur = cur->next; + cur->handler(req, res); + found_handler = true; } - // don't call the middleware if there's no route handler - if (mindex > 0 || rindex == 0) - mindex = 0; - - // call the middlewares, stop if a middleware cancels the request - for (int i = 0; !req->cancel && middlewares[i] != NULL; i++) - middlewares[i]->handler(req, res); - // if the request is not cancelled, call all the routes if (!req->cancel) { - for (int i = 0; routes[i] != NULL; i++) - routes[i]->handler(req, res); - } + for (cur = app->route_maps; !req->cancel && cur != NULL; cur = cur->next) { + if (cur->method != req->method && !cur->is_all) + continue; + + if (!path_matches(cur->path, req->path)) + continue; - // cleanup the lists - free(middlewares); - free(routes); + cur->handler(req, res); + found_handler = true; + } + } // is the request already handled? - if (rindex != 0 || mindex != 0) + if (found_handler) return; // if not check if we have a static route configured @@ -378,7 +345,11 @@ void app_route(app_t *app, req_t *req, res_t *res) { return; end: - // if the request can't be handled, call the all route - // by default its the 404 route + /* + + * if the request can't be handled, call the all route + * by default its the 404 route + + */ return app->allroute(req, res); } diff --git a/src/errors.c b/src/errors.c index 6f49b63..d9923af 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,11 +1,13 @@ #include "../include/errors.h" -#include + #include +#include + +#include struct app_error_desc_t descs[] = { {.code = BadTcpTimeout, .desc = "invalid TCP timeout" }, {.code = BadPoolSize, .desc = "invalid pool size" }, - {.code = EventFailed, .desc = "failed to create event base" }, {.code = PoolFailed, .desc = "failed to create threadpool" }, {.code = ListenFailed, .desc = "failed to listen on the interface" }, {.code = BadAddress, .desc = "bad address for the interface" }, @@ -25,7 +27,7 @@ struct app_error_desc_t descs[] = { {.code = BadPathPointer, .desc = "invalid path pointer" }, {.code = BadDataPointer, .desc = "invalid data pointer" }, {.code = BadHeaderPointer, .desc = "invalid header name/value pointer" }, - {.code = MutexFail, .desc = "failed to initialize thread mutex" }, + {.code = BadMaxConnCount, .desc = "invalid max connection count" }, }; char *app_geterror_code(app_error_t code) { @@ -33,7 +35,7 @@ char *app_geterror_code(app_error_t code) { if (descs[i].code == code) return descs[i].desc; } - return NULL; + return strerror(code); } char *app_geterror() { diff --git a/src/form.c b/src/form.c new file mode 100644 index 0000000..7c7aebc --- /dev/null +++ b/src/form.c @@ -0,0 +1,9 @@ +#include "../include/table.h" +#include "../include/form.h" + +#include + +char *form_get(form_t *f, char *k) { + table_node_t *node = table_get(f, k); + return NULL == node ? NULL : node->value; +} diff --git a/src/headers.c b/src/headers.c new file mode 100644 index 0000000..04ddb92 --- /dev/null +++ b/src/headers.c @@ -0,0 +1,58 @@ +#include "../include/headers.h" + +#include +#include + +#define tolower(c) (c | 32) + +int headers_cmp(const char *s1, const char *s2) { + while (*s1 != 0 && *s2 != 0) { + if (tolower(*s1) != tolower(*s2)) + return -1; + s1++; + s2++; + } + + return (*s1 == 0 && *s2 == 0) ? 0 : -1; +} + +uint64_t headers_hasher(const char *data) { + uint64_t sum = 0; + + for (; *data != 0; data++) + sum += tolower(*data); + + return sum; +} + +bool headers_set(headers_t *h, char *key, char *value, bool dup) { + table_node_t *node = NULL; + char *valdp = value; + + if (dup) + valdp = strdup(value); + + if ((node = table_get(h, key)) == NULL) + return table_add(h, dup ? strdup(key) : key, valdp, true); + + if (node->alloced) { + free(node->value); + node->value = valdp; + return true; + } + + node->alloced = true; + node->key = dup ? strdup(key) : key; + node->value = valdp; + + return true; +} + +char *headers_get(headers_t *h, char *key) { + table_node_t *node = table_get(h, key); + + if (NULL == node) + return NULL; + + return node->value; +} diff --git a/src/log.c b/src/log.c index a25274d..cb3cfbc 100644 --- a/src/log.c +++ b/src/log.c @@ -1,5 +1,7 @@ -#include "../include/log.h" #include "../include/ctorm.h" +#include "../include/options.h" + +#include "../include/log.h" #include "../include/req.h" #include "../include/res.h" @@ -18,13 +20,13 @@ void log_req(double time, req_t *req, res_t *res) { char tstr[25]; get_time(tstr); - printf(COLOR_MAGENTA "%s" COLOR_BOLD COLOR_MAGENTA " [ log ] " COLOR_RESET COLOR_CYAN - "(%.0fμs)" COLOR_RESET COLOR_GREEN " %s %s" COLOR_CYAN " => " COLOR_RESET "%d", + printf(COLOR_MAGENTA "%s" COLOR_BOLD COLOR_MAGENTA " LOG " COLOR_RESET COLOR_YELLO "%.0fμs" COLOR_RESET COLOR_CYAN + " %d " COLOR_GREEN "%s %s" COLOR_RESET, tstr, time, + res->code, req_method(req), - req->path, - res->code); + req->path); printf("\n"); } @@ -35,7 +37,7 @@ void info(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_BLUE "%s" COLOR_RESET COLOR_BLUE COLOR_BOLD " [info ] " COLOR_RESET, tstr); + printf(COLOR_BLUE "%s" COLOR_RESET COLOR_BLUE COLOR_BOLD " INFO " COLOR_RESET, tstr); vprintf(msg, args); printf("\n"); @@ -49,7 +51,7 @@ void error(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_RED "%s" COLOR_RESET COLOR_RED COLOR_BOLD " [error] " COLOR_RESET, tstr); + printf(COLOR_RED "%s" COLOR_RESET COLOR_RED COLOR_BOLD " ERROR " COLOR_RESET, tstr); vprintf(msg, args); printf("\n"); @@ -63,15 +65,15 @@ void warn(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_YELLO "%s" COLOR_RESET COLOR_YELLO COLOR_BOLD " [warn ] " COLOR_RESET, tstr); + printf(COLOR_YELLO "%s" COLOR_RESET COLOR_YELLO COLOR_BOLD " WARN " COLOR_RESET, tstr); vprintf(msg, args); printf("\n"); va_end(args); } -void debug(const char *msg, ...) { - if (DEBUG == 0) +void _debug(const char *msg, ...) { + if (CTORM_DEBUG == 0) return; va_list args; @@ -80,7 +82,7 @@ void debug(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_CYAN "%s" COLOR_RESET COLOR_CYAN COLOR_BOLD " [debug] " COLOR_RESET, tstr); + printf(COLOR_CYAN "%s" COLOR_RESET COLOR_CYAN COLOR_BOLD " DEBUG " COLOR_RESET, tstr); vprintf(msg, args); printf("\n"); diff --git a/src/parse.c b/src/parse.c index 95f97db..6a8eeb3 100644 --- a/src/parse.c +++ b/src/parse.c @@ -46,9 +46,7 @@ bool parse_form(table_t *table, char *data) { urldecode(key); urldecode(value); - table_add(table, strdup(key), true); - table_set(table, strdup(value)); - + table_add(table, strdup(key), strdup(value), true); continue; } @@ -69,8 +67,7 @@ bool parse_form(table_t *table, char *data) { urldecode(key); urldecode(value); - table_add(table, strdup(key), true); - table_set(table, strdup(value)); + table_add(table, strdup(key), strdup(value), true); return true; } @@ -81,13 +78,17 @@ parse_ret_t parse_request(req_t *req, int socket) { ssize_t read = -1; parse_ret_t ret = RET_BADREQ; - // we extend the buffer 200 bytes everytime - // this is to prevent using realloc as much as possible + /* + + * we extend the buffer 200 bytes everytime + * this is to prevent using realloc as much as possible + + * also to prevent allocing from the heap, we'll start by + * allocating on the stack - // also to prevent allocing from the heap, we'll start by - // allocating on the stack + */ char _buffer[size]; - char *buffer = _buffer; + char *buffer = _buffer, *header = NULL; // we don't really need to zero the buffer // bzero(buffer, size); @@ -97,8 +98,12 @@ parse_ret_t parse_request(req_t *req, int socket) { switch (state) { case STATE_METHOD_0: - // if method is larger than the max method length - // the its an invalid request + /* + + * if method is larger than the max method length + * the its an invalid request + + */ if (index > http_static.method_max) goto end; @@ -106,23 +111,19 @@ parse_ret_t parse_request(req_t *req, int socket) { if (!is_letter(buffer[index]) && !is_digit(buffer[index]) && buffer[index] != ' ') goto end; - // if the current char is ' ' - // we just finished reading the HTTP method + // if the current char is ' ', we just finished reading the HTTP method if (buffer[index] != ' ') break; - // if the first char is ' ' then its a bad - // invalid request + // if the first char is ' ' then its a bad invalid request if (0 == index) goto end; - // we need the ID, so change the space with - // null terminator + // we need the ID, so change the space with null terminator buffer[index] = 0; req->method = http_method_id(buffer); - // if the method is invalid then its a bad - // request, we should return + // if the method is invalid then its a bad request, we should return if (-1 == req->method) goto end; @@ -133,8 +134,12 @@ parse_ret_t parse_request(req_t *req, int socket) { break; case STATE_PATH_1: - // if buffer is larger than the maximum path - // limit, then its a bad request + /* + + * if buffer is larger than the maximum path limit + * then its a bad request + + */ if (index > http_static.path_max) goto end; @@ -151,13 +156,11 @@ parse_ret_t parse_request(req_t *req, int socket) { if (buffer[index] != ' ') break; - // if the first char is ' ' then its a bad - // invalid request + // if the first char is ' ' then its a bad invalid request if (0 == index) goto end; - // if so, replace the ' ' with null - // terminator for string operation + // if so, replace the ' ' with null terminator for string operation buffer[index] = 0; // allocate enough space for the path, +1 for null terminator @@ -173,8 +176,12 @@ parse_ret_t parse_request(req_t *req, int socket) { break; case STATE_VERSION_2: - // if buffer is larger than the HTTP version - // length, then its a bad request + /* + + * if buffer is larger than the HTTP version length, + * then its a bad request + + */ if (index > http_static.version_len) goto end; @@ -187,13 +194,11 @@ parse_ret_t parse_request(req_t *req, int socket) { if (buffer[index] != '\r' && buffer[index] != '\n') break; - // if its the first char, then the length is 0 - // and its a bad request + // if its the first char, then the length is 0 and its a bad request if (0 == index) goto end; - // replace the '\r' or '\n' with null terminator - // we will restore it later + // replace the '\r' or '\n' with null terminator we will restore it later temp = buffer[index]; buffer[index] = 0; @@ -204,8 +209,7 @@ parse_ret_t parse_request(req_t *req, int socket) { debug("Version: %s", req->version); - // move on to next state, without resetting - // the buffer, see the next section + // move on to next state, without resetting the buffer, see the next section buffer[index] = temp; state++; break; @@ -238,20 +242,27 @@ parse_ret_t parse_request(req_t *req, int socket) { // if its the first option, just move on - // no out-of-bounds here, we checked the index - // in the previous section + // no out-of-bounds here, we checked the index in the previous section if ('\n' == buffer[index] && '\r' == buffer[index - 1]) goto next; - // if its the second option, we just read the request - // and zero headers, so we dont have a body, we are done + /* + + * if its the second option, we just read the request + * and zero headers, so we dont have a body, we are done + + */ if ('\n' == buffer[index] && '\n' == buffer[index - 1]) { ret = RET_OK; goto end; } - // if its the third option, clear the buffer and - // restore the first char of the header + /* + + * if its the third option, clear the buffer and + * restore the first char of the header + + */ temp = buffer[index]; bzero(buffer, size); @@ -262,8 +273,12 @@ parse_ret_t parse_request(req_t *req, int socket) { break; case STATE_NAME_4: - // if instead of the header we got a newline, skip - // the value state and move on to the body + /* + + * if instead of the header we got a newline, + * skip the value state and move on to the body + + */ if (index == 0 && ('\r' == buffer[index] || '\n' == buffer[index])) { buffer[index] = 0; state = STATE_BODY_7; @@ -272,31 +287,37 @@ parse_ret_t parse_request(req_t *req, int socket) { break; } - // if the buffer is larger then the maximum header - // name length, then the request is too large + /* + + * if the buffer is larger then the maximum header + * name length, then the request is too large + + */ if (index > http_static.header_max) { ret = RET_TOOLARGE; goto end; } - // if the header name contains an invalid char, then - // return bad request + // if the header name contains an invalid char, then return bad request if (!contains(valid_header, buffer[index]) && !is_digit(buffer[index]) && !is_letter(buffer[index])) goto end; - // if the the char is ' ', then we probably just read - // the header name + // if the the char is ' ', then we probably just read the header name if (buffer[index] != ' ') break; - // if we are at the start or the previous char is not ':' - // then its a bad header name, so return bad request + /* + + * if we are at the start or the previous char is not ':' + * then its a bad header name, so return bad request + + */ if (0 == index || buffer[index - 1] != ':') goto end; // otherwise add the header to the request and move on buffer[index - 1] = 0; - req_add_header(req, buffer); + header = strdup(buffer); debug("Header: %s", buffer); @@ -304,40 +325,42 @@ parse_ret_t parse_request(req_t *req, int socket) { break; case STATE_VALUE_5: - // if the buffer is larger then the maximum header - // value length, then the request is too large + /* + + * if the buffer is larger then the maximum header + * value length, then the request is too large + + */ if (index > http_static.header_max) { ret = RET_TOOLARGE; goto end; } - // if the header value contains an invalid char, then - // return bad request + // if the header value contains an invalid char, then return bad request if (!contains(valid_header, buffer[index]) && !is_digit(buffer[index]) && !is_letter(buffer[index]) && buffer[index] != '\r' && buffer[index] != '\n') goto end; - // if the the char is '\r' or '\n' we are done reading - // the header value + // if the the char is '\r' or '\n' we are done reading the header value if (buffer[index] != '\r' && buffer[index] != '\n') break; - // if we are at the start, then its a bad request - // yes, empty header value is not allowed + // if we are at the start, then its a bad request, yes, empty header value is not allowed if (0 == index) goto end; - // otherwise set the header value and move on - // this time we will need to restore the char + // otherwise set the header value and move on this time we will need to restore the char char prev = buffer[index]; buffer[index] = 0; - req_add_header_value(req, buffer); debug("Value: %s", buffer); + req_set(req, header, strdup(buffer), false); + // header buffer is now used by the header table, we don't need to free it + header = NULL; buffer[index] = prev; - // why not goto next? well see the next sectipn + // why not goto next? well see the next section state++; break; @@ -368,8 +391,7 @@ parse_ret_t parse_request(req_t *req, int socket) { * */ - // if its the first option, go back to reading the - // header name + // if its the first option, go back to reading the header name if (buffer[index] == '\n' && buffer[index - 1] == '\r') { state = STATE_NAME_4; goto reset; @@ -378,13 +400,16 @@ parse_ret_t parse_request(req_t *req, int socket) { // if its the second option move on to the body - // we can't go out of bounds, index was checked - // before this section + // we can't go out of bounds, index was checked before this section if (buffer[index] == '\n' && buffer[index - 1] == '\n') goto next; - // if its the third option, go back to reading the - // header, but first clear the body and restore the char + /* + + * if its the third option, go back to reading the + * header, but first clear the body and restore the char + + */ temp = buffer[index]; bzero(buffer, size); @@ -402,7 +427,7 @@ parse_ret_t parse_request(req_t *req, int socket) { goto end; // do we have the content length header? - char *contentlen = req_header(req, "content-length"); + char *contentlen = req_get(req, "content-length"); if (NULL == contentlen) goto end; @@ -443,17 +468,17 @@ parse_ret_t parse_request(req_t *req, int socket) { if (index < size) continue; - // realloc if buffer is full - // or malloc we are still on the stack - if (size == BUFFER_SIZE) - buffer = malloc((size *= 2)); - else + // realloc if buffer is full or malloc we are still on the stack + if (size == BUFFER_SIZE) { + char *alloc = malloc((size *= 2)); + memcpy(alloc, buffer, size); + buffer = alloc; + } else buffer = realloc(buffer, (size *= 2)); continue; next: - // clear buffer, reset index, and move - // on to the next state + // clear buffer, reset index, and move on to the next state state++; reset: bzero(buffer, size); @@ -499,5 +524,7 @@ parse_ret_t parse_request(req_t *req, int socket) { // free the buffer and return the result if (size != BUFFER_SIZE) free(buffer); + + free(header); // free the current unused allocated header return ret; } diff --git a/src/pool.c b/src/pool.c index cfea945..5245c09 100644 --- a/src/pool.c +++ b/src/pool.c @@ -1,4 +1,6 @@ #include "../include/pool.h" + +#include #include work_t *pool_work(func_t func, void *arg) { @@ -61,9 +63,11 @@ void *pool_worker(void *arg) { return NULL; } -pool_t *pool_init(int n) { - pool_t *tp = calloc(1, sizeof(pool_t)); - tp->all = n; +pool_t *pool_init(uint64_t n) { + pool_t *tp = calloc(1, sizeof(pool_t)); + pthread_t handle; + + tp->all = n; pthread_mutex_init(&(tp->mutex), NULL); pthread_cond_init(&(tp->work_lock), NULL); @@ -72,8 +76,7 @@ pool_t *pool_init(int n) { tp->first = NULL; tp->last = NULL; - pthread_t handle; - for (int i = 0; i < n; i++) { + for (; n > 0; n--) { pthread_create(&handle, NULL, pool_worker, tp); pthread_detach(handle); } diff --git a/src/req.c b/src/req.c index 5b0531d..c0480f4 100644 --- a/src/req.c +++ b/src/req.c @@ -1,34 +1,35 @@ -#include "../include/req.h" -#include "../include/log.h" #include "../include/parse.h" #include "../include/table.h" + #include "../include/util.h" -#include -#include +#include "../include/log.h" +#include "../include/req.h" + +#include #include #include +#include + void req_init(req_t *req) { - table_init(&req->headers); - table_init(&req->query); + headers_init(&req->headers); + table_init(&req->query, NULL, NULL); req->cancel = false; req->version = NULL; req->encpath = NULL; req->path = NULL; - req->addr = NULL; req->bodysize = 0; req->body = NULL; } void req_free(req_t *req) { - table_free(&req->headers); + headers_free(&req->headers); table_free(&req->query); free(req->body); - free(req->addr); if (req->encpath == req->path) { free(req->encpath); @@ -40,7 +41,12 @@ void req_free(req_t *req) { } char *req_query(req_t *req, char *name) { - return table_get(&req->query, name); + table_node_t *query = table_get(&req->query, name); + + if (NULL == query) + return NULL; + + return query->value; } bool req_body(req_t *req, char *buffer) { @@ -58,67 +64,78 @@ size_t req_body_size(req_t *req) { return req->bodysize + 1; } -form_t *req_form_parse(req_t *req) { +bool req_form_parse(req_t *req, form_t *form) { size_t size = req_body_size(req); if (size == 0) - return NULL; + return false; - char *contentt = req_header(req, "content-type"); + char *contentt = req_get(req, "content-type"); if (!startswith(contentt, "application/x-www-form-urlencoded")) - return NULL; + return false; char data[size]; if (!req_body(req, data)) - return NULL; + return false; - table_t *form = malloc(sizeof(table_t)); - table_init(form); + form_init(form); if (!parse_form(form, data)) { - table_free(form); - free(form); - return NULL; + form_free(form); + return false; } - return form; + return true; } -char *req_form_get(form_t *form, char *key) { - if (NULL == form) +void req_form_free(form_t *form) { + form_free(form); +} + +cJSON *req_json_parse(req_t *req) { + size_t size = req_body_size(req); + if (size == 0) + return NULL; + + char *contentt = req_get(req, "content-type"); + if (!startswith(contentt, "application/json")) return NULL; - return table_get(form, key); + + char data[size]; + if (!req_body(req, data)) + return NULL; + + return cJSON_Parse(data); } -void req_form_free(form_t *form) { - if (NULL == form) - return; - table_free(form); +void req_json_free(cJSON *json) { + if (NULL != json) + cJSON_Delete(json); } char *req_method(req_t *req) { return http_method_name(req->method); } -void req_add_header(req_t *req, char *name) { - table_add(&req->headers, strdup(name), true); -} - -void req_add_header_value(req_t *req, char *value) { - table_set(&req->headers, strdup(value)); +void req_set(req_t *req, char *name, char *value, bool dup) { + if (NULL == name || NULL == value) + errno = BadHeaderPointer; + else + headers_set(&req->headers, name, value, dup); } size_t req_size(req_t *req) { - char *method = req_method(req); + char *method = req_method(req); + header_t cur; size_t size = strlen(method) + 1; // "GET " size += strlen(req->encpath) + 1; // "/ " size += strlen(req->version) + 1; // "HTTP/1.1\n" - char **cur = table_next(&req->headers, NULL); - while (cur) { - size += strlen(cur[0]) + 2; // "User-Agent: " - size += strlen(cur[1]) + 1; // "curl\n" - cur = table_next(&req->headers, cur); + headers_start(&cur); + + while (headers_next(&req->headers, &cur)) { + size += strlen(cur.key) + 2; // "User-Agent: " + size += strlen(cur.value) + 1; // "curl\n" } size += 1; // "\n" @@ -126,58 +143,20 @@ size_t req_size(req_t *req) { } void req_tostr(req_t *req, char *str) { - char *method = req_method(req); - char **cur = table_next(&req->headers, NULL); - size_t index = 0; + char *method = req_method(req); + size_t index = 0; + header_t cur; index += sprintf(str + index, "%s %s %s\n", method, req->encpath, req->version); - while (cur) { - index += sprintf(str + index, "%s: %s\n", cur[0], cur[1]); - cur = table_next(&req->headers, cur); - } - - sprintf(str + index, "\n"); -} - -char *req_header(req_t *req, char *name) { - size_t len = strlen(name); - char low[len + 1]; - stolower(name, low); - - char **cur = table_next(&req->headers, NULL); - while (cur) { - stolower(cur[0], cur[0]); + headers_start(&cur); - if (eq(low, cur[0])) { - char *ret = cur[1]; - free(cur); - return ret; - } - - cur = table_next(&req->headers, cur); - } - - return NULL; -} + while (headers_next(&req->headers, &cur)) + index += sprintf(str + index, "%s: %s\n", cur.key, cur.value); -cJSON *req_json_parse(req_t *req) { - size_t size = req_body_size(req); - if (size == 0) - return NULL; - - char *contentt = req_header(req, "content-type"); - if (!startswith(contentt, "application/json")) - return NULL; - - char data[size]; - if (!req_body(req, data)) - return NULL; - - return cJSON_Parse(data); + sprintf(str + index, "\n"); } -void req_json_free(cJSON *json) { - if (NULL != json) - cJSON_Delete(json); +char *req_get(req_t *req, char *name) { + return headers_get(&req->headers, name); } diff --git a/src/res.c b/src/res.c index 36c5c7a..9941556 100644 --- a/src/res.c +++ b/src/res.c @@ -1,31 +1,28 @@ -#include "../include/res.h" #include "../include/errors.h" +#include "../include/log.h" #include "../include/util.h" +#include "../include/res.h" -#include -#include #include -#include #include #include + +#include +#include + #include void res_init(res_t *res) { - table_init(&res->headers); + headers_init(&res->headers); res->version = NULL; res->bodysize = 0; res->body = NULL; res->code = 200; - table_add(&res->headers, "Server", false); - table_set(&res->headers, "ctorm"); - - table_add(&res->headers, "Connection", false); - table_set(&res->headers, "close"); - - table_add(&res->headers, "Content-Length", false); - table_set(&res->headers, "0"); + headers_add(&res->headers, "Server", "ctorm", false); + headers_add(&res->headers, "Connection", "close", false); + headers_add(&res->headers, "Content-Length", "0", false); struct tm *gmt; time_t raw; @@ -40,22 +37,15 @@ void res_init(res_t *res) { } void res_free(res_t *res) { - table_free(&res->headers); + headers_free(&res->headers); free(res->body); } void res_set(res_t *res, char *name, char *value) { - if (NULL == name || NULL == value) { + if (NULL == name || NULL == value) errno = BadHeaderPointer; - return; - } - - char *valuecp = strdup(value); - if (table_update(&res->headers, name, valuecp)) - return; - - table_add(&res->headers, strdup(name), true); - table_set(&res->headers, valuecp); + else + headers_set(&res->headers, name, value, true); } void res_del(res_t *res, char *name) { @@ -64,7 +54,7 @@ void res_del(res_t *res, char *name) { return; } - table_del(&res->headers, name); + headers_del(&res->headers, name); } void res_update_size(res_t *res) { @@ -142,15 +132,17 @@ bool res_sendfile(res_t *res, char *path) { } size_t res_size(res_t *res) { - size_t size = 0; + size_t size = 0; + header_t cur; + size += http_static.version_len + 1; // "HTTP/1.1 " size += 5; // "200\r\n" - char **cur = table_next(&res->headers, NULL); - while (cur) { - size += strlen(cur[0]) + 2; // "User-Agent: " - size += strlen(cur[1]) + 2; // "curl\r\n" - cur = table_next(&res->headers, cur); + headers_start(&cur); + + while (headers_next(&res->headers, &cur)) { + size += strlen(cur.key) + 2; // "User-Agent: " + size += strlen(cur.value) + 2; // "curl\r\n" } size += 2; // "\r\n" @@ -161,8 +153,8 @@ size_t res_size(res_t *res) { } void res_tostr(res_t *res, char *str) { - char **cur = table_next(&res->headers, NULL); - size_t index = 0; + size_t index = 0; + header_t cur; // fix the HTTP code if its invalid if (res->code > 999 || res->code < 100) @@ -173,12 +165,13 @@ void res_tostr(res_t *res, char *str) { else index += sprintf(str, "%s %d\r\n", res->version, res->code); - while (cur) { - index += sprintf(str + index, "%s: %s\r\n", cur[0], cur[1]); - cur = table_next(&res->headers, cur); - } + headers_start(&cur); + + while (headers_next(&res->headers, &cur)) + index += sprintf(str + index, "%s: %s\r\n", cur.key, cur.value); index += sprintf(str + index, "\r\n"); + if (res->bodysize > 0) memcpy(str + index, res->body, res->bodysize); } diff --git a/src/socket.c b/src/socket.c index 83e632a..f176e46 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,55 +1,73 @@ #include "../include/socket.h" #include "../include/errors.h" -#include "../include/log.h" + +#include "../include/options.h" + #include "../include/parse.h" #include "../include/pool.h" + +#include "../include/log.h" #include "../include/req.h" #include "../include/res.h" #include -#include -#include -#include -#include #include +#include + #include -#include + +#include #include #include -#include -#include #include +#include +#include +#include + +#include + +pthread_mutex_t socket_mutex; +req_t req; +res_t res; + void socket_handle(socket_args_t *_args) { socket_args_t *args = (socket_args_t *)_args; debug("(Socket %d) Created thread", args->socket); - struct sockaddr *addr = args->address; - evutil_socket_t socket = args->socket; + struct sockaddr *addr = &args->addr; + int socket = args->socket; app_t *app = args->app; size_t buffer_size; + parse_ret_t ret = 0; + clock_t time = 0; - req_t req; - req_init(&req); + pthread_mutex_lock(&socket_mutex); - res_t res; + req_init(&req); res_init(&res); // set the request address - if (addr->sa_family == AF_INET) { - req.addr = malloc(INET_ADDRSTRLEN); + switch (addr->sa_family) { + case AF_INET: inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, req.addr, INET_ADDRSTRLEN); - } else if (addr->sa_family == AF_INET6) { - req.addr = malloc(INET6_ADDRSTRLEN); + break; + + case AF_INET6: inet_ntop(AF_INET6, &((struct sockaddr_in *)addr)->sin_addr, req.addr, INET6_ADDRSTRLEN); + break; } - // measure the time that takes - // to complete the request - clock_t time = clock(); - parse_ret_t ret = parse_request(&req, socket); + /* + + * if request logging is enabled + * measure the time that takes to complete the request - switch (ret) { + */ + if (!app->config->disable_logging) + time = clock(); + + switch ((ret = parse_request(&req, socket))) { case RET_BADREQ: debug("(Socket %d) Received a bad request", args->socket); res.code = 400; @@ -66,8 +84,11 @@ void socket_handle(socket_args_t *_args) { res_free(&res); req_free(&req); + pthread_mutex_unlock(&socket_mutex); + close(socket); free(args); + return; default: @@ -75,7 +96,7 @@ void socket_handle(socket_args_t *_args) { break; } - if (DEBUG) { + if (CTORM_DEBUG) { buffer_size = req_size(&req); char buffer[buffer_size]; req_tostr(&req, buffer); @@ -83,17 +104,12 @@ void socket_handle(socket_args_t *_args) { } res.version = req.version; + if (!app->config->server_header) res_del(&res, "Server"); - if (app->config->lock_request) - pthread_mutex_lock(&app->request_mutex); - app_route(app, &req, &res); - if (app->config->lock_request) - pthread_mutex_unlock(&app->request_mutex); - close: buffer_size = res_size(&res); char buffer[buffer_size]; @@ -101,35 +117,42 @@ void socket_handle(socket_args_t *_args) { res_tostr(&res, buffer); send(socket, buffer, buffer_size, 0); - // finish the measurement and print out - // the result - if (RET_OK == ret) { - time = (clock() - time) * 1000000; - double passed = (((double)time) / CLOCKS_PER_SEC); - if (!app->config->disable_logging) - log_req(passed, &req, &res); + // finish the measurement and print out the result + if (!app->config->disable_logging && RET_OK == ret) { + time = (clock() - time) * 1000000; + log_req((((double)time) / CLOCKS_PER_SEC), &req, &res); } req_free(&req); res_free(&res); + pthread_mutex_unlock(&socket_mutex); + free(args); close(socket); } -bool socket_set_opts(app_t *app, int socket) { +bool socket_set_opts(app_t *app, int sockfd) { struct timeval timeout; bzero(&timeout, sizeof(timeout)); int flag = 1; - // 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) + /* + + * 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(sockfd, 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) + /* + + * 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(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) < 0) return false; // set the socket timeout @@ -137,56 +160,97 @@ bool socket_set_opts(app_t *app, int socket) { timeout.tv_sec = app->config->tcp_timeout; timeout.tv_usec = 0; - setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); } // make the socket blocking - fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) & (~O_NONBLOCK)); + fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & (~O_NONBLOCK)); return true; } -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->address = address; - args->socket = fd; - args->app = ctx; +bool socket_start(app_t *app, char *addr, uint16_t port) { + int sockfd = -1, clientfd = -1, flag = 1; + struct addrinfo *info = NULL, *cur = NULL; + struct sockaddr saddr, caddr; + socklen_t caddr_len = sizeof(caddr); + socket_args_t *args = NULL; + bool ret = false; - if (!socket_set_opts(args->app, args->socket)) - error("Failed to setsockopt for %d: %s", socket, strerror(errno)); - pool_add(args->app->pool, (void *)socket_handle, (void *)args); -} + bzero(&saddr, sizeof(saddr)); -void socket_err(struct evconnlistener *listener, void *ctx) { - struct event_base *base = evconnlistener_get_base(listener); - int err = EVUTIL_SOCKET_ERROR(); - error("Listener error: %s", evutil_socket_error_to_string(err)); - event_base_loopexit(base, NULL); -} + if (0 == port) + goto end; -bool socket_start(app_t *app, char *addr, int port) { - struct sockaddr_in address; - bool ret = false; - - bzero(&address, sizeof(address)); - inet_pton(AF_INET, addr, &(address.sin_addr)); - address.sin_family = AF_INET; - address.sin_port = htons(port); - - struct evconnlistener *listener = evconnlistener_new_bind(app->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; + if (getaddrinfo(addr, NULL, NULL, &info) != 0 || NULL == info) + goto end; + + for (cur = info; cur != NULL; cur = cur->ai_next) { + switch (cur->ai_family) { + case AF_INET: + ((struct sockaddr_in *)cur->ai_addr)->sin_port = htons(port); + goto found_addr; + + case AF_INET6: + ((struct sockaddr_in6 *)cur->ai_addr)->sin6_port = htons(port); + goto found_addr; + } } - evconnlistener_set_error_cb(listener, socket_err); - event_base_dispatch(app->base); - evconnlistener_free(listener); - return true; + debug("Failed to resolve the address"); + goto end; + +found_addr: + memcpy(&saddr, cur->ai_addr, sizeof(saddr)); + + if ((sockfd = socket(saddr.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0) { + debug("Failed to create socket: %s", strerror(errno)); + goto end; + } + + // prevent EADDRINUSE + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) < 0) { + debug("Failed to set the REUSEADDR: %s", strerror(errno)); + goto end; + } + + if (bind(sockfd, &saddr, sizeof(saddr)) != 0) { + debug("Failed to bind the socket: %s", strerror(errno)); + goto end; + } + + if (listen(sockfd, app->config->max_connections) != 0) { + debug("Failed to listen socket: %s", strerror(errno)); + goto end; + } + + pthread_mutex_init(&socket_mutex, NULL); + + while (app->running && (clientfd = accept(sockfd, &caddr, &caddr_len)) > 0) { + debug("Accepted a new connection (socket %d)", clientfd); + + if (!socket_set_opts(app, clientfd)) { + error("Failed to setsockopt for %d: %s", clientfd, strerror(errno)); + break; + } + + args = malloc(sizeof(socket_args_t)); + memcpy(&args->addr, &caddr, sizeof(caddr)); + args->socket = clientfd; + args->app = app; + + pool_add(app->pool, (void *)socket_handle, (void *)args); + } + + pthread_mutex_destroy(&socket_mutex); + ret = true; + +end: + if (NULL != info) + freeaddrinfo(info); + + if (sockfd > 0) + close(sockfd); + + free(args); + return ret; } diff --git a/src/table.c b/src/table.c index 6f318cb..8dc1e0f 100644 --- a/src/table.c +++ b/src/table.c @@ -1,141 +1,140 @@ -#include -#include -#include - #include "../include/errors.h" #include "../include/table.h" #include "../include/util.h" +#include "../include/log.h" + +#include +#include + +#include +#include + +uint64_t __table_basic_hasher(const char *data) { + uint64_t sum = 0; + + for (; *data != 0; data++) + sum += *data; -void table_init(table_t *t) { - t->head = NULL; - t->tail = NULL; + return sum; } -bool table_add(table_t *t, char *key, bool alloced) { - table_node_t *new = malloc(sizeof(table_node_t)); - if (NULL == new) { +void table_init(table_t *t, table_cmp_t *comparer, table_hash_t *hasher) { + bzero(t, sizeof(table_t)); + t->comparer = comparer == NULL ? strcmp : comparer; + t->hasher = hasher == NULL ? __table_basic_hasher : hasher; +} + +#define table_hash(d) (t->hasher(d) % TABLE_SIZE) +#define table_list(k) (&(t->lists[table_hash(k)])) +#define table_cmp(k1, k2) (t->comparer(k1, k2) == 0) + +bool table_add(table_t *t, char *key, char *value, bool alloced) { + table_node_t *new = NULL, *cur = NULL, **head = NULL; + + if (NULL == (new = malloc(sizeof(table_node_t)))) { errno = AllocFailed; return false; } new->alloced = alloced; + new->value = value; new->key = key; new->next = NULL; - new->value = NULL; - if (NULL == t->head) { - t->head = new; - t->tail = new; + if (NULL == (cur = *(head = table_list(key)))) { + *head = new; return true; } - t->tail->next = new; - t->tail = new; - return true; -} + while (NULL != cur->next) + cur = cur->next; -bool table_set(table_t *t, char *value) { - if (NULL == t->tail) - return false; + cur->next = new; - t->tail->value = value; return true; } -char *table_get(table_t *t, char *key) { - table_node_t *cur = t->head; - while (cur) { - if (eq(cur->key, key)) - return cur->value; - cur = cur->next; +table_node_t *table_get(table_t *t, char *key) { + table_node_t **head = NULL, *cur = NULL; + head = table_list(key); + + for (cur = *head; NULL != cur; cur = cur->next) { + if (table_cmp(key, cur->key)) + return cur; } + return NULL; } -bool table_update(table_t *t, char *key, char *value) { - table_node_t *cur = t->head; - while (cur) { - if (!eq(cur->key, key)) { - cur = cur->next; - continue; - } - - if (cur->alloced) - free(cur->value); +bool table_next(table_t *t, table_entry_t *cur) { + if (NULL == cur || NULL == t) + return false; - cur->value = value; - return true; +next_node: + if (cur->_indx >= TABLE_SIZE) { + return false; } - return false; -} - -char **table_next(table_t *t, char **prev) { - if (NULL == t->head) - return NULL; + if (NULL == cur->_node) + cur->_node = t->lists[cur->_indx]; + else + cur->_node = cur->_node->next; - char **ret = malloc(sizeof(char *) * 2); - if (NULL == prev) { - ret[0] = t->head->key; - ret[1] = t->head->value; - return ret; + if (NULL == cur->_node) { + cur->_indx++; + goto next_node; } - table_node_t *cur = t->head; - while (cur) { - if (!eq(cur->key, prev[0])) { - cur = cur->next; - continue; - } - - if (NULL == cur->next) { - free(prev); - return NULL; - } - - prev[0] = cur->next->key; - prev[1] = cur->next->value; - return prev; - } + cur->key = cur->_node->key; + cur->value = cur->_node->value; - free(prev); - return NULL; + return true; } -void table_free_node(table_node_t *n) { +void __table_single_node_free(table_node_t *n) { if (n->alloced) { free(n->key); free(n->value); } + free(n); } -void table_free(table_t *t) { - table_node_t *cur = t->head, *prev; +void __table_node_free(table_node_t *node) { + table_node_t *pre = NULL; - while (cur) { - prev = cur; - cur = cur->next; - table_free_node(prev); + while (node != NULL) { + pre = node; + node = node->next; + + __table_single_node_free(pre); } } -bool table_del(table_t *t, char *key) { - table_node_t *cur = t->head, *prev = NULL; - while (cur) { - if (!eq(cur->key, key)) { - prev = cur; - cur = cur->next; - continue; - } - - if (NULL == prev) - t->head = cur->next; - else - prev->next = cur->next; - - table_free_node(cur); - return true; +void table_free(table_t *t) { + for (uint8_t i = 0; i < TABLE_SIZE; i++) + __table_node_free(t->lists[i]); +} + +void table_del(table_t *t, char *key) { + table_node_t **head = NULL, *cur = NULL, *pre = NULL; + head = table_list(key); + + if (NULL == (cur = *head)) + return; + + while (cur != NULL) { + if (table_cmp(key, cur->key)) + break; + + pre = cur; + cur = cur->next; } - return false; + + if (NULL == pre) + *head = cur->next; + else + pre->next = cur->next; + + __table_single_node_free(cur); } diff --git a/src/util.c b/src/util.c index 32f0697..fcf0b4f 100644 --- a/src/util.c +++ b/src/util.c @@ -10,16 +10,6 @@ #include #include -bool eq(char *s1, char *s2) { - if (NULL == s1 || NULL == s2) - return false; - - if (strlen(s1) != strlen(s2)) - return false; - - return strcmp(s1, s2) == 0; -} - bool startswith(char *str, char *pre) { if (NULL == str || NULL == pre) return false; From df4f0a2494868e2a2c19751ef3a3d6d6bf9b855e Mon Sep 17 00:00:00 2001 From: ngn Date: Mon, 25 Nov 2024 02:10:58 +0300 Subject: [PATCH 2/5] add CTORM_JSON_SUPPORT compile option --- Makefile | 10 ++++++++-- example/echo/main.c | 2 +- example/hello/main.c | 2 +- example/middleware/main.c | 8 ++++++-- include/errors.h | 1 + include/options.h | 4 ++++ include/req.h | 4 ++++ include/res.h | 4 ++++ src/errors.c | 1 + src/req.c | 14 +++++++++++--- src/res.c | 7 ++++++- 11 files changed, 47 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 7ee3ee4..21ad44b 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,15 @@ HDRS = $(wildcard include/*.h) # compiler flags CFLAGS = -O3 -march=native -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -LIBS = -lpthread -lcjson +LIBS = -lpthread # options CTORM_DEBUG = 0 +CTORM_JSON_SUPPORT = 1 + +ifeq ($(CTORM_JSON_SUPPORT), 1) + LIBS += -lcjson +endif dist/libctorm.so: $(OBJS) mkdir -p dist @@ -20,7 +25,8 @@ dist/libctorm.so: $(OBJS) dist/%.o: src/%.c mkdir -p dist $(CC) -c -Wall -fPIC -o $@ $^ $(LIBS) $(CFLAGS) \ - -DCTORM_DEBUG=$(CTORM_DEBUG) + -DCTORM_DEBUG=$(CTORM_DEBUG) \ + -DCTORM_JSON_SUPPORT=$(CTORM_JSON_SUPPORT) install: install -m755 dist/libctorm.so $(DESTDIR)$(PREFIX)/lib/libctorm.so diff --git a/example/echo/main.c b/example/echo/main.c index 056d08e..fa0f5c7 100644 --- a/example/echo/main.c +++ b/example/echo/main.c @@ -54,7 +54,7 @@ int main() { // run the app if (!app_run(app, "0.0.0.0:8080")) - error("failed to start the app: %s", app_geterror()); + error("Failed to start the app: %s", app_geterror()); // clean up app_free(app); diff --git a/example/hello/main.c b/example/hello/main.c index 5bdca13..94cd95c 100644 --- a/example/hello/main.c +++ b/example/hello/main.c @@ -20,7 +20,7 @@ int main() { // run the app if (!app_run(app, "0.0.0.0:8080")) - error("app failed: %s", app_geterror()); + error("Failed to start the app: %s", app_geterror()); // clean up app_free(app); diff --git a/example/middleware/main.c b/example/middleware/main.c index aa1a6bc..2faa043 100644 --- a/example/middleware/main.c +++ b/example/middleware/main.c @@ -49,7 +49,11 @@ void user_list(req_t *req, res_t *res) { cur = cur->next; } - RES_JSON(json); + if (!RES_JSON(json)) { + error("Failed to send the JSON data: %s", app_geterror()); + res->code = 500; + RES_SEND("Failed to send the JSON"); + } } void user_delete(req_t *req, res_t *res) { @@ -136,7 +140,7 @@ int main() { GET(app, "/users", user_list); if (!app_run(app, "0.0.0.0:8080")) - error("app failed: %s", app_geterror()); + error("Failed to start the app: %s", app_geterror()); app_free(app); } diff --git a/include/errors.h b/include/errors.h index d49f10b..b639327 100644 --- a/include/errors.h +++ b/include/errors.h @@ -23,6 +23,7 @@ typedef enum app_error_t { BadDataPointer = 9927, BadHeaderPointer = 9928, BadMaxConnCount = 9929, + NoJSONSupport = 9930, } app_error_t; struct app_error_desc_t { diff --git a/include/options.h b/include/options.h index b41d039..0d2bf52 100644 --- a/include/options.h +++ b/include/options.h @@ -11,3 +11,7 @@ #ifndef CTORM_DEBUG #define CTORM_DEBUG 0 #endif + +#ifndef CTORM_JSON_SUPPORT +#define CTORM_JSON_SUPPORT 1 +#endif diff --git a/include/req.h b/include/req.h index 7c4de19..570bf56 100644 --- a/include/req.h +++ b/include/req.h @@ -1,7 +1,11 @@ #pragma once #include +#if __has_include() #include +#else +typedef void cJSON; +#endif #include "http.h" #include "table.h" diff --git a/include/res.h b/include/res.h index 6141299..71f8fdd 100644 --- a/include/res.h +++ b/include/res.h @@ -1,6 +1,10 @@ #pragma once +#if __has_include() #include +#else +typedef void cJSON; +#endif #include "headers.h" #include "http.h" diff --git a/src/errors.c b/src/errors.c index d9923af..eb16e0d 100644 --- a/src/errors.c +++ b/src/errors.c @@ -28,6 +28,7 @@ struct app_error_desc_t descs[] = { {.code = BadDataPointer, .desc = "invalid data pointer" }, {.code = BadHeaderPointer, .desc = "invalid header name/value pointer" }, {.code = BadMaxConnCount, .desc = "invalid max connection count" }, + {.code = NoJSONSupport, .desc = "library not compiled with JSON support" }, }; char *app_geterror_code(app_error_t code) { diff --git a/src/req.c b/src/req.c index c0480f4..0de2b27 100644 --- a/src/req.c +++ b/src/req.c @@ -1,9 +1,7 @@ +#include "../include/options.h" #include "../include/parse.h" #include "../include/table.h" - #include "../include/util.h" - -#include "../include/log.h" #include "../include/req.h" #include @@ -92,6 +90,7 @@ void req_form_free(form_t *form) { } cJSON *req_json_parse(req_t *req) { +#if CTORM_JSON_SUPPORT size_t size = req_body_size(req); if (size == 0) return NULL; @@ -105,11 +104,20 @@ cJSON *req_json_parse(req_t *req) { return NULL; return cJSON_Parse(data); +#else + errno = NoJSONSupport; + return NULL; +#endif } void req_json_free(cJSON *json) { +#if CTORM_JSON_SUPPORT if (NULL != json) cJSON_Delete(json); +#else + errno = NoJSONSupport; + return; +#endif } char *req_method(req_t *req) { diff --git a/src/res.c b/src/res.c index 9941556..1b931a9 100644 --- a/src/res.c +++ b/src/res.c @@ -1,5 +1,5 @@ +#include "../include/options.h" #include "../include/errors.h" -#include "../include/log.h" #include "../include/util.h" #include "../include/res.h" @@ -237,6 +237,7 @@ bool res_add(res_t *res, const char *fmt, ...) { } bool res_json(res_t *res, cJSON *json) { +#if CTORM_JSON_SUPPORT if (NULL == json) { errno = BadJsonPointer; return false; @@ -255,6 +256,10 @@ bool res_json(res_t *res, cJSON *json) { cJSON_Delete(json); return true; +#else + errno = NoJSONSupport; + return false; +#endif } void res_redirect(res_t *res, char *url) { From a094c06bdb0f038418981272273cf6dddee8141e Mon Sep 17 00:00:00 2001 From: ngn Date: Fri, 29 Nov 2024 02:31:56 +0300 Subject: [PATCH 3/5] performance improvements and cleanup --- .gitignore | 1 + Makefile | 15 +- README.md | 19 +- benchmark/.gitignore | 5 - benchmark/README.md | 16 - benchmark/crow/main.cpp | 18 - benchmark/crow/test.sh | 17 - benchmark/ctorm/main.c | 29 - benchmark/ctorm/test.sh | 10 - benchmark/express/index.js | 12 - benchmark/express/package-lock.json | 754 --------- benchmark/express/package.json | 5 - benchmark/express/test.sh | 10 - benchmark/fiber/go.mod | 17 - benchmark/fiber/go.sum | 25 - benchmark/fiber/main.go | 16 - benchmark/fiber/test.sh | 10 - benchmark/tide/.gitignore | 1 - benchmark/tide/Cargo.lock | 2308 --------------------------- benchmark/tide/Cargo.toml | 9 - benchmark/tide/src/main.rs | 19 - benchmark/tide/test.sh | 10 - example/echo/main.c | 14 +- example/hello/main.c | 3 + example/middleware/main.c | 7 +- include/all.h | 6 +- include/config.h | 19 + include/connection.h | 23 + include/ctorm.h | 32 +- include/encoding.h | 23 + include/errors.h | 52 +- include/form.h | 10 - include/headers.h | 39 +- include/http.h | 33 +- include/log.h | 18 +- include/parse.h | 25 - include/req.h | 64 +- include/res.h | 53 +- include/socket.h | 10 +- include/table.h | 39 - include/util.h | 11 +- scripts/benchmark.sh | 3 + scripts/echo_test.sh | 12 + scripts/hello_test.sh | 16 + scripts/middleware_test.sh | 31 + scripts/pressure.py | 62 + src/config.c | 15 + src/connection.c | 97 ++ src/ctorm.c | 22 +- src/encoding/json.c | 33 + src/encoding/url.c | 109 ++ src/errors.c | 52 +- src/form.c | 9 - src/headers.c | 142 +- src/http.c | 7 +- src/log.c | 12 +- src/parse.c | 530 ------ src/pool.c | 40 +- src/req.c | 400 ++++- src/res.c | 192 +-- src/socket.c | 160 +- src/table.c | 140 -- src/util.c | 22 +- 63 files changed, 1277 insertions(+), 4636 deletions(-) delete mode 100644 benchmark/.gitignore delete mode 100644 benchmark/README.md delete mode 100644 benchmark/crow/main.cpp delete mode 100755 benchmark/crow/test.sh delete mode 100644 benchmark/ctorm/main.c delete mode 100755 benchmark/ctorm/test.sh delete mode 100644 benchmark/express/index.js delete mode 100644 benchmark/express/package-lock.json delete mode 100644 benchmark/express/package.json delete mode 100755 benchmark/express/test.sh delete mode 100644 benchmark/fiber/go.mod delete mode 100644 benchmark/fiber/go.sum delete mode 100644 benchmark/fiber/main.go delete mode 100755 benchmark/fiber/test.sh delete mode 100644 benchmark/tide/.gitignore delete mode 100644 benchmark/tide/Cargo.lock delete mode 100644 benchmark/tide/Cargo.toml delete mode 100644 benchmark/tide/src/main.rs delete mode 100755 benchmark/tide/test.sh create mode 100644 include/config.h create mode 100644 include/connection.h create mode 100644 include/encoding.h delete mode 100644 include/form.h delete mode 100644 include/parse.h delete mode 100644 include/table.h create mode 100755 scripts/benchmark.sh create mode 100755 scripts/echo_test.sh create mode 100755 scripts/hello_test.sh create mode 100755 scripts/middleware_test.sh create mode 100644 scripts/pressure.py create mode 100644 src/config.c create mode 100644 src/connection.c create mode 100644 src/encoding/json.c create mode 100644 src/encoding/url.c delete mode 100644 src/form.c delete mode 100644 src/parse.c delete mode 100644 src/table.c diff --git a/.gitignore b/.gitignore index 849ddff..3facbcb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +# dist directory for examples and the library dist/ diff --git a/Makefile b/Makefile index 21ad44b..95d2b13 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PREFIX = /usr CC = gcc # sources -SRCS = $(wildcard src/*.c) +SRCS = $(shell find src/ -type f -name '*.c') OBJS = $(patsubst src/%.c,dist/%.o,$(SRCS)) HDRS = $(wildcard include/*.h) @@ -18,15 +18,18 @@ ifeq ($(CTORM_JSON_SUPPORT), 1) LIBS += -lcjson endif +all: dist dist/libctorm.so + +dist: + mkdir -pv dist/encoding + dist/libctorm.so: $(OBJS) - mkdir -p dist $(CC) -shared -o $@ $^ $(LIBS) $(CFLAGS) dist/%.o: src/%.c - mkdir -p dist $(CC) -c -Wall -fPIC -o $@ $^ $(LIBS) $(CFLAGS) \ - -DCTORM_DEBUG=$(CTORM_DEBUG) \ - -DCTORM_JSON_SUPPORT=$(CTORM_JSON_SUPPORT) + -DCTORM_JSON_SUPPORT=$(CTORM_JSON_SUPPORT) \ + -DCTORM_DEBUG=$(CTORM_DEBUG) install: install -m755 dist/libctorm.so $(DESTDIR)$(PREFIX)/lib/libctorm.so @@ -38,7 +41,7 @@ uninstall: rm -r $(DESTDIR)/usr/include/ctorm format: - clang-format -i -style=file src/*.c include/*.h example/*/*.c + clang-format -i -style=file $(SRCS) $(HDRS) example/*/*.c clean: rm -rf dist diff --git a/README.md b/README.md index aa06cba..cc83ba6 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ ctorm is a multi-threaded HTTP server for `HTTP/1.1` and `HTTP/1.0`. It has an easy API for general web server applications. -### Important!!! -This software is pretty much in alpha state. I don't suggest you use ctorm on -production, however it can be used to build simple web applications just for fun. +> [!WARNING] +> This software is pretty much in alpha state. I don't suggest you use ctorm on +> production, however it can be used to build simple web applications just for fun. I do plan to continue the development of this project, so please consider contributing if you are interested. @@ -31,21 +31,10 @@ if you are interested. - Handling 404 (all) routes - Sending files and static file serving -### Benchmarking -Benchmark results for hello world applications (see [benchmark](benchmark/)): - -| Framework | Version | Time per request | -| ---------------- | ------------- | ---------------- | -| crow (C++) | v1.2.0 | ~4 ms | -| fiber (Go) | v3.0.0-beta.1 | ~4 ms | -| **ctorm (C)** | **1.5** | **~4.5 ms** | -| tide (Rust) | 0.16.0 | ~12 ms | -| express (NodeJS) | 4.19.2 | ~21 ms | - ### Installation You will need the following software in order to build and install ctorm: - GCC and other general build tools (`build-essential`) -- cJSON and it's headers (`cjson`, `libcjson-dev`) +- If you want JSON support, cJSON and it's headers (`cjson`, `libcjson-dev`) - tar (to extract the release archive) First [download the latest release](https://github.com/ngn13/ctorm/tags) archive, diff --git a/benchmark/.gitignore b/benchmark/.gitignore deleted file mode 100644 index 9d0a09e..0000000 --- a/benchmark/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -express/node_modules -fiber/fiber -ctorm/out -crow/crow_all.h -crow/out diff --git a/benchmark/README.md b/benchmark/README.md deleted file mode 100644 index baa36eb..0000000 --- a/benchmark/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# ctorm | benchmarking -> [!WARNING] -> I am not a benchmark expert, and I am pretty sure that this is a pretty bad way -> to do benchmarking, results may not be the most accurate. - -### Requirements -- Install [Apache Benchmark](https://httpd.apache.org/docs/2.4/programs/ab.html) -- Install [Crow](https://crowcpp.org/master/) -- Install [NodeJS](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) -- Install [Go](https://go.dev/) -- Install [Rust tookit](https://www.rust-lang.org/) -- Install ctorm - -### Running the benchmark -Every directory contains a `test.sh`, which you can run to build, run and view -benchmark results. diff --git a/benchmark/crow/main.cpp b/benchmark/crow/main.cpp deleted file mode 100644 index 2bf3734..0000000 --- a/benchmark/crow/main.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "crow_all.h" - -int main(){ - // create a simple app - crow::SimpleApp app; - - // disable logging - app.loglevel(crow::LogLevel::Warning); - - // setup the hello world route - CROW_ROUTE(app, "/")([](const crow::request&, crow::response& res){ - res.write("hello world!"); - return res.end(); - }); - - // start the app - app.port(8080).run(); -} diff --git a/benchmark/crow/test.sh b/benchmark/crow/test.sh deleted file mode 100755 index edc848d..0000000 --- a/benchmark/crow/test.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -e - -header_url="https://github.com/CrowCpp/Crow/releases/download/v1.2.0/crow_all.h" -header="crow_all.h" - -if [ ! -f "$header" ]; then - wget "$header_url" -O "$header" -fi - -g++ -O3 -o out *.cpp -./out & fpid=$! -sleep 3 - -ab -t 10 -n 10000 -c 100 127.0.0.1:8080/ - -sleep 3 -kill -9 $fpid diff --git a/benchmark/ctorm/main.c b/benchmark/ctorm/main.c deleted file mode 100644 index 60f58cd..0000000 --- a/benchmark/ctorm/main.c +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include - -void hello_world(req_t* req, res_t* res){ - // send the "hello world!" message - RES_SEND("hello world!"); -} - -int main(){ - // create a new config - app_config_t config; - app_config_new(&config); - - // disable logging for better benchmarking - config.disable_logging = true; - - // create new app - app_t *app = app_new(&config); - - // setup the hello_world route - GET(app, "/", hello_world); - - // start the app - app_run(app, "127.0.0.1:8080"); - - // cleanup when done - app_free(app); -} diff --git a/benchmark/ctorm/test.sh b/benchmark/ctorm/test.sh deleted file mode 100755 index 4460a5f..0000000 --- a/benchmark/ctorm/test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -gcc -O3 -o out -lctorm *.c -./out & fpid=$! -sleep 3 - -ab -t 10 -n 10000 -c 100 127.0.0.1:8080/ - -sleep 3 -kill -9 $fpid diff --git a/benchmark/express/index.js b/benchmark/express/index.js deleted file mode 100644 index f2bc63e..0000000 --- a/benchmark/express/index.js +++ /dev/null @@ -1,12 +0,0 @@ -// import express -const express = require("express") -// create the app -const app = express() - -// setup the "hello world!" route -app.get("/", (_, res) => { - res.send("hello world!") -}) - -// start the app -app.listen(8080) diff --git a/benchmark/express/package-lock.json b/benchmark/express/package-lock.json deleted file mode 100644 index 7a5e8c3..0000000 --- a/benchmark/express/package-lock.json +++ /dev/null @@ -1,754 +0,0 @@ -{ - "name": "express", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "express": "^4.19.2" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/benchmark/express/package.json b/benchmark/express/package.json deleted file mode 100644 index 4c70953..0000000 --- a/benchmark/express/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "express": "^4.19.2" - } -} diff --git a/benchmark/express/test.sh b/benchmark/express/test.sh deleted file mode 100755 index 9632e80..0000000 --- a/benchmark/express/test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -npm install -node index.js & fpid=$! -sleep 3 - -ab -t 10 -n 10000 -c 100 127.0.0.1:8080/ - -sleep 3 -kill -9 $fpid diff --git a/benchmark/fiber/go.mod b/benchmark/fiber/go.mod deleted file mode 100644 index b0b7119..0000000 --- a/benchmark/fiber/go.mod +++ /dev/null @@ -1,17 +0,0 @@ -module github.com/ngn13/ctorm/benchmark/fiber - -go 1.21.6 - -require ( - github.com/andybalholm/brotli v1.1.0 // indirect - github.com/gofiber/fiber/v3 v3.0.0-20240301123040-4ab862970609 // indirect - github.com/gofiber/utils/v2 v2.0.0-beta.3 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.52.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.17.0 // indirect -) diff --git a/benchmark/fiber/go.sum b/benchmark/fiber/go.sum deleted file mode 100644 index 0ef60d6..0000000 --- a/benchmark/fiber/go.sum +++ /dev/null @@ -1,25 +0,0 @@ -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/gofiber/fiber/v3 v3.0.0-20240301123040-4ab862970609 h1:f+wyS97rdFF26qDJ74aTmYra2yybDxVxvXDEGeiA/Dw= -github.com/gofiber/fiber/v3 v3.0.0-20240301123040-4ab862970609/go.mod h1:M5+ErQSUndBsaHN3zyHLWgmvscqtJzhJVxMm6G8sr9g= -github.com/gofiber/utils/v2 v2.0.0-beta.3 h1:pfOhUDDVjBJpkWv6C5jaDyYLvpui7zQ97zpyFFsUOKw= -github.com/gofiber/utils/v2 v2.0.0-beta.3/go.mod h1:jsl17+MsKfwJjM3ONCE9Rzji/j8XNbwjhUVTjzgfDCo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= -github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/benchmark/fiber/main.go b/benchmark/fiber/main.go deleted file mode 100644 index e313f93..0000000 --- a/benchmark/fiber/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import "github.com/gofiber/fiber/v3" - -func main(){ - // create the app - app := fiber.New() - - // setup the "hello world!" route - app.Get("/", func(c fiber.Ctx) error { - return c.SendString("hello world!") - }) - - // start the app - app.Listen("127.0.0.1:8080") -} diff --git a/benchmark/fiber/test.sh b/benchmark/fiber/test.sh deleted file mode 100755 index 6a27d59..0000000 --- a/benchmark/fiber/test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -go build -./fiber & fpid=$! -sleep 3 - -ab -t 10 -n 10000 -c 100 127.0.0.1:8080/ - -sleep 3 -kill -9 $fpid diff --git a/benchmark/tide/.gitignore b/benchmark/tide/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/benchmark/tide/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/benchmark/tide/Cargo.lock b/benchmark/tide/Cargo.lock deleted file mode 100644 index c142db6..0000000 --- a/benchmark/tide/Cargo.lock +++ /dev/null @@ -1,2308 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aead" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" -dependencies = [ - "generic-array", -] - -[[package]] -name = "aes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher", -] - -[[package]] -name = "aes-gcm" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher", - "opaque-debug", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher", - "opaque-debug", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "async-dup" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2886ab563af5038f79ec016dd7b87947ed138b794e8dd64992962c9cca0411" -dependencies = [ - "async-lock 3.4.0", - "futures-io", -] - -[[package]] -name = "async-executor" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.3.1", - "async-executor", - "async-io 2.3.3", - "async-lock 3.4.0", - "blocking", - "futures-lite 2.3.0", - "once_cell", -] - -[[package]] -name = "async-h1" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1d1dae8cb2c4258a79d6ed088b7fb9b4763bf4e9b22d040779761e046a2971" -dependencies = [ - "async-channel 1.9.0", - "async-dup", - "async-global-executor", - "async-io 1.13.0", - "futures-lite 1.13.0", - "http-types", - "httparse", - "log", - "pin-project", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" -dependencies = [ - "async-lock 3.4.0", - "cfg-if 1.0.0", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.1", - "rustix 0.38.34", - "slab", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.3.1", - "event-listener-strategy", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "async-process" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" -dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", - "blocking", - "cfg-if 1.0.0", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.34", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-session" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345022a2eed092cd105cc1b26fd61c341e100bd5fcbbd792df4baf31c2cc631f" -dependencies = [ - "anyhow", - "async-std", - "async-trait", - "base64 0.12.3", - "bincode", - "blake3", - "chrono", - "hmac 0.8.1", - "kv-log-macro", - "rand 0.7.3", - "serde", - "serde_json", - "sha2", -] - -[[package]] -name = "async-signal" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" -dependencies = [ - "async-io 2.3.3", - "async-lock 3.4.0", - "atomic-waker", - "cfg-if 1.0.0", - "futures-core", - "futures-io", - "rustix 0.38.34", - "signal-hook-registry", - "slab", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-sse" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bba003996b8fd22245cd0c59b869ba764188ed435392cf2796d03b805ade10" -dependencies = [ - "async-channel 1.9.0", - "async-std", - "http-types", - "log", - "memchr", - "pin-project-lite 0.1.12", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 1.13.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite 0.2.14", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - -[[package]] -name = "blake3" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if 0.1.10", - "constant_time_eq", - "crypto-mac 0.8.0", - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel 2.3.1", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "cc" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.52.5", -] - -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const_fn" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "cookie" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" -dependencies = [ - "aes-gcm", - "base64 0.13.1", - "hkdf", - "hmac 0.10.1", - "percent-encoding", - "rand 0.8.5", - "sha2", - "time", - "version_check", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "cpuid-bool" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "crypto-mac" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "ctr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" -dependencies = [ - "cipher", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "discard" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" - -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "erased-serde" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" -dependencies = [ - "serde", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener 5.3.1", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - -[[package]] -name = "femme" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc04871e5ae3aa2952d552dae6b291b3099723bf779a8054281c1366a54613ef" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite 0.2.14", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite 0.2.14", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite 0.2.14", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "ghash" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hkdf" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" -dependencies = [ - "digest", - "hmac 0.10.1", -] - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest", -] - -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac 0.10.0", - "digest", -] - -[[package]] -name = "http-client" -version = "6.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1947510dc91e2bf586ea5ffb412caad7673264e14bb39fb9078da114a94ce1a5" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "http-types", - "log", -] - -[[package]] -name = "http-types" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" -dependencies = [ - "anyhow", - "async-channel 1.9.0", - "async-std", - "base64 0.13.1", - "cookie", - "futures-lite 1.13.0", - "infer", - "pin-project-lite 0.2.14", - "rand 0.7.3", - "serde", - "serde_json", - "serde_qs", - "serde_urlencoded", - "url", -] - -[[package]] -name = "httparse" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "idna" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" -dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "infer" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -dependencies = [ - "serde", - "value-bag", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" -dependencies = [ - "atomic-waker", - "fastrand 2.1.0", - "futures-io", -] - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if 1.0.0", - "concurrent-queue", - "libc", - "log", - "pin-project-lite 0.2.14", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" -dependencies = [ - "cfg-if 1.0.0", - "concurrent-queue", - "hermit-abi", - "pin-project-lite 0.2.14", - "rustix 0.38.34", - "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "polyval" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" -dependencies = [ - "cpuid-bool", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "route-recognizer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.37.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.203" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "serde_fmt" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_json" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_qs" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer", - "cfg-if 1.0.0", - "cpufeatures", - "digest", - "opaque-debug", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "standback" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" -dependencies = [ - "version_check", -] - -[[package]] -name = "stdweb" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" -dependencies = [ - "discard", - "rustc_version", - "stdweb-derive", - "stdweb-internal-macros", - "stdweb-internal-runtime", - "wasm-bindgen", -] - -[[package]] -name = "stdweb-derive" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "serde_derive", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-macros" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" -dependencies = [ - "base-x", - "proc-macro2", - "quote", - "serde", - "serde_derive", - "serde_json", - "sha1", - "syn 1.0.109", -] - -[[package]] -name = "stdweb-internal-runtime" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "sval" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" - -[[package]] -name = "sval_buffer" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" -dependencies = [ - "sval", - "sval_ref", -] - -[[package]] -name = "sval_dynamic" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_fmt" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_json" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" -dependencies = [ - "itoa", - "ryu", - "sval", -] - -[[package]] -name = "sval_nested" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" -dependencies = [ - "sval", - "sval_buffer", - "sval_ref", -] - -[[package]] -name = "sval_ref" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" -dependencies = [ - "sval", -] - -[[package]] -name = "sval_serde" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" -dependencies = [ - "serde", - "sval", - "sval_nested", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "thiserror" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - -[[package]] -name = "tide" -version = "0.1.0" -dependencies = [ - "async-std", - "serde", - "tide 0.16.0", -] - -[[package]] -name = "tide" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c459573f0dd2cc734b539047f57489ea875af8ee950860ded20cf93a79a1dee0" -dependencies = [ - "async-h1", - "async-session", - "async-sse", - "async-std", - "async-trait", - "femme", - "futures-util", - "http-client", - "http-types", - "kv-log-macro", - "log", - "pin-project-lite 0.2.14", - "route-recognizer", - "serde", - "serde_json", -] - -[[package]] -name = "time" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" -dependencies = [ - "const_fn", - "libc", - "standback", - "stdweb", - "time-macros", - "version_check", - "winapi", -] - -[[package]] -name = "time-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" -dependencies = [ - "proc-macro-hack", - "time-macros-impl", -] - -[[package]] -name = "time-macros-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" -dependencies = [ - "proc-macro-hack", - "proc-macro2", - "quote", - "standback", - "syn 1.0.109", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite 0.2.14", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" - -[[package]] -name = "typeid" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "universal-hash" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "url" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "value-bag" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" -dependencies = [ - "value-bag-serde1", - "value-bag-sval2", -] - -[[package]] -name = "value-bag-serde1" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" -dependencies = [ - "erased-serde", - "serde", - "serde_fmt", -] - -[[package]] -name = "value-bag-sval2" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" -dependencies = [ - "sval", - "sval_buffer", - "sval_dynamic", - "sval_fmt", - "sval_json", - "sval_ref", - "sval_serde", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if 1.0.0", - "serde", - "serde_json", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.66", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] diff --git a/benchmark/tide/Cargo.toml b/benchmark/tide/Cargo.toml deleted file mode 100644 index 8d64bfd..0000000 --- a/benchmark/tide/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tide" -version = "0.1.0" -edition = "2021" - -[dependencies] -tide = "0.16.0" -async-std = { version = "1.8.0", features = ["attributes"] } -serde = { version = "1.0", features = ["derive"] } diff --git a/benchmark/tide/src/main.rs b/benchmark/tide/src/main.rs deleted file mode 100644 index 37f29a5..0000000 --- a/benchmark/tide/src/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -use tide::Request; -use tide::prelude::*; - -#[async_std::main] -async fn main() -> tide::Result<()> { - // create the app - let mut app = tide::new(); - - // setup the "hello world!" route - app.at("/").get(index); - - // start the app - app.listen("127.0.0.1:8080").await?; - Ok(()) -} - -async fn index(mut req: Request<()>) -> tide::Result { - Ok(format!("hello world!").into()) -} diff --git a/benchmark/tide/test.sh b/benchmark/tide/test.sh deleted file mode 100755 index 8e4b42c..0000000 --- a/benchmark/tide/test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -cargo build -./target/debug/tide & fpid=$! -sleep 3 - -ab -t 10 -n 10000 -c 100 127.0.0.1:8080/ - -sleep 3 -kill -9 $fpid diff --git a/example/echo/main.c b/example/echo/main.c index fa0f5c7..bb26908 100644 --- a/example/echo/main.c +++ b/example/echo/main.c @@ -7,17 +7,19 @@ void handle_notfound(req_t *req, res_t *res) { } void handle_post(req_t *req, res_t *res) { - char *msg = NULL; - form_t form; + urlenc_t *form = NULL; + char *msg = NULL; - if (!REQ_FORM(&form)) { + if ((form = REQ_FORM()) == NULL) { res->code = 400; + error("Failed to parse the form data: %s", app_geterror()); return RES_SEND("bad body"); } - if (NULL == (msg = form_get(&form, "msg"))) { + if (NULL == (msg = enc_url_get(form, "msg"))) { res->code = 400; - req_form_free(&form); + enc_url_free(form); + error("Form data does not contain the message"); return RES_SEND("bad body"); } @@ -26,7 +28,7 @@ void handle_post(req_t *req, res_t *res) { RES_SET("Cool", "yes"); - req_form_free(&form); + enc_url_free(form); } void handle_get(req_t *req, res_t *res) { diff --git a/example/hello/main.c b/example/hello/main.c index 94cd95c..8c30c50 100644 --- a/example/hello/main.c +++ b/example/hello/main.c @@ -12,6 +12,9 @@ int main() { // example: disable the server header config.server_header = false; + // another example: disable request logging + config.disable_logging = true; + // create the app app_t *app = app_new(&config); diff --git a/example/middleware/main.c b/example/middleware/main.c index 2faa043..99de93f 100644 --- a/example/middleware/main.c +++ b/example/middleware/main.c @@ -57,13 +57,14 @@ void user_list(req_t *req, res_t *res) { } void user_delete(req_t *req, res_t *res) { - char *name = REQ_QUERY("name"); + char *name = REQ_QUERY("name"); + user_t *cur = users, *prev = NULL; + if (NULL == name) { res->code = 400; return RES_SEND("Please specify a name"); } - user_t *cur = users, *prev = NULL; while (NULL != cur) { if (strcmp(cur->name, name) != 0) { prev = cur; @@ -90,6 +91,7 @@ void user_add(req_t *req, res_t *res) { if (NULL == json) { res->code = 400; + error("Failed to get the JSON body: %s", app_geterror()); return RES_SEND("Please specify user data"); } @@ -98,6 +100,7 @@ void user_add(req_t *req, res_t *res) { if (NULL == name || NULL == age) { res->code = 400; + error("Failed to get the name or age"); return RES_SEND("Please specify user data"); } diff --git a/include/all.h b/include/all.h index a8fe413..c1d0e8f 100644 --- a/include/all.h +++ b/include/all.h @@ -21,6 +21,8 @@ #pragma once #include "ctorm.h" +#include "errors.h" +#include "log.h" #define ALL(app, path, func) app_add(app, "", false, path, func) #define GET(app, path, func) app_add(app, "GET", false, path, func) @@ -43,8 +45,8 @@ #define REQ_BODY(data) req_body(req, data) #define REQ_GET(header) req_get(req, header) #define REQ_QUERY(query) req_query(req, query) -#define REQ_FORM(f) req_form_parse(req, f) -#define REQ_JSON() req_json_parse(req) +#define REQ_FORM() req_form(req) +#define REQ_JSON() req_json(req) #define RES_SEND(text) res_send(res, text, 0) #define RES_SENDFILE(path) res_sendfile(res, path) diff --git a/include/config.h b/include/config.h new file mode 100644 index 0000000..ea4cbe2 --- /dev/null +++ b/include/config.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +// ####################### +// ## app configuration ## +// ####################### +typedef struct { + int max_connections; // max parallel connection count + bool disable_logging; // disables request logging and the banner + bool handle_signal; // disables SIGINT handler (which stops app_run()) + bool server_header; // disable sending the "Server: ctorm" header in the response + bool lock_request; // locks threads until the request handler returns + __time_t tcp_timeout; // sets the TCP socket timeout for sending and receiving + uint64_t pool_size; // app threadpool size +} app_config_t; + +void app_config_new(app_config_t *config); diff --git a/include/connection.h b/include/connection.h new file mode 100644 index 0000000..04c4cc8 --- /dev/null +++ b/include/connection.h @@ -0,0 +1,23 @@ +#pragma once + +#include "headers.h" +#include "http.h" + +#include +#include + +#include +#include + +typedef struct { + void *app; + struct sockaddr addr; + int socket; +} connection_t; + +connection_t *connection_new(); +void connection_free(connection_t *con); + +void connection_handle(connection_t *con); +#define connection_recv(c, b, s, f) recv(c->socket, b, s, f) +#define connection_send(c, b, s, f) send(c->socket, b, s, f) diff --git a/include/ctorm.h b/include/ctorm.h index a80d24d..a4e3a4c 100644 --- a/include/ctorm.h +++ b/include/ctorm.h @@ -2,9 +2,8 @@ #define CTORM_VERSION "1.5" -#include "errors.h" +#include "config.h" #include "http.h" -#include "log.h" #include "pool.h" #include "req.h" #include "res.h" @@ -26,31 +25,18 @@ typedef struct routemap_t { route_t handler; } routemap_t; -// ####################### -// ## app configuration ## -// ####################### -typedef struct app_config_t { - int max_connections; // max parallel connection count - bool disable_logging; // disables request logging and the banner - bool handle_signal; // disables SIGINT handler (which stops app_run()) - bool server_header; // disable sending the "Server: ctorm" header in the response - __time_t tcp_timeout; // sets the TCP socket timeout for sending and receiving - uint64_t pool_size; // app threadpool size -} app_config_t; - -void app_config_new(app_config_t *config); - // ################### // ## app structure ## // ################### typedef struct app_t { - routemap_t *middleware_maps; // middleware map - routemap_t *route_maps; // route map - char *staticpath; // static directory serving path - char *staticdir; // static directory - route_t allroute; // all handler route (see app_all()) - bool running; // is the app running? - pool_t *pool; // thread pool for the app + routemap_t *middleware_maps; // middleware map + routemap_t *route_maps; // route map + char *staticpath; // static directory serving path + char *staticdir; // static directory + route_t allroute; // all handler route (see app_all()) + bool running; // is the app running? + pool_t *pool; // thread pool for the app + pthread_mutex_t request_mutex; // mutex used to lock request threads app_config_t *config; // app configuration bool is_default_config; // using the default configuration? diff --git a/include/encoding.h b/include/encoding.h new file mode 100644 index 0000000..221299c --- /dev/null +++ b/include/encoding.h @@ -0,0 +1,23 @@ +#pragma once +#include + +// URL encoding (application/x-www-form-urlencoded) +typedef struct urlenc { + struct urlenc *pre; + char *key, *value; +} urlenc_t; + +urlenc_t *enc_url_parse(char *, uint64_t); // parse URL encoded data from the byte array +char *enc_url_get(urlenc_t *, char *); // get a value from the URL encoded data +void enc_url_free(urlenc_t *); // free the URL encoded data + +// JSON encoding (application/json) +#if __has_include() +#include +#else +typedef void cJSON; +#endif + +cJSON *enc_json_parse(char *); // parse JSON encoded data from the byte array +char *enc_json_dump(cJSON *, uint64_t *); // dump JSON encoded data to a byte array +void enc_json_free(cJSON *); // free the JSON encoded data diff --git a/include/errors.h b/include/errors.h index b639327..310e467 100644 --- a/include/errors.h +++ b/include/errors.h @@ -1,29 +1,35 @@ #pragma once typedef enum app_error_t { - BadTcpTimeout = 9908, - BadPoolSize = 9909, - PoolFailed = 9910, - ListenFailed = 9911, - BadAddress = 9912, - BadPort = 9913, - OptFailed = 9914, - AllocFailed = 9915, - UnknownErr = 9916, - CantRead = 9917, - SizeFail = 9918, - BadReadPerm = 9919, - FileNotExists = 9920, - BadPath = 9921, - InvalidAppPointer = 9922, - BadUrlPointer = 9923, - BadJsonPointer = 9924, - BadFmtPointer = 9925, - BadPathPointer = 9926, - BadDataPointer = 9927, - BadHeaderPointer = 9928, - BadMaxConnCount = 9929, - NoJSONSupport = 9930, + BadTcpTimeout = 9908, + BadPoolSize = 9909, + PoolFailed = 9910, + ListenFailed = 9911, + BadAddress = 9912, + BadPort = 9913, + OptFailed = 9914, + AllocFailed = 9915, + UnknownErr = 9916, + CantRead = 9917, + SizeFail = 9918, + BadReadPerm = 9919, + FileNotExists = 9920, + BadPath = 9921, + InvalidAppPointer = 9922, + BadUrlPointer = 9923, + BadJsonPointer = 9924, + BadFmtPointer = 9925, + BadPathPointer = 9926, + BadDataPointer = 9927, + BadHeaderPointer = 9928, + BadMaxConnCount = 9929, + NoJSONSupport = 9930, + MutexFail = 9931, + BadResponseCode = 9932, + ResponseAlreadySent = 9933, + InvalidContentType = 9934, + EmptyBody = 9935, + BodyRecvFail = 9936, } app_error_t; struct app_error_desc_t { diff --git a/include/form.h b/include/form.h deleted file mode 100644 index 9c7241a..0000000 --- a/include/form.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "table.h" - -typedef table_t form_t; - -#define form_init(f) table_init(f, NULL, NULL) -#define form_free(f) table_free(f) - -char *form_get(form_t *f, char *k); diff --git a/include/headers.h b/include/headers.h index 945429b..3f0f4ab 100644 --- a/include/headers.h +++ b/include/headers.h @@ -1,20 +1,35 @@ #pragma once -#include "table.h" +#include +#include -typedef table_t headers_t; -typedef table_entry_t header_t; +#define TABLE_SIZE 20 -int headers_cmp(const char *s1, const char *s2); +struct header { + struct header *next; + char *key; + char *value; + bool alloced; +}; + +typedef struct { + uint8_t _indx; + struct header *_cur; + char *key; + char *value; +} header_pos_t; + +typedef struct header *headers_t[TABLE_SIZE]; + +bool headers_cmp(const char *s1, const char *s2); uint64_t headers_hasher(const char *data); -#define headers_init(h) table_init(h, headers_cmp, headers_hasher) -#define headers_free(h) table_free(h) +#define headers_init(h) bzero(h, sizeof(headers_t)) +void headers_free(headers_t); -#define headers_start(e) table_start(e) -#define headers_next(h, e) table_next(h, e) +#define headers_start(p) bzero(p, sizeof(header_pos_t)) +bool headers_next(headers_t, header_pos_t *); -#define headers_add(h, k, v, a) table_add(h, k, v, a); -bool headers_set(headers_t *h, char *key, char *value, bool dup); -char *headers_get(headers_t *h, char *key); -#define headers_del(h, k) table_del(h, k) +bool headers_set(headers_t, char *, char *, bool); +char *headers_get(headers_t, char *); +void headers_del(headers_t, char *); diff --git a/include/http.h b/include/http.h index c734a02..a7d2140 100644 --- a/include/http.h +++ b/include/http.h @@ -1,16 +1,19 @@ #pragma once #include +#include #include // HTTP method enum -typedef enum { +enum { METHOD_GET = 0, METHOD_HEAD = 1, METHOD_POST = 2, METHOD_PUT = 3, METHOD_DELETE = 4, METHOD_OPTIONS = 5, -} method_t; +}; + +typedef int8_t method_t; // HTTP method map typedef struct { @@ -22,7 +25,7 @@ typedef struct { extern method_map_t http_method_map[]; // supported HTTP versions -extern char *http_versions[]; +extern const char *http_versions[]; /* @@ -35,24 +38,28 @@ extern char *http_versions[]; */ typedef struct { - size_t method_count; // stores the count of HTTP methods - size_t method_max; // stores the longest HTTP method's length + uint8_t method_count; // stores the count of HTTP methods + uint8_t method_max; // stores the longest HTTP method's length + + uint8_t version_count; // stores the count of HTTP versions + uint8_t version_len; // stores the HTTP version length - size_t version_count; // stores the count of HTTP versions - size_t version_len; // stores the HTTP version length + uint64_t header_max; // stores the max header size + uint64_t path_max; // stroes the max path size + uint64_t body_max; // stores the max size for HTTP body - size_t header_max; // stores the max header size - size_t path_max; // stroes the max path size - size_t body_max; // stores the max size for HTTP body + uint16_t res_code_min; // stores the minimum HTTP response code value + uint16_t res_code_max; // stores the maximum HTTP response code value } http_static_t; extern http_static_t http_static; void http_static_load(); -// helpers for HTTP methods method_t http_method_id(char *); char *http_method_name(int); bool http_method_has_body(int); -// helpers for HTTP versions -char *http_version_get(char *); +#define http_is_valid_header_char(c) (is_digit(c) || is_letter(c) || contains("_ :;.,\\/\"'?!(){}[]@<>=-+*#$&`|~^%", c)) +#define http_is_valid_path_char(c) (is_digit(c) || is_letter(c) || contains("-._~:/?#[]@!$&'()*+,;%=", c)) + +const char *http_version_get(char *); diff --git a/include/log.h b/include/log.h index 2b83603..0ac35bc 100644 --- a/include/log.h +++ b/include/log.h @@ -3,17 +3,17 @@ #include "req.h" #include "res.h" -#define COLOR_RED "\x1b[31m" -#define COLOR_BOLD "\x1b[1m" -#define COLOR_BLUE "\x1b[34m" -#define COLOR_CYAN "\x1b[36m" -#define COLOR_YELLO "\x1b[33m" -#define COLOR_GREEN "\x1b[32m" -#define COLOR_MAGENTA "\x1b[35m" -#define COLOR_RESET "\x1b[0m" +#define FG_RED "\x1b[31m" +#define FG_BOLD "\x1b[1m" +#define FG_BLUE "\x1b[34m" +#define FG_CYAN "\x1b[36m" +#define FG_YELLO "\x1b[33m" +#define FG_GREEN "\x1b[32m" +#define FG_MAGENTA "\x1b[35m" +#define FG_RESET "\x1b[0m" #if CTORM_DEBUG -#define debug(...) _debug(__VA_ARGS__) +#define debug(f, ...) _debug("(" FG_BOLD "%s" FG_RESET ") " f, __func__, ##__VA_ARGS__) #else #define debug(...) asm("nop") #endif diff --git a/include/parse.h b/include/parse.h deleted file mode 100644 index f703ed7..0000000 --- a/include/parse.h +++ /dev/null @@ -1,25 +0,0 @@ -#include "ctorm.h" -#include "req.h" - -#define BUFFER_SIZE 100 - -typedef enum parse_ret { - RET_CONFAIL = 0, - RET_TOOLARGE = 1, - RET_BADREQ = 2, - RET_OK = 3, -} parse_ret_t; - -typedef enum parse_state { - STATE_METHOD_0 = 0, // "GET" - STATE_PATH_1 = 1, // "/example" - STATE_VERSION_2 = 2, // "HTTP/1.1" - STATE_NEWLINE_3 = 3, // "\r\n" - STATE_NAME_4 = 4, // "User-Agent" - STATE_VALUE_5 = 5, // "curl/8.8.0" - STATE_NEWLINE_6 = 6, // "\r\n" - STATE_BODY_7 = 7, -} parse_state_t; - -bool parse_form(table_t *, char *); -parse_ret_t parse_request(req_t *, int); diff --git a/include/req.h b/include/req.h index 570bf56..c0cf135 100644 --- a/include/req.h +++ b/include/req.h @@ -1,47 +1,43 @@ #pragma once -#include -#if __has_include() -#include -#else -typedef void cJSON; -#endif +#include "connection.h" +#include "encoding.h" +#include "headers.h" +#include "headers.h" #include "http.h" -#include "table.h" #include "form.h" -#include "headers.h" typedef struct req_t { - method_t method; // HTTP method (GET, POST, PUT etc.) - bool cancel; - char *encpath; // url encoded path (does include queries) - char *path; // url decoded path (does not include queries) - char *version; // HTTP version number (for example "HTTP/1.1") - char addr[INET6_ADDRSTRLEN]; // TCP connection address - - headers_t headers; // HTTP headers - table_t query; // HTTP queries (for example "?key=1") - - size_t bodysize; // size of the HTTP body - char *body; // raw body, does NOT have a NULL terminator + connection_t *con; // socket connection + + method_t method; // HTTP method (GET, POST, PUT etc.) + bool cancel; + char *encpath; // url encoded path (does include queries) + char *path; // url decoded path (does not include queries) + const char *version; // HTTP version number (for example "HTTP/1.1") + + headers_t headers; // HTTP headers + bool received_headers; // did we receive all the HTTP headers + urlenc_t *queries; // HTTP queries (for example "?key=1") + int64_t bodysize; // size of the HTTP body } req_t; -void req_init(req_t *); // setup a request -void req_free(req_t *); // cleanup a request -size_t req_size(req_t *); // get the request size (used with req_tostr()) -void req_tostr(req_t *, char *); // convert the request to string +void req_init(req_t *, connection_t *); // setup a request +void req_free(req_t *); // cleanup a request + +bool req_start(req_t *); // receive the (at least the first part) of the HTTP request +void req_end(req_t *); // completely receive the HTTP request -char *req_method(req_t *); // get the request method (GET, POST, PUT etc.) -bool req_body(req_t *, char *); // get the request body, with a NULL terminator (printable) -size_t req_body_size(req_t *); // get the body size that will be returned with req_body() -char *req_query(req_t *, char *); // get a request URL query +char *req_method(req_t *); // get the request method (GET, POST, PUT etc.) +char *req_query(req_t *, char *); // get a request URL query +char *req_get(req_t *, char *); // get a request header -void req_set(req_t *req, char *name, char *value, bool dup); // set a request header -char *req_get(req_t *, char *); // get a request header +uint64_t req_body(req_t *, char *, uint64_t); // copy given length of body to the buffer +uint64_t req_body_size(req_t *); // get the body size -bool req_form_parse(req_t *, form_t *); // parse the URL encoded form body -void req_form_free(form_t *); // free the parsed form body +char *req_ip(req_t *, char *); // get the requester IPv4/IPv6 address as string +#define req_addr(r) ((r)->con->addr) // get the requester address as sockaddr -cJSON *req_json_parse(req_t *); // parse the JSON formatted body -void req_json_free(cJSON *); // free the parsed JSON body +urlenc_t *req_form(req_t *); // parse URL encoded form body +cJSON *req_json(req_t *); // parse JSON encoded form body diff --git a/include/res.h b/include/res.h index 71f8fdd..10902ca 100644 --- a/include/res.h +++ b/include/res.h @@ -1,32 +1,37 @@ #pragma once -#if __has_include() -#include -#else -typedef void cJSON; -#endif - +#include "connection.h" +#include "encoding.h" #include "headers.h" #include "http.h" typedef struct res_t { - unsigned short code; - char *version; - char *body; - size_t bodysize; - headers_t headers; + connection_t *con; // socket connection + + const char *version; // HTTP version + unsigned short code; // HTTP response code + + char *body; // HTTP response body + uint64_t bodysize; // HTTP response body size + int bodyfd; // file descriptor associated with the body + + headers_t headers; // HTTP headers + + bool completed; // set to true if response is transferred } res_t; -void res_init(res_t *); -void res_free(res_t *); -size_t res_size(res_t *); -void res_tostr(res_t *, char *); -void res_send(res_t *, char *, size_t); -bool res_sendfile(res_t *, char *); -void res_set(res_t *, char *, char *); -void res_del(res_t *, char *); -bool res_fmt(res_t *, const char *, ...); -bool res_add(res_t *, const char *, ...); -bool res_json(res_t *, cJSON *); -void res_redirect(res_t *, char *); -void res_clear(res_t *); +void res_init(res_t *, connection_t *); // setup a response +void res_free(res_t *); // cleanup a response + +bool res_fmt(res_t *, const char *, ...); // set the response body using a format +bool res_add(res_t *, const char *, ...); // add data to response body +void res_send(res_t *, char *, uint64_t); // set response body data +bool res_sendfile(res_t *, char *); // send file data in the body data +void res_clear(res_t *); // clear the response body +bool res_json(res_t *, cJSON *); // set the response body to a JSON object + +void res_set(res_t *, char *, char *); // set a response header +void res_del(res_t *, char *); // delete a response header +void res_redirect(res_t *, char *); // set the "Location" header to redirect + +bool res_end(res_t *); // transfers the actual response diff --git a/include/socket.h b/include/socket.h index b02e76a..6014ade 100644 --- a/include/socket.h +++ b/include/socket.h @@ -1,15 +1,9 @@ #pragma once #include "ctorm.h" -#include "util.h" #include #include #include -typedef struct socket_args_t { - app_t *app; - int socket; - struct sockaddr addr; -} socket_args_t; - -bool socket_start(app_t *, char *, uint16_t); +bool socket_set_opts(app_t *app, int sockfd); +bool socket_start(app_t *app, char *addr, uint16_t port); diff --git a/include/table.h b/include/table.h deleted file mode 100644 index ae2d7eb..0000000 --- a/include/table.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include - -#define TABLE_SIZE 20 - -typedef struct table_node { - struct table_node *next; - char *key; - char *value; - bool alloced; -} table_node_t; - -typedef struct { - uint8_t _indx; - table_node_t *_node; - char *key; - char *value; -} table_entry_t; - -typedef int(table_cmp_t)(const char *s1, const char *s2); -typedef uint64_t(table_hash_t)(const char *data); - -typedef struct { - table_node_t *lists[TABLE_SIZE]; - table_cmp_t *comparer; - table_hash_t *hasher; -} table_t; - -void table_init(table_t *t, table_cmp_t *comparer, table_hash_t *hasher); -void table_free(table_t *t); - -bool table_add(table_t *t, char *key, char *value, bool alloced); -table_node_t *table_get(table_t *t, char *key); -void table_del(table_t *t, char *key); - -#define table_start(e) bzero(e, sizeof(table_entry_t)); -bool table_next(table_t *t, table_entry_t *cur); diff --git a/include/util.h b/include/util.h index 2d1bc75..650283c 100644 --- a/include/util.h +++ b/include/util.h @@ -1,14 +1,19 @@ #pragma once #include + #include +#include #define eq(s1, s2) (strcmp(s1, s2) == 0) +#define truncate_buf(buf, size, indx, ch) \ + if (size >= indx && buf[size - indx] == ch) \ + buf[size - indx] = 0 + bool startswith(char *, char *); bool endswith(char *, char *); -bool file_read(char *, char *, size_t); -bool file_canread(char *); -size_t file_size(char *); +#define file_canread(p) (access(p, O_RDONLY) == 0) +bool file_size(char *, uint64_t *); void urldecode(char *); void stolower(char *, char *); diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh new file mode 100755 index 0000000..bb35117 --- /dev/null +++ b/scripts/benchmark.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +wrk -t12 -c400 -d10s 'http://127.0.0.1:8080/' diff --git a/scripts/echo_test.sh b/scripts/echo_test.sh new file mode 100755 index 0000000..2e42255 --- /dev/null +++ b/scripts/echo_test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +data=$(curl -X POST 'http://127.0.0.1:8080/post' \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + --data 'msg=testing' --silent) + +if [[ "${data}" != "Message: testing" ]]; then + echo 'fail (1)' + exit 1 +fi + +echo 'success' diff --git a/scripts/hello_test.sh b/scripts/hello_test.sh new file mode 100755 index 0000000..20bb206 --- /dev/null +++ b/scripts/hello_test.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +res_200=$(curl --silent -w "%{http_code}" 'http://127.0.0.1:8080/') +res_404=$(curl --silent -o /dev/null -w "%{http_code}" 'http://127.0.0.1:8080/none') + +if [[ "${res_200}" != "Hello world!200" ]]; then + echo 'fail (1)' + exit 1 +fi + +if [[ "${res_404}" != "404" ]]; then + echo 'fail (2)' + exit 1 +fi + +echo 'success' diff --git a/scripts/middleware_test.sh b/scripts/middleware_test.sh new file mode 100755 index 0000000..58723b1 --- /dev/null +++ b/scripts/middleware_test.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +test_users() { + users=$(curl --silent 'http://127.0.0.1:8080/users' | jq -r '.list[0]| "\(.name) \(.age)"') + + if [[ "${users}" == "${1}" ]]; then + return 0 + fi + + return 1 +} + +if ! test_users 'John 23'; then + echo 'fail (1)' + exit 1 +fi + +curl --silent 'http://127.0.0.1:8080/user/add' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: secretpassword' \ + --data '{"name": "test", "age": 42}' -o /dev/null + +curl -X DELETE --silent 'http://127.0.0.1:8080/user/delete?name=John' \ + -H 'Authorization: secretpassword' -o /dev/null + +if ! test_users 'test 42'; then + echo 'fail (2)' + exit 1 +fi + +echo 'success' diff --git a/scripts/pressure.py b/scripts/pressure.py new file mode 100644 index 0000000..b27a244 --- /dev/null +++ b/scripts/pressure.py @@ -0,0 +1,62 @@ +from datetime import datetime +from threading import Thread +from requests import get +from typing import List +from sys import argv + +# this script is used to send bunch of requests real fast +# hence the name "pressure" + +def __send_req(url: str) -> bool: + try: + r = get(url) + + if r.status_code != 200: + print("received non-OK response (%d)" % r.status_code) + return False + + return True + except Exception as e: + print("request failed: %s" % e) + return False + +def send_req(id: int, rc: int, url: str) -> None: + for _ in range(rc): + if not __send_req(url): + print("thread %d failed" % id) + +if __name__ == "__main__": + if len(argv) < 2: + print("usage: %s [thread count] [request count]" % argv[0]) + exit(1) + + threads: List[Thread] = [] + thread_count = int(argv[2]) if len(argv) >= 3 else 10 + req_count = int(argv[3]) if len(argv) >= 4 else 1000 + + if thread_count <= 0: + print("invalid thread count") + exit(1) + + if req_count <= 0: + print("invalid request count") + exit(1) + + # create all the threads + for i in range(thread_count): + print("creating thread %d" % i) + threads.append(Thread(target=send_req, args=(i, req_count, argv[1],))) + + start = datetime.now() + + # start all of them + for t in threads: + t.start() + + # wait for all of them (will hang) + for t in threads: + t.join() + + secs = (datetime.now()-start).total_seconds() + print(f"took {secs} seconds") + diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..96bd030 --- /dev/null +++ b/src/config.c @@ -0,0 +1,15 @@ +#include "../include/config.h" +#include + +void app_config_new(app_config_t *config) { + if (NULL == config) + return; + + config->max_connections = 1000; + config->disable_logging = false; + config->handle_signal = true; + config->server_header = true; + config->lock_request = true; + config->tcp_timeout = 10; + config->pool_size = 30; +} diff --git a/src/connection.c b/src/connection.c new file mode 100644 index 0000000..407f7e1 --- /dev/null +++ b/src/connection.c @@ -0,0 +1,97 @@ +#include "../include/connection.h" +#include "../include/options.h" +#include "../include/errors.h" + +#include "../include/ctorm.h" +#include "../include/log.h" + +#include +#include +#include + +#include + +connection_t *connection_new() { + connection_t *con = malloc(sizeof(connection_t)); + + if (NULL == con) { + errno = AllocFailed; + return NULL; + } + + bzero(con, sizeof(connection_t)); + return con; +} + +void connection_free(connection_t *con) { + if (con->socket != 0) + close(con->socket); + free(con); +} + +#define __connection_lock(c) \ + if (((app_t *)c->app)->config->lock_request) \ + pthread_mutex_lock(&((app_t *)c->app)->request_mutex) +#define __connection_unlock(c) \ + if (((app_t *)c->app)->config->lock_request) \ + pthread_mutex_unlock(&((app_t *)c->app)->request_mutex) +#define __connection_debug(f, ...) debug("(" FG_BOLD "Socket " FG_CYAN "%d" FG_RESET ") " f, con->socket, ##__VA_ARGS__) + +void connection_handle(connection_t *con) { + __connection_debug("Handling new connection"); + + app_t *app = con->app; + clock_t time = 0; + bool ret = false; + + req_t req; + res_t res; + + req_init(&req, con); + res_init(&res, con); + + // if request logging is enabled measure the time that takes to complete the request + if (!app->config->disable_logging) + time = clock(); + + if (!(ret = req_start(&req))) { + __connection_debug("Received a bad request"); + res.code = 400; + goto done; + } + + __connection_debug("Received a valid request"); + res.version = req.version; + + if (!app->config->server_header) + res_del(&res, "Server"); + + // actually call the routes/middlewares + __connection_lock(con); + app_route(app, &req, &res); + __connection_unlock(con); + req_end(&req); + +done: + __connection_debug("Sending response (%d)", res.code); + + // send the complete response + if (!res_end(&res)) { + __connection_debug("Failed to send the response: %s", app_geterror()); + goto free; + } + + // finish the measurement and print out the result + if (!app->config->disable_logging && ret) { + time = (clock() - time) * 1000000; + __connection_lock(con); + log_req((((double)time) / CLOCKS_PER_SEC), &req, &res); + __connection_unlock(con); + } + +free: + __connection_debug("Closing connection"); + req_free(&req); + res_free(&res); + connection_free(con); +} diff --git a/src/ctorm.c b/src/ctorm.c index 9b9a29e..3d3c510 100644 --- a/src/ctorm.c +++ b/src/ctorm.c @@ -23,6 +23,7 @@ #include "../include/ctorm.h" #include "../include/pool.h" +#include "../include/util.h" #include "../include/log.h" #include "../include/req.h" @@ -85,6 +86,11 @@ app_t *app_new(app_config_t *_config) { goto fail; } + if (config->lock_request && pthread_mutex_init(&app->request_mutex, NULL) != 0) { + errno = MutexFail; + goto fail; + } + http_static_load(); setbuf(stdout, NULL); return app; @@ -114,6 +120,9 @@ void app_free(app_t *app) { free(prev); } + if (app->config->lock_request) + pthread_mutex_destroy(&app->request_mutex); + cur = app->route_maps; while (cur != NULL) { prev = cur; @@ -127,18 +136,6 @@ void app_free(app_t *app) { free(app); } -void app_config_new(app_config_t *config) { - if (NULL == config) - return; - - config->max_connections = 1000; - config->disable_logging = false; - config->handle_signal = true; - config->server_header = true; - config->tcp_timeout = 10; - config->pool_size = 30; -} - bool app_run(app_t *app, const char *addr) { if (NULL == app) { errno = InvalidAppPointer; @@ -277,7 +274,6 @@ void app_route(app_t *app, req_t *req, res_t *res) { continue; cur->handler(req, res); - found_handler = true; } // if the request is not cancelled, call all the routes diff --git a/src/encoding/json.c b/src/encoding/json.c new file mode 100644 index 0000000..b1955f6 --- /dev/null +++ b/src/encoding/json.c @@ -0,0 +1,33 @@ +#include "../../include/encoding.h" +#include "../../include/options.h" + +#include + +cJSON *enc_json_parse(char *data) { +#if CTORM_JSON_SUPPORT + return NULL == data ? NULL : cJSON_Parse(data); +#else + errno = NoJSONSupport; + return NULL; +#endif +} + +char *enc_json_dump(cJSON *json, uint64_t *size) { +#if CTORM_JSON_SUPPORT + char *res = cJSON_Print(json); + *size = NULL == res ? 0 : strlen(res); + return res; +#else + errno = NoJSONSupport; + return NULL; +#endif +} + +void enc_json_free(cJSON *json) { +#if CTORM_JSON_SUPPORT + if (NULL != json) + cJSON_Delete(json); +#else + errno = NoJSONSupport; +#endif +} diff --git a/src/encoding/url.c b/src/encoding/url.c new file mode 100644 index 0000000..d5089a2 --- /dev/null +++ b/src/encoding/url.c @@ -0,0 +1,109 @@ +#include "../../include/encoding.h" +#include "../../include/errors.h" +#include "../../include/util.h" +#include "../../include/log.h" + +#include +#include + +#define BUF_SIZE 20 + +urlenc_t *__enc_url_add(urlenc_t *url, char *key, char *val) { + urlenc_t *new = malloc(sizeof(urlenc_t)); + bzero(new, sizeof(urlenc_t)); + + new->pre = url; + new->key = key; + new->value = val; + + return new; +} + +urlenc_t *enc_url_parse(char *data, uint64_t len) { + if (NULL == data) + return NULL; + + if (len == 0) + for (char *c = data; *c != 0; c++) + len++; + + uint64_t key_size = 0, val_size = 0, indx = 0; + char *key_buf = NULL, *val_buf = NULL; + urlenc_t *url = NULL; + bool is_key = true; + + key_size = val_size = len > BUF_SIZE * 2 ? BUF_SIZE : len; + + if (NULL == (key_buf = malloc(key_size))) { + debug("Failed to allocate a buffer for the key size"); + errno = AllocFailed; + return NULL; + } + + if (NULL == (val_buf = malloc(val_size))) { + debug("Failed to allocate a buffer for the value size"); + errno = AllocFailed; + return NULL; + } + + for (; len > 0; len--, data++) { + if (*data == '=' && is_key) { + is_key = false; + indx = 0; + continue; + } + + if (!is_key && *data == '&') + goto url_add; + + if (is_key) { + if (indx + 1 >= key_size) + key_buf = realloc(key_buf, key_size *= 2); + key_buf[indx++] = *data; + key_buf[indx] = 0; + } + + else { + if (indx + 1 >= val_size) + val_buf = realloc(val_buf, val_size *= 2); + val_buf[indx++] = *data; + val_buf[indx] = 0; + } + + if (len != 1) + continue; + + url_add: + is_key = true; + indx = 0; + + urldecode(key_buf); + urldecode(val_buf); + + url = __enc_url_add(url, key_buf, val_buf); + } + + return url; +} + +char *enc_url_get(urlenc_t *url, char *key) { + while (NULL != url) { + if (strcmp(url->key, key) == 0) + return url->value; + url = url->pre; + } + + return NULL; +} + +void enc_url_free(urlenc_t *url) { + urlenc_t *pre = NULL; + + while (NULL != url) { + pre = url->pre; + free(url->value); + free(url->key); + free(url); + url = pre; + } +} diff --git a/src/errors.c b/src/errors.c index eb16e0d..ee563e6 100644 --- a/src/errors.c +++ b/src/errors.c @@ -6,29 +6,35 @@ #include struct app_error_desc_t descs[] = { - {.code = BadTcpTimeout, .desc = "invalid TCP timeout" }, - {.code = BadPoolSize, .desc = "invalid pool size" }, - {.code = PoolFailed, .desc = "failed to create threadpool" }, - {.code = ListenFailed, .desc = "failed to listen on the interface" }, - {.code = BadAddress, .desc = "bad address for the interface" }, - {.code = BadPort, .desc = "bad port number for the interface" }, - {.code = OptFailed, .desc = "failed to set socket options" }, - {.code = AllocFailed, .desc = "memory allocation failed" }, - {.code = UnknownErr, .desc = "unknown error" }, - {.code = BadReadPerm, .desc = "permissions do not allow reading" }, - {.code = SizeFail, .desc = "failed to get the size of the file" }, - {.code = CantRead, .desc = "failed to read the file" }, - {.code = FileNotExists, .desc = "file does not exist" }, - {.code = BadPath, .desc = "invalid HTTP path (should start with /)"}, - {.code = InvalidAppPointer, .desc = "invalid app pointer" }, - {.code = BadUrlPointer, .desc = "invalid URL pointer" }, - {.code = BadJsonPointer, .desc = "invalid cJSON pointer" }, - {.code = BadFmtPointer, .desc = "invalid string format pointer" }, - {.code = BadPathPointer, .desc = "invalid path pointer" }, - {.code = BadDataPointer, .desc = "invalid data pointer" }, - {.code = BadHeaderPointer, .desc = "invalid header name/value pointer" }, - {.code = BadMaxConnCount, .desc = "invalid max connection count" }, - {.code = NoJSONSupport, .desc = "library not compiled with JSON support" }, + {.code = BadTcpTimeout, .desc = "invalid TCP timeout" }, + {.code = BadPoolSize, .desc = "invalid pool size" }, + {.code = PoolFailed, .desc = "failed to create threadpool" }, + {.code = ListenFailed, .desc = "failed to listen on the interface" }, + {.code = BadAddress, .desc = "bad address for the interface" }, + {.code = BadPort, .desc = "bad port number for the interface" }, + {.code = OptFailed, .desc = "failed to set socket options" }, + {.code = AllocFailed, .desc = "memory allocation failed" }, + {.code = UnknownErr, .desc = "unknown error" }, + {.code = BadReadPerm, .desc = "permissions do not allow reading" }, + {.code = SizeFail, .desc = "failed to get the size of the file" }, + {.code = CantRead, .desc = "failed to read the file" }, + {.code = FileNotExists, .desc = "file does not exist" }, + {.code = BadPath, .desc = "invalid HTTP path (should start with /)" }, + {.code = InvalidAppPointer, .desc = "invalid app pointer" }, + {.code = BadUrlPointer, .desc = "invalid URL pointer" }, + {.code = BadJsonPointer, .desc = "invalid cJSON pointer" }, + {.code = BadFmtPointer, .desc = "invalid string format pointer" }, + {.code = BadPathPointer, .desc = "invalid path pointer" }, + {.code = BadDataPointer, .desc = "invalid data pointer" }, + {.code = BadHeaderPointer, .desc = "invalid header name/value pointer" }, + {.code = BadMaxConnCount, .desc = "invalid max connection count" }, + {.code = NoJSONSupport, .desc = "library not compiled with JSON support" }, + {.code = MutexFail, .desc = "failed to initialize thread mutex" }, + {.code = BadResponseCode, .desc = "specified response code is invalid" }, + {.code = ResponseAlreadySent, .desc = "response has already been sent" }, + {.code = InvalidContentType, .desc = "body is not using the requested content type"}, + {.code = EmptyBody, .desc = "body is empty" }, + {.code = BodyRecvFail, .desc = "failed to receive the body" }, }; char *app_geterror_code(app_error_t code) { diff --git a/src/form.c b/src/form.c deleted file mode 100644 index 7c7aebc..0000000 --- a/src/form.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "../include/table.h" -#include "../include/form.h" - -#include - -char *form_get(form_t *f, char *k) { - table_node_t *node = table_get(f, k); - return NULL == node ? NULL : node->value; -} diff --git a/src/headers.c b/src/headers.c index 04ddb92..932aacf 100644 --- a/src/headers.c +++ b/src/headers.c @@ -1,58 +1,152 @@ #include "../include/headers.h" +#include "../include/errors.h" #include #include -#define tolower(c) (c | 32) +#include -int headers_cmp(const char *s1, const char *s2) { +#define __tolower(c) (c | 32) + +bool headers_cmp(const char *s1, const char *s2) { while (*s1 != 0 && *s2 != 0) { - if (tolower(*s1) != tolower(*s2)) - return -1; + if (__tolower(*s1) != __tolower(*s2)) + return false; s1++; s2++; } - return (*s1 == 0 && *s2 == 0) ? 0 : -1; + return *s1 == 0 && *s2 == 0; } uint64_t headers_hasher(const char *data) { uint64_t sum = 0; for (; *data != 0; data++) - sum += tolower(*data); + sum += __tolower(*data); return sum; } -bool headers_set(headers_t *h, char *key, char *value, bool dup) { - table_node_t *node = NULL; - char *valdp = value; +#define headers_hash(k) (headers_hasher(k) % TABLE_SIZE) +#define headers_list(k) (&(h[headers_hash(k)])) + +void __headers_free_single(struct header *h) { + if (h->alloced) { + free(h->key); + free(h->value); + } + + free(h); +} + +void __headers_free_list(struct header *cur) { + struct header *pre = NULL; + + while (cur != NULL) { + pre = cur; + cur = cur->next; + + __headers_free_single(pre); + } +} + +void headers_free(headers_t h) { + for (uint8_t i = 0; i < TABLE_SIZE; i++) + __headers_free_list(h[i]); +} + +bool headers_next(headers_t h, header_pos_t *pos) { + if (NULL == pos || NULL == h) + return false; + +next_node: + if (pos->_indx >= TABLE_SIZE) { + return false; + } + + if (NULL == pos->_cur) + pos->_cur = h[pos->_indx]; + else + pos->_cur = pos->_cur->next; + + if (NULL == pos->_cur) { + pos->_indx++; + goto next_node; + } + + pos->key = pos->_cur->key; + pos->value = pos->_cur->value; + + return true; +} + +bool __headers_add(headers_t h, char *key, char *val, bool alloced) { + struct header *new = NULL, *cur = NULL, **head = NULL; - if (dup) - valdp = strdup(value); + if (NULL == (new = malloc(sizeof(struct header)))) { + errno = AllocFailed; + return false; + } - if ((node = table_get(h, key)) == NULL) - return table_add(h, dup ? strdup(key) : key, valdp, true); + new->alloced = alloced; + new->next = NULL; + new->value = val; + new->key = key; - if (node->alloced) { - free(node->value); - node->value = valdp; + if (NULL == (cur = *(head = headers_list(key)))) { + *head = new; return true; } - node->alloced = true; - node->key = dup ? strdup(key) : key; - node->value = valdp; + while (NULL != cur->next) + cur = cur->next; + cur->next = new; return true; } -char *headers_get(headers_t *h, char *key) { - table_node_t *node = table_get(h, key); +bool headers_set(headers_t h, char *key, char *val, bool alloced) { + struct header **header = NULL; + + if (*(header = headers_list(key)) == NULL) + return __headers_add(h, key, val, alloced); + + if ((*header)->alloced) { + free((*header)->value); + free((*header)->key); + } + + (*header)->alloced = alloced; + (*header)->value = val; + (*header)->key = key; + + return true; +} + +char *headers_get(headers_t h, char *key) { + struct header **cur = headers_list(key); + return NULL == *cur ? NULL : (*cur)->value; +} + +void headers_del(headers_t h, char *key) { + struct header **head = headers_list(key), *cur = NULL, *pre = NULL; + + if (NULL == (cur = *head)) + return; + + while (cur != NULL) { + if (headers_cmp(key, cur->key)) + break; + + pre = cur; + cur = cur->next; + } - if (NULL == node) - return NULL; + if (NULL == pre) + *head = cur->next; + else + pre->next = cur->next; - return node->value; + __headers_free_single(cur); } diff --git a/src/http.c b/src/http.c index 662cd40..32a0821 100644 --- a/src/http.c +++ b/src/http.c @@ -14,7 +14,7 @@ method_map_t http_method_map[] = { {.code = METHOD_OPTIONS, .name = "OPTIONS", .body = false}, }; -char *http_versions[] = {"HTTP/1.1", "HTTP/1.0"}; +const char *http_versions[] = {"HTTP/1.1", "HTTP/1.0"}; http_static_t http_static; @@ -29,6 +29,9 @@ void http_static_load() { http_static.body_max = getpagesize(); http_static.path_max = 2000; + http_static.res_code_min = 100; // 100 Continue + http_static.res_code_max = 511; // 511 Network Authentication Required + for (int i = 1; i < http_static.method_count; i++) { size_t cur_len = strlen(http_method_map[i].name); if (http_static.method_max < cur_len) @@ -57,7 +60,7 @@ bool http_method_has_body(int code) { return false; } -char *http_version_get(char *version) { +const char *http_version_get(char *version) { for (int i = 0; i < http_static.version_count; i++) if (eq(http_versions[i], version)) return http_versions[i]; diff --git a/src/log.c b/src/log.c index cb3cfbc..250e9e9 100644 --- a/src/log.c +++ b/src/log.c @@ -20,8 +20,8 @@ void log_req(double time, req_t *req, res_t *res) { char tstr[25]; get_time(tstr); - printf(COLOR_MAGENTA "%s" COLOR_BOLD COLOR_MAGENTA " LOG " COLOR_RESET COLOR_YELLO "%.0fμs" COLOR_RESET COLOR_CYAN - " %d " COLOR_GREEN "%s %s" COLOR_RESET, + printf(FG_MAGENTA "%s" FG_BOLD FG_MAGENTA " LOG " FG_RESET FG_YELLO "%.0fμs" FG_RESET FG_CYAN " %d " FG_GREEN + "%s %s" FG_RESET, tstr, time, res->code, @@ -37,7 +37,7 @@ void info(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_BLUE "%s" COLOR_RESET COLOR_BLUE COLOR_BOLD " INFO " COLOR_RESET, tstr); + printf(FG_BLUE "%s" FG_RESET FG_BLUE FG_BOLD " INFO " FG_RESET, tstr); vprintf(msg, args); printf("\n"); @@ -51,7 +51,7 @@ void error(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_RED "%s" COLOR_RESET COLOR_RED COLOR_BOLD " ERROR " COLOR_RESET, tstr); + printf(FG_RED "%s" FG_RESET FG_RED FG_BOLD " ERROR " FG_RESET, tstr); vprintf(msg, args); printf("\n"); @@ -65,7 +65,7 @@ void warn(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_YELLO "%s" COLOR_RESET COLOR_YELLO COLOR_BOLD " WARN " COLOR_RESET, tstr); + printf(FG_YELLO "%s" FG_RESET FG_YELLO FG_BOLD " WARN " FG_RESET, tstr); vprintf(msg, args); printf("\n"); @@ -82,7 +82,7 @@ void _debug(const char *msg, ...) { char tstr[25]; get_time(tstr); - printf(COLOR_CYAN "%s" COLOR_RESET COLOR_CYAN COLOR_BOLD " DEBUG " COLOR_RESET, tstr); + printf(FG_CYAN "%s" FG_RESET FG_CYAN FG_BOLD " DEBUG " FG_RESET, tstr); vprintf(msg, args); printf("\n"); diff --git a/src/parse.c b/src/parse.c deleted file mode 100644 index 6a8eeb3..0000000 --- a/src/parse.c +++ /dev/null @@ -1,530 +0,0 @@ -#include "../include/parse.h" -#include "../include/ctorm.h" -#include "../include/log.h" -#include "../include/req.h" -#include "../include/socket.h" -#include "../include/util.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -char valid_header[] = "_ :;.,\\/\"'?!(){}[]@<>=-+*#$&`|~^%"; -char valid_path[] = "-._~:/?#[]@!$&'()*+,;%="; - -bool parse_form(table_t *table, char *data) { - if (NULL == data || NULL == table) - return false; - - size_t size = strlen(data), index = 0; - char key[size + 1], value[size + 1]; - bool iskey = true; - - if (size <= 0) - return false; - - bzero(key, size + 1); - bzero(value, size + 1); - - for (char *c = data; *c != 0; c++) { - if (*c == '=' && iskey) { - iskey = false; - index = 0; - continue; - } - - if (!iskey && *c == '&') { - iskey = true; - index = 0; - - urldecode(key); - urldecode(value); - - table_add(table, strdup(key), strdup(value), true); - continue; - } - - if (iskey) { - key[index] = *c; - key[index + 1] = 0; - } else { - value[index] = *c; - value[index + 1] = 0; - } - - index++; - } - - if (iskey) - return true; - - urldecode(key); - urldecode(value); - - table_add(table, strdup(key), strdup(value), true); - - return true; -} - -parse_ret_t parse_request(req_t *req, int socket) { - char state = STATE_METHOD_0, temp = 0; - size_t size = BUFFER_SIZE, index = 0; - ssize_t read = -1; - parse_ret_t ret = RET_BADREQ; - - /* - - * we extend the buffer 200 bytes everytime - * this is to prevent using realloc as much as possible - - * also to prevent allocing from the heap, we'll start by - * allocating on the stack - - */ - char _buffer[size]; - char *buffer = _buffer, *header = NULL; - - // we don't really need to zero the buffer - // bzero(buffer, size); - - // one byte at a time... - while ((read = recv(socket, buffer + index, 1, MSG_WAITALL)) > 0) { - - switch (state) { - case STATE_METHOD_0: - /* - - * if method is larger than the max method length - * the its an invalid request - - */ - if (index > http_static.method_max) - goto end; - - // check if char is a valid method char - if (!is_letter(buffer[index]) && !is_digit(buffer[index]) && buffer[index] != ' ') - goto end; - - // if the current char is ' ', we just finished reading the HTTP method - if (buffer[index] != ' ') - break; - - // if the first char is ' ' then its a bad invalid request - if (0 == index) - goto end; - - // we need the ID, so change the space with null terminator - buffer[index] = 0; - req->method = http_method_id(buffer); - - // if the method is invalid then its a bad request, we should return - if (-1 == req->method) - goto end; - - debug("Method: %s", buffer); - - // move on to next state - goto next; - break; - - case STATE_PATH_1: - /* - - * if buffer is larger than the maximum path limit - * then its a bad request - - */ - if (index > http_static.path_max) - goto end; - - // path should always start with "/" - if (0 == index && buffer[index] != '/') - goto end; - - // check if char is a valid path char - if (!contains(valid_path, buffer[index]) && !is_letter(buffer[index]) && !is_digit(buffer[index]) && - buffer[index] != ' ') - goto end; - - // check if we are end of the path - if (buffer[index] != ' ') - break; - - // if the first char is ' ' then its a bad invalid request - if (0 == index) - goto end; - - // if so, replace the ' ' with null terminator for string operation - buffer[index] = 0; - - // allocate enough space for the path, +1 for null terminator - req->encpath = malloc(index); - bzero(req->encpath, index); - - // copy the path to allocated memory - memcpy(req->encpath, buffer, index); - debug("Path: %s", req->encpath); - - // move on to next state - goto next; - break; - - case STATE_VERSION_2: - /* - - * if buffer is larger than the HTTP version length, - * then its a bad request - - */ - if (index > http_static.version_len) - goto end; - - // check if the char is a valid - if (!is_letter(buffer[index]) && !is_digit(buffer[index]) && buffer[index] != '/' && buffer[index] != '.' && - buffer[index] != '\r' && buffer[index] != '\n') - goto end; - - // check if we are at the end of the version - if (buffer[index] != '\r' && buffer[index] != '\n') - break; - - // if its the first char, then the length is 0 and its a bad request - if (0 == index) - goto end; - - // replace the '\r' or '\n' with null terminator we will restore it later - temp = buffer[index]; - buffer[index] = 0; - - // get the static pointer for the HTTP version - req->version = http_version_get(buffer); - if (NULL == req->version) - goto end; - - debug("Version: %s", req->version); - - // move on to next state, without resetting the buffer, see the next section - buffer[index] = temp; - state++; - break; - - case STATE_NEWLINE_3: - /* - * we have few cases to handle here - * - * first option: - * ============================= - * GET / HTTP/1.1\r\n - * ^ - * ============================= - * - * second option: - * ============================= - * GET / HTTP/1.1\n - * \n - * ^ - * ============================= - * - * third option: - * ============================= - * GET / HTTP/1.1\n - * User-Agent: curl - * ^ - * ============================= - * - */ - - // if its the first option, just move on - - // no out-of-bounds here, we checked the index in the previous section - if ('\n' == buffer[index] && '\r' == buffer[index - 1]) - goto next; - - /* - - * if its the second option, we just read the request - * and zero headers, so we dont have a body, we are done - - */ - if ('\n' == buffer[index] && '\n' == buffer[index - 1]) { - ret = RET_OK; - goto end; - } - - /* - - * if its the third option, clear the buffer and - * restore the first char of the header - - */ - temp = buffer[index]; - - bzero(buffer, size); - index = 0; - state++; - - buffer[index] = temp; - break; - - case STATE_NAME_4: - /* - - * if instead of the header we got a newline, - * skip the value state and move on to the body - - */ - if (index == 0 && ('\r' == buffer[index] || '\n' == buffer[index])) { - buffer[index] = 0; - state = STATE_BODY_7; - index--; - - break; - } - - /* - - * if the buffer is larger then the maximum header - * name length, then the request is too large - - */ - if (index > http_static.header_max) { - ret = RET_TOOLARGE; - goto end; - } - - // if the header name contains an invalid char, then return bad request - if (!contains(valid_header, buffer[index]) && !is_digit(buffer[index]) && !is_letter(buffer[index])) - goto end; - - // if the the char is ' ', then we probably just read the header name - if (buffer[index] != ' ') - break; - - /* - - * if we are at the start or the previous char is not ':' - * then its a bad header name, so return bad request - - */ - if (0 == index || buffer[index - 1] != ':') - goto end; - - // otherwise add the header to the request and move on - buffer[index - 1] = 0; - header = strdup(buffer); - - debug("Header: %s", buffer); - - goto next; - break; - - case STATE_VALUE_5: - /* - - * if the buffer is larger then the maximum header - * value length, then the request is too large - - */ - if (index > http_static.header_max) { - ret = RET_TOOLARGE; - goto end; - } - - // if the header value contains an invalid char, then return bad request - if (!contains(valid_header, buffer[index]) && !is_digit(buffer[index]) && !is_letter(buffer[index]) && - buffer[index] != '\r' && buffer[index] != '\n') - goto end; - - // if the the char is '\r' or '\n' we are done reading the header value - if (buffer[index] != '\r' && buffer[index] != '\n') - break; - - // if we are at the start, then its a bad request, yes, empty header value is not allowed - if (0 == index) - goto end; - - // otherwise set the header value and move on this time we will need to restore the char - char prev = buffer[index]; - buffer[index] = 0; - - debug("Value: %s", buffer); - req_set(req, header, strdup(buffer), false); - - // header buffer is now used by the header table, we don't need to free it - header = NULL; - buffer[index] = prev; - - // why not goto next? well see the next section - state++; - break; - - case STATE_NEWLINE_6: - /* - * kinda complicated, there are two options: - * ===================================== - * User-Agent: curl\r\n - * ^ - * ===================================== - * - * second option is: - * ===================================== - * User-Agent: curl\n - * \n - * ^ - * ===================================== - * - * third option is: - * ===================================== - * User-Agent: curl\n - * Host: whateverig - * ^ - * ===================================== - * - * this is why we didn't clear the buffer - * after reading the value - * - */ - - // if its the first option, go back to reading the header name - if (buffer[index] == '\n' && buffer[index - 1] == '\r') { - state = STATE_NAME_4; - goto reset; - break; - } - - // if its the second option move on to the body - - // we can't go out of bounds, index was checked before this section - if (buffer[index] == '\n' && buffer[index - 1] == '\n') - goto next; - - /* - - * if its the third option, go back to reading the - * header, but first clear the body and restore the char - - */ - temp = buffer[index]; - - bzero(buffer, size); - index = 0; - state = STATE_NAME_4; - - buffer[index] = temp; - break; - - case STATE_BODY_7: - ret = RET_OK; - - // can the request method have a body? - if (!http_method_has_body(req->method)) - goto end; - - // do we have the content length header? - char *contentlen = req_get(req, "content-length"); - if (NULL == contentlen) - goto end; - - // if so, then parse the header value - req->bodysize = atol(contentlen); - if (req->bodysize <= 0) - goto end; - - // make sure the body is not too large - if (req->bodysize > http_static.body_max) { - ret = RET_TOOLARGE; - goto end; - } - - // allocate and receive body - req->body = malloc(req->bodysize); - if (recv(socket, req->body, req->bodysize, MSG_WAITALL) > 0) - goto end; - - // connection failed? cleanup and return - free(req->body); - req->bodysize = 0; - req->body = NULL; - ret = RET_CONFAIL; - - goto end; - break; - - default: - debug("Unknown section"); - ret = RET_BADREQ; - goto end; - } - - // move to the next char - index++; - - if (index < size) - continue; - - // realloc if buffer is full or malloc we are still on the stack - if (size == BUFFER_SIZE) { - char *alloc = malloc((size *= 2)); - memcpy(alloc, buffer, size); - buffer = alloc; - } else - buffer = realloc(buffer, (size *= 2)); - continue; - - next: - // clear buffer, reset index, and move on to the next state - state++; - reset: - bzero(buffer, size); - index = 0; - } - - if (read <= 0) - ret = RET_CONFAIL; - -end: - if (RET_OK != ret) - goto ret; - - char *save = NULL, *rest = NULL, *dup = NULL; - - if (!contains(req->encpath, '?')) { - req->path = req->encpath; - goto ret; - } - - // read the first part of the path - dup = strdup(req->encpath); - req->path = strtok_r(dup, "?", &save); - - if (NULL == req->path) { - req->path = req->encpath; - free(dup); - goto ret; - } - - rest = strtok_r(NULL, "?", &save); - if (NULL == rest) { - req->path = strdup(req->path); - free(dup); - goto ret; - } - - parse_form(&req->query, rest); - req->path = strdup(req->path); - free(dup); - -ret: - // free the buffer and return the result - if (size != BUFFER_SIZE) - free(buffer); - - free(header); // free the current unused allocated header - return ret; -} diff --git a/src/pool.c b/src/pool.c index 5245c09..4203558 100644 --- a/src/pool.c +++ b/src/pool.c @@ -1,13 +1,16 @@ #include "../include/pool.h" +#include #include #include work_t *pool_work(func_t func, void *arg) { work_t *work = malloc(sizeof(work_t)); - work->next = NULL; - work->func = func; - work->arg = arg; + + work->next = NULL; + work->func = func; + work->arg = arg; + return work; } @@ -17,14 +20,12 @@ void pool_free(work_t *work) { work_t *pool_get(pool_t *tp) { work_t *work; - work = tp->first; - if (NULL == work) + + if (NULL == (work = tp->first)) return NULL; - tp->first = work->next; - if (NULL == tp->first) { + if (NULL == (tp->first = work->next)) tp->last = NULL; - } return work; } @@ -66,6 +67,12 @@ void *pool_worker(void *arg) { pool_t *pool_init(uint64_t n) { pool_t *tp = calloc(1, sizeof(pool_t)); pthread_t handle; + sigset_t set; + + // ignore SIGPIPE (sockets may raise it) + sigemptyset(&set); + sigaddset(&set, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &set, NULL); tp->all = n; @@ -80,19 +87,24 @@ pool_t *pool_init(uint64_t n) { pthread_create(&handle, NULL, pool_worker, tp); pthread_detach(handle); } + return tp; } bool pool_add(pool_t *tp, func_t func, void *arg) { work_t *work = pool_work(func, arg); + if (work == NULL) return false; pthread_mutex_lock(&(tp->mutex)); + if (tp->first == NULL) { tp->first = work; tp->last = tp->first; - } else { + } + + else { tp->last->next = work; tp->last = work; } @@ -104,12 +116,12 @@ bool pool_add(pool_t *tp, func_t func, void *arg) { void pool_stop(pool_t *tp) { pthread_mutex_lock(&(tp->mutex)); + work_t *cur = tp->first, *next = NULL; - work_t *f = tp->first; - while (f != NULL) { - work_t *n = f->next; - pool_free(n); - f = n; + while (cur != NULL) { + next = cur->next; + pool_free(cur); + cur = next; } tp->stop = true; diff --git a/src/req.c b/src/req.c index 0de2b27..0fc8fc7 100644 --- a/src/req.c +++ b/src/req.c @@ -1,33 +1,105 @@ #include "../include/options.h" -#include "../include/parse.h" -#include "../include/table.h" +#include "../include/headers.h" + +#include "../include/errors.h" #include "../include/util.h" #include "../include/req.h" +#include "../include/log.h" -#include +#include #include #include +#include #include -void req_init(req_t *req) { +#define req_debug(f, ...) \ + debug("(" FG_BOLD "Socket " FG_CYAN "%d" FG_RESET FG_BOLD " Request " FG_CYAN "0x%p" FG_RESET ") " f, \ + req->con->socket, \ + req, \ + ##__VA_ARGS__) +#define rrecv(b, s, f) connection_recv(req->con, b, s, f) +#define RECV_BUF_SIZE 20 + +bool __rrecv_until(req_t *req, char **buf, uint64_t *size, char del, bool (*is_valid)(char)) { + if (NULL == req) + return NULL; + + uint64_t buf_size = 0, buf_indx = 0; + char cur = 0; + bool ret = false; + + if (NULL != buf && NULL == *buf) { + buf_size = RECV_BUF_SIZE; + *buf = malloc(buf_size); + } + + while (rrecv(&cur, sizeof(cur), MSG_WAITALL) > 0) { + if (NULL != size && *size != 0 && buf_indx >= *size) + break; + + if (buf_size > 0 && buf_indx >= buf_size) + *buf = realloc(*buf, buf_size += RECV_BUF_SIZE); + + if (NULL != buf) + *(*buf + buf_indx) = cur; + + if (del == cur) { + if (NULL != buf) + *(*buf + buf_indx) = 0; // replace the delemiter with a NULL terminator + + if (NULL != size) + *size = buf_indx + 1; + + ret = true; + break; + } + + if (NULL != is_valid && !is_valid(cur)) + break; + + buf_indx++; + } + + if (ret) + return true; + + if (buf_size > 0) + free(*buf); + + if (NULL != size) + *size = 0; + + return false; +} + +#define rrecv_until(b, s, d, v) __rrecv_until(req, b, s, d, v) + +bool __rrecv_is_valid_header(char c) { + return c == '\r' || http_is_valid_header_char(c); +} + +bool __rrecv_is_valid_path(char c) { + return c == '\r' || http_is_valid_path_char(c); +} + +void req_init(req_t *req, connection_t *con) { headers_init(&req->headers); - table_init(&req->query, NULL, NULL); + req->received_headers = false; + req->queries = NULL; + req->bodysize = -1; + + req->con = con; req->cancel = false; req->version = NULL; req->encpath = NULL; req->path = NULL; - - req->bodysize = 0; - req->body = NULL; } void req_free(req_t *req) { - headers_free(&req->headers); - table_free(&req->query); - - free(req->body); + headers_free(req->headers); + enc_url_free(req->queries); if (req->encpath == req->path) { free(req->encpath); @@ -36,87 +108,161 @@ void req_free(req_t *req) { free(req->encpath); free(req->path); -} -char *req_query(req_t *req, char *name) { - table_node_t *query = table_get(&req->query, name); + // req->version is a static pointer +} - if (NULL == query) - return NULL; +bool req_start(req_t *req) { + char _http_method[http_static.method_max + 1], *http_method = _http_method; + char _http_version[http_static.version_len + 2], *http_version = _http_version; + uint64_t buf_size = 0; - return query->value; -} + // get the HTTP method + buf_size = sizeof(_http_method); -bool req_body(req_t *req, char *buffer) { - if (NULL == req->body) + if (!rrecv_until(&http_method, &buf_size, ' ', NULL)) { + req_debug("Failed to get the HTTP method"); return false; + } - memcpy(buffer, req->body, req->bodysize); - buffer[req->bodysize] = 0; - return true; -} + if ((req->method = http_method_id(http_method)) == -1) { + req_debug("Invalid HTTP method: %s", http_method); + return false; + } -size_t req_body_size(req_t *req) { - if (req->bodysize <= 0) - return 0; - return req->bodysize + 1; -} + // get the HTTP path + buf_size = http_static.path_max + 1; -bool req_form_parse(req_t *req, form_t *form) { - size_t size = req_body_size(req); - if (size == 0) + if (!rrecv_until(&req->encpath, &buf_size, ' ', __rrecv_is_valid_path)) { + req_debug("Failed to get the request path"); return false; + } - char *contentt = req_get(req, "content-type"); - if (!startswith(contentt, "application/x-www-form-urlencoded")) - return false; + // get the HTTP version + buf_size = sizeof(_http_version); - char data[size]; - if (!req_body(req, data)) + if (!rrecv_until(&http_version, &buf_size, '\n', NULL)) { + req_debug("Failed to get the HTTP version"); return false; + } - form_init(form); + truncate_buf(http_version, buf_size, 2, '\r'); - if (!parse_form(form, data)) { - form_free(form); + if (NULL == (req->version = http_version_get(http_version))) { + req_debug("Received an invalid HTTP version: %s", http_version); return false; } + // decode the path (queries and shit) + char *save = NULL, *rest = NULL, *dup = NULL; + + if (!contains(req->encpath, '?')) { + req->path = req->encpath; + return true; + } + + dup = strdup(req->encpath); + + if (NULL == (req->path = strtok_r(dup, "?", &save))) { + req->path = req->encpath; + goto dup_free_ret; + } + + if (NULL == (rest = strtok_r(NULL, "?", &save))) { + req->path = strdup(req->path); + goto dup_free_ret; + } + + req->queries = enc_url_parse(rest, 0); + req->path = strdup(req->path); + +dup_free_ret: + free(dup); return true; } -void req_form_free(form_t *form) { - form_free(form); +void req_end(req_t *req) { + // get the request body size + req_body_size(req); + + if (!req->received_headers) { + // skip all the headers (we aint gonna need them after this point) + uint64_t size = 0; + + while (rrecv_until(NULL, &size, '\n', NULL) && (size != 1 && size != 2)) + continue; + + req->received_headers = true; + } + + // receive all the body from the connection + char c = 0; + while (req_body(req, &c, sizeof(c)) > 0) + ; } -cJSON *req_json_parse(req_t *req) { -#if CTORM_JSON_SUPPORT - size_t size = req_body_size(req); - if (size == 0) +char *req_query(req_t *req, char *name) { + if (NULL == name) + return NULL; + return enc_url_get(req->queries, name); +} + +urlenc_t *req_form(req_t *req) { + char *contentt = req_get(req, "content-type"); + urlenc_t *form = NULL; + uint64_t size = 0; + + if (!startswith(contentt, "application/x-www-form-urlencoded")) { + errno = InvalidContentType; return NULL; + } - char *contentt = req_get(req, "content-type"); - if (!startswith(contentt, "application/json")) + if ((size = req_body_size(req)) == 0) { + errno = EmptyBody; return NULL; + } char data[size]; - if (!req_body(req, data)) + bzero(data, size); + + if (req_body(req, data, size) != size) { + errno = BodyRecvFail; return NULL; + } - return cJSON_Parse(data); -#else - errno = NoJSONSupport; - return NULL; -#endif + if ((form = enc_url_parse(data, size)) == NULL) + return NULL; + + return form; } -void req_json_free(cJSON *json) { +cJSON *req_json(req_t *req) { #if CTORM_JSON_SUPPORT - if (NULL != json) - cJSON_Delete(json); + char *contentt = req_get(req, "content-type"); + uint64_t size = 0; + + if (!startswith(contentt, "application/json")) { + errno = InvalidContentType; + return NULL; + } + + if ((size = req_body_size(req)) == 0) { + errno = BodyRecvFail; + return NULL; + } + + char data[size + 1]; + bzero(data, size); + + if (req_body(req, data, size) != size) { + errno = BodyRecvFail; + return NULL; + } + + return enc_json_parse(data); #else errno = NoJSONSupport; - return; + return NULL; #endif } @@ -124,47 +270,125 @@ char *req_method(req_t *req) { return http_method_name(req->method); } -void req_set(req_t *req, char *name, char *value, bool dup) { - if (NULL == name || NULL == value) - errno = BadHeaderPointer; - else - headers_set(&req->headers, name, value, dup); -} +char *req_get(req_t *req, char *name) { + char *header_val = NULL; + + // if the name equals NULL, we want to receive all the headers + if (NULL != name && (header_val = headers_get(req->headers, name)) != NULL) + return header_val; -size_t req_size(req_t *req) { - char *method = req_method(req); - header_t cur; + if (req->received_headers) + return NULL; - size_t size = strlen(method) + 1; // "GET " - size += strlen(req->encpath) + 1; // "/ " - size += strlen(req->version) + 1; // "HTTP/1.1\n" + char *header_name = NULL, sep = 0; + uint64_t buf_size = 0; + +next_header: + // check if we reached the body + if (rrecv(&sep, sizeof(sep), MSG_PEEK) == sizeof(sep)) { + if (sep == '\r') { + rrecv(&sep, sizeof(sep), MSG_WAITALL); + rrecv(&sep, sizeof(sep), MSG_PEEK); + } + + // we reached the body + if (sep == '\n') { + rrecv(&sep, sizeof(sep), MSG_WAITALL); + req_debug("Received all the headers"); + req->received_headers = true; + return NULL; + } + } - headers_start(&cur); + // receive the header name + buf_size = http_static.header_max; + header_name = NULL; - while (headers_next(&req->headers, &cur)) { - size += strlen(cur.key) + 2; // "User-Agent: " - size += strlen(cur.value) + 1; // "curl\n" + if (!rrecv_until(&header_name, &buf_size, ':', __rrecv_is_valid_header)) { + req_debug("Failed to get the header name"); + return NULL; + } + + // receive the header value + if (rrecv(&sep, sizeof(sep), MSG_WAITALL) != sizeof(sep) || sep != ' ') { + req_debug("Failed to receive the header value (invalid separator)"); + return NULL; + } + + buf_size = http_static.header_max; + header_val = NULL; + + if (!rrecv_until(&header_val, &buf_size, '\n', __rrecv_is_valid_header)) { + req_debug("Failed to get the header value"); + return NULL; } - size += 1; // "\n" - return size; + truncate_buf(header_val, buf_size, 2, '\r'); + req_debug("Received a new header: %s (%.5s...)", header_name, header_val); + headers_set(req->headers, header_name, header_val, true); + + if (NULL != name && headers_cmp(header_name, name)) + return header_val; + + goto next_header; } -void req_tostr(req_t *req, char *str) { - char *method = req_method(req); - size_t index = 0; - header_t cur; +uint64_t req_body_size(req_t *req) { + if (req->bodysize >= 0) + return req->bodysize; + + if (!http_method_has_body(req->method)) { + req->bodysize = 0; + return 0; + } - index += sprintf(str + index, "%s %s %s\n", method, req->encpath, req->version); + char *content_len = req_get(req, "content-length"); - headers_start(&cur); + if (NULL == content_len) { + req->bodysize = 0; + return 0; + } - while (headers_next(&req->headers, &cur)) - index += sprintf(str + index, "%s: %s\n", cur.key, cur.value); + if ((req->bodysize = atol(content_len)) < 0) + req->bodysize = 0; - sprintf(str + index, "\n"); + return req->bodysize; } -char *req_get(req_t *req, char *name) { - return headers_get(&req->headers, name); +uint64_t req_body(req_t *req, char *buffer, uint64_t size) { + // receive all the headers so we can receive the body next + req_get(req, NULL); + + if (size >= req_body_size(req)) + size = req->bodysize; + req->bodysize -= size; + + if (size == 0) + return 0; + + return rrecv(buffer, size, MSG_WAITALL); +} + +char *req_ip(req_t *req, char *_ipbuf) { + char *ipbuf = _ipbuf; + + if (NULL == ipbuf) + ipbuf = malloc(INET6_ADDRSTRLEN > INET_ADDRSTRLEN ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN); + + if (NULL == ipbuf) { + errno = AllocFailed; + return NULL; + } + + switch (req->con->addr.sa_family) { + case AF_INET: + inet_ntop(AF_INET, &((struct sockaddr_in *)&req->con->addr)->sin_addr, ipbuf, INET_ADDRSTRLEN); + break; + + case AF_INET6: + inet_ntop(AF_INET6, &((struct sockaddr_in *)&req->con->addr)->sin_addr, ipbuf, INET6_ADDRSTRLEN); + break; + } + + return ipbuf; } diff --git a/src/res.c b/src/res.c index 1b931a9..d6dd2f0 100644 --- a/src/res.c +++ b/src/res.c @@ -1,28 +1,62 @@ #include "../include/options.h" +#include "../include/headers.h" + #include "../include/errors.h" #include "../include/util.h" #include "../include/res.h" +#include "../include/log.h" + +#include #include #include #include +#include #include #include +#include #include -void res_init(res_t *res) { +#define res_debug(f, ...) \ + debug("(" FG_BOLD "Socket " FG_CYAN "%d" FG_RESET FG_BOLD " Response " FG_CYAN "0x%p" FG_RESET ") " f, \ + res->con->socket, \ + res, \ + ##__VA_ARGS__) +#define rsend(b, s, f) connection_send(res->con, b, s, f) +#define rprintf(f, ...) dprintf(res->con->socket, f, ##__VA_ARGS__) + +/*void __rprintf(res_t *res, char *fmt, ...) { + int size = 0; + va_list args, args_cp; + + va_start(args, fmt); + va_copy(args_cp, args); + + size = vsnprintf(NULL, 0, fmt, args) + 1; + char buf[size]; + vsnprintf(buf, size, fmt, args_cp); + + rsend(buf, size-1, 0); + va_end(args); +} + +#define rprintf(f, ...) __rprintf(res, f, ##__VA_ARGS__)*/ + +void res_init(res_t *res, connection_t *con) { headers_init(&res->headers); - res->version = NULL; - res->bodysize = 0; - res->body = NULL; - res->code = 200; + res->con = con; + res->version = NULL; + res->bodysize = 0; + res->body = NULL; + res->bodyfd = -1; + res->code = 200; + res->completed = false; - headers_add(&res->headers, "Server", "ctorm", false); - headers_add(&res->headers, "Connection", "close", false); - headers_add(&res->headers, "Content-Length", "0", false); + headers_set(res->headers, "Server", "ctorm", false); + headers_set(res->headers, "Connection", "close", false); struct tm *gmt; time_t raw; @@ -37,15 +71,15 @@ void res_init(res_t *res) { } void res_free(res_t *res) { - headers_free(&res->headers); - free(res->body); + headers_free(res->headers); + res_clear(res); } void res_set(res_t *res, char *name, char *value) { if (NULL == name || NULL == value) errno = BadHeaderPointer; else - headers_set(&res->headers, name, value, true); + headers_set(res->headers, strdup(name), strdup(value), true); } void res_del(res_t *res, char *name) { @@ -54,36 +88,33 @@ void res_del(res_t *res, char *name) { return; } - headers_del(&res->headers, name); -} - -void res_update_size(res_t *res) { - int len = digits(res->bodysize) + 1; - char buf[len]; - - snprintf(buf, len, "%lu", res->bodysize); - res_set(res, "Content-Length", buf); + headers_del(res->headers, name); } void res_clear(res_t *res) { free(res->body); + + if (res->bodyfd > 0) + close(res->bodyfd); + res->body = NULL; + res->bodyfd = -1; res->bodysize = 0; } -void res_send(res_t *res, char *data, size_t size) { +void res_send(res_t *res, char *data, uint64_t size) { if (NULL == data) { errno = BadDataPointer; return; } res_clear(res); + if (size <= 0) res->bodysize = strlen(data); res->body = malloc(res->bodysize); memcpy(res->body, data, res->bodysize); - res_update_size(res); } bool res_sendfile(res_t *res, char *path) { @@ -102,19 +133,13 @@ bool res_sendfile(res_t *res, char *path) { res_clear(res); - res->bodysize = file_size(path); - if (res->bodysize < 0) { - errno = SizeFail; - res->bodysize = 0; + if (!file_size(path, &res->bodysize)) { + errno = SizeFail; return false; } - res->body = malloc(res->bodysize); - - if (!file_read(path, res->body, res->bodysize)) { - errno = CantRead; + if ((res->bodyfd = open(path, O_RDONLY)) < 0) return false; - } if (endswith(path, ".html")) res_set(res, "Content-Type", "text/html; charset=utf-8"); @@ -127,55 +152,9 @@ bool res_sendfile(res_t *res, char *path) { else res_set(res, "Content-Type", "text/plain; charset=utf-8"); - res_update_size(res); return true; } -size_t res_size(res_t *res) { - size_t size = 0; - header_t cur; - - size += http_static.version_len + 1; // "HTTP/1.1 " - size += 5; // "200\r\n" - - headers_start(&cur); - - while (headers_next(&res->headers, &cur)) { - size += strlen(cur.key) + 2; // "User-Agent: " - size += strlen(cur.value) + 2; // "curl\r\n" - } - - size += 2; // "\r\n" - - // body - size += res->bodysize; - return size; -} - -void res_tostr(res_t *res, char *str) { - size_t index = 0; - header_t cur; - - // fix the HTTP code if its invalid - if (res->code > 999 || res->code < 100) - res->code = 200; - - if (NULL == res->version) - index += sprintf(str, "HTTP/1.1 %d\r\n", res->code); - else - index += sprintf(str, "%s %d\r\n", res->version, res->code); - - headers_start(&cur); - - while (headers_next(&res->headers, &cur)) - index += sprintf(str + index, "%s: %s\r\n", cur.key, cur.value); - - index += sprintf(str + index, "\r\n"); - - if (res->bodysize > 0) - memcpy(str + index, res->body, res->bodysize); -} - bool res_fmt(res_t *res, const char *fmt, ...) { if (NULL == fmt) { errno = BadFmtPointer; @@ -196,7 +175,6 @@ bool res_fmt(res_t *res, const char *fmt, ...) { ret = vsnprintf(res->body, res->bodysize + 1, fmt, argscp) > 0; res_set(res, "Content-Type", "text/plain; charset=utf-8"); - res_update_size(res); va_end(args); va_end(argscp); @@ -228,7 +206,6 @@ bool res_add(res_t *res, const char *fmt, ...) { ret = vsnprintf(res->body + res->bodysize, (res->bodysize + 1) + vsize, fmt, argscp) > 0; res->bodysize += vsize; - res_update_size(res); va_end(args); va_end(argscp); @@ -237,7 +214,6 @@ bool res_add(res_t *res, const char *fmt, ...) { } bool res_json(res_t *res, cJSON *json) { -#if CTORM_JSON_SUPPORT if (NULL == json) { errno = BadJsonPointer; return false; @@ -245,21 +221,11 @@ bool res_json(res_t *res, cJSON *json) { res_clear(res); - res->body = cJSON_Print(json); - res->bodysize = strlen(res->body); - - if (NULL == res->body || res->bodysize <= 0) + if ((res->body = enc_json_dump(json, &res->bodysize)) == NULL) return false; res_set(res, "Content-Type", "application/json; charset=utf-8"); - res_update_size(res); - - cJSON_Delete(json); return true; -#else - errno = NoJSONSupport; - return false; -#endif } void res_redirect(res_t *res, char *url) { @@ -271,3 +237,47 @@ void res_redirect(res_t *res, char *url) { res->code = 301; res_set(res, "Location", url); } + +bool res_end(res_t *res) { + if (res->completed) { + errno = ResponseAlreadySent; + return false; + } + + header_pos_t pos; + + // fix the HTTP code if its invalid + if (res->code > http_static.res_code_max || res->code < http_static.res_code_min) { + errno = BadResponseCode; + return false; + } + + // send the HTTP response + if (NULL == res->version) + rprintf("HTTP/1.1 %u\r\n", res->code); + else + rprintf("%s %u\r\n", res->version, res->code); + + // send response headers + headers_start(&pos); + + while (headers_next(res->headers, &pos)) + rprintf("%s: %s\r\n", pos.key, pos.value); + rprintf("Content-Length: %lu\r\n", res->bodysize); + rprintf("\r\n"); + + // send the body + if (res->bodyfd > 0) { + size_t read_size = 0; + char read_buf[50]; + + while ((read_size = read(res->bodyfd, read_buf, sizeof(read_buf))) > 0) + rsend(read_buf, read_size, 0); + } + + else if (res->bodysize > 0) + rsend(res->body, res->bodysize, 0); + + res->completed = true; + return true; +} diff --git a/src/socket.c b/src/socket.c index f176e46..d5e7183 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,22 +1,15 @@ +#include "../include/connection.h" #include "../include/socket.h" #include "../include/errors.h" - -#include "../include/options.h" - -#include "../include/parse.h" #include "../include/pool.h" #include "../include/log.h" #include "../include/req.h" #include "../include/res.h" -#include #include #include -#include - -#include #include #include #include @@ -25,113 +18,6 @@ #include #include -#include - -pthread_mutex_t socket_mutex; -req_t req; -res_t res; - -void socket_handle(socket_args_t *_args) { - socket_args_t *args = (socket_args_t *)_args; - debug("(Socket %d) Created thread", args->socket); - - struct sockaddr *addr = &args->addr; - int socket = args->socket; - app_t *app = args->app; - size_t buffer_size; - parse_ret_t ret = 0; - clock_t time = 0; - - pthread_mutex_lock(&socket_mutex); - - req_init(&req); - res_init(&res); - - // set the request address - switch (addr->sa_family) { - case AF_INET: - inet_ntop(AF_INET, &((struct sockaddr_in *)addr)->sin_addr, req.addr, INET_ADDRSTRLEN); - break; - - case AF_INET6: - inet_ntop(AF_INET6, &((struct sockaddr_in *)addr)->sin_addr, req.addr, INET6_ADDRSTRLEN); - break; - } - - /* - - * if request logging is enabled - * measure the time that takes to complete the request - - */ - if (!app->config->disable_logging) - time = clock(); - - switch ((ret = parse_request(&req, socket))) { - case RET_BADREQ: - debug("(Socket %d) Received a bad request", args->socket); - res.code = 400; - goto close; - - case RET_TOOLARGE: - debug("(Socket %d) Received a request that is too large", args->socket); - res.code = 413; - goto close; - - case RET_CONFAIL: - debug("(Socket %d) Connection failed", args->socket); - - res_free(&res); - req_free(&req); - - pthread_mutex_unlock(&socket_mutex); - - close(socket); - free(args); - - return; - - default: - debug("(Socket %d) Received a valid request", args->socket); - break; - } - - if (CTORM_DEBUG) { - buffer_size = req_size(&req); - char buffer[buffer_size]; - req_tostr(&req, buffer); - debug("(Socket %d)\n%s", socket, buffer); - } - - res.version = req.version; - - if (!app->config->server_header) - res_del(&res, "Server"); - - app_route(app, &req, &res); - -close: - buffer_size = res_size(&res); - char buffer[buffer_size]; - - res_tostr(&res, buffer); - send(socket, buffer, buffer_size, 0); - - // finish the measurement and print out the result - if (!app->config->disable_logging && RET_OK == ret) { - time = (clock() - time) * 1000000; - log_req((((double)time) / CLOCKS_PER_SEC), &req, &res); - } - - req_free(&req); - res_free(&res); - - pthread_mutex_unlock(&socket_mutex); - - free(args); - close(socket); -} - bool socket_set_opts(app_t *app, int sockfd) { struct timeval timeout; bzero(&timeout, sizeof(timeout)); @@ -169,12 +55,12 @@ bool socket_set_opts(app_t *app, int sockfd) { } bool socket_start(app_t *app, char *addr, uint16_t port) { - int sockfd = -1, clientfd = -1, flag = 1; + int sockfd = -1, flag = 1; struct addrinfo *info = NULL, *cur = NULL; - struct sockaddr saddr, caddr; - socklen_t caddr_len = sizeof(caddr); - socket_args_t *args = NULL; + struct sockaddr saddr; + socklen_t saddr_len = sizeof(saddr); bool ret = false; + connection_t *con = NULL; bzero(&saddr, sizeof(saddr)); @@ -223,27 +109,31 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { goto end; } - pthread_mutex_init(&socket_mutex, NULL); + // new connection handler loop + do { + if (NULL == con) + goto con_new; - while (app->running && (clientfd = accept(sockfd, &caddr, &caddr_len)) > 0) { - debug("Accepted a new connection (socket %d)", clientfd); + debug("Accepted a new connection (con: %p, socket %d)", con, con->socket); - if (!socket_set_opts(app, clientfd)) { - error("Failed to setsockopt for %d: %s", clientfd, strerror(errno)); + if (!socket_set_opts(app, con->socket)) { + error("Failed to setsockopt for connection (con: %p, socket %d): %s", con, con->socket, strerror(errno)); break; } - args = malloc(sizeof(socket_args_t)); - memcpy(&args->addr, &caddr, sizeof(caddr)); - args->socket = clientfd; - args->app = app; + con->app = app; - pool_add(app->pool, (void *)socket_handle, (void *)args); - } + debug("Creating a thread for connection (con: %p, socket %d)", con, con->socket); + pool_add(app->pool, (void *)connection_handle, (void *)con); - pthread_mutex_destroy(&socket_mutex); - ret = true; + con_new: + if ((con = connection_new()) == NULL) { + debug("Failed to create a new connection: %s", app_geterror()); + goto end; + } + } while (app->running && (con->socket = accept(sockfd, &con->addr, &saddr_len)) > 0); + ret = true; end: if (NULL != info) freeaddrinfo(info); @@ -251,6 +141,10 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { if (sockfd > 0) close(sockfd); - free(args); + if (NULL != con) { + debug("Freeing unused connection (%p)", con); + connection_free(con); + } + return ret; } diff --git a/src/table.c b/src/table.c deleted file mode 100644 index 8dc1e0f..0000000 --- a/src/table.c +++ /dev/null @@ -1,140 +0,0 @@ -#include "../include/errors.h" -#include "../include/table.h" -#include "../include/util.h" -#include "../include/log.h" - -#include -#include - -#include -#include - -uint64_t __table_basic_hasher(const char *data) { - uint64_t sum = 0; - - for (; *data != 0; data++) - sum += *data; - - return sum; -} - -void table_init(table_t *t, table_cmp_t *comparer, table_hash_t *hasher) { - bzero(t, sizeof(table_t)); - t->comparer = comparer == NULL ? strcmp : comparer; - t->hasher = hasher == NULL ? __table_basic_hasher : hasher; -} - -#define table_hash(d) (t->hasher(d) % TABLE_SIZE) -#define table_list(k) (&(t->lists[table_hash(k)])) -#define table_cmp(k1, k2) (t->comparer(k1, k2) == 0) - -bool table_add(table_t *t, char *key, char *value, bool alloced) { - table_node_t *new = NULL, *cur = NULL, **head = NULL; - - if (NULL == (new = malloc(sizeof(table_node_t)))) { - errno = AllocFailed; - return false; - } - - new->alloced = alloced; - new->value = value; - new->key = key; - new->next = NULL; - - if (NULL == (cur = *(head = table_list(key)))) { - *head = new; - return true; - } - - while (NULL != cur->next) - cur = cur->next; - - cur->next = new; - - return true; -} - -table_node_t *table_get(table_t *t, char *key) { - table_node_t **head = NULL, *cur = NULL; - head = table_list(key); - - for (cur = *head; NULL != cur; cur = cur->next) { - if (table_cmp(key, cur->key)) - return cur; - } - - return NULL; -} - -bool table_next(table_t *t, table_entry_t *cur) { - if (NULL == cur || NULL == t) - return false; - -next_node: - if (cur->_indx >= TABLE_SIZE) { - return false; - } - - if (NULL == cur->_node) - cur->_node = t->lists[cur->_indx]; - else - cur->_node = cur->_node->next; - - if (NULL == cur->_node) { - cur->_indx++; - goto next_node; - } - - cur->key = cur->_node->key; - cur->value = cur->_node->value; - - return true; -} - -void __table_single_node_free(table_node_t *n) { - if (n->alloced) { - free(n->key); - free(n->value); - } - - free(n); -} - -void __table_node_free(table_node_t *node) { - table_node_t *pre = NULL; - - while (node != NULL) { - pre = node; - node = node->next; - - __table_single_node_free(pre); - } -} - -void table_free(table_t *t) { - for (uint8_t i = 0; i < TABLE_SIZE; i++) - __table_node_free(t->lists[i]); -} - -void table_del(table_t *t, char *key) { - table_node_t **head = NULL, *cur = NULL, *pre = NULL; - head = table_list(key); - - if (NULL == (cur = *head)) - return; - - while (cur != NULL) { - if (table_cmp(key, cur->key)) - break; - - pre = cur; - cur = cur->next; - } - - if (NULL == pre) - *head = cur->next; - else - pre->next = cur->next; - - __table_single_node_free(cur); -} diff --git a/src/util.c b/src/util.c index fcf0b4f..1d6f0cd 100644 --- a/src/util.c +++ b/src/util.c @@ -34,30 +34,16 @@ bool endswith(char *str, char *suf) { return strncmp(str + (strl - sufl), suf, sufl) == 0; } -bool file_read(char *path, char *buffer, size_t size) { - int fd = open(path, O_RDONLY); - if (fd < 0) - return false; +bool file_size(char *path, uint64_t *size) { + struct stat st; - if (read(fd, buffer, size) < 0) + if (stat(path, &st) < 0) return false; - close(fd); + *size = st.st_size; return true; } -bool file_canread(char *path) { - return access(path, O_RDONLY) == 0; -} - -size_t file_size(char *path) { - struct stat st; - if (stat(path, &st) < 0) - return -1; - - return st.st_size; -} - int digits(int n) { if (n < 0) return digits((n == INT_MIN) ? INT_MAX : -n); From b3bc06777f034fcef2c67fac5943e93136831bb6 Mon Sep 17 00:00:00 2001 From: ngn Date: Fri, 29 Nov 2024 05:19:41 +0300 Subject: [PATCH 4/5] fix docs and add testing workflow --- .github/workflows/docker.yml | 10 +++--- .github/workflows/test.yml | 41 +++++++++++++++++++++ Dockerfile | 5 +-- README.md | 1 + docs/req.md | 69 ++++++++++++++++-------------------- docs/res.md | 6 +++- example/echo/main.c | 4 +-- include/all.h | 18 +++++----- include/encoding.h | 14 ++++---- include/req.h | 12 +++---- src/ctorm.c | 8 ++--- src/encoding/json.c | 32 ++++++++++------- src/encoding/url.c | 22 ++++++------ src/req.c | 8 ++--- src/socket.c | 31 ++++++++-------- 15 files changed, 163 insertions(+), 118 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index adced48..d7072ec 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Publish Docker Images +name: Publish Docker Images on: push: tags: @@ -7,23 +7,21 @@ on: jobs: push-image: runs-on: ubuntu-latest - permissions: contents: read packages: write - steps: - - name: 'Checkout GitHub Action' + - name: 'checkout the source code' uses: actions/checkout@main - - name: 'Login to GitHub Container Registry' + - name: 'login to container registry' uses: docker/login-action@v1 with: registry: ghcr.io username: ${{github.actor}} password: ${{secrets.GITHUB_TOKEN}} - - name: 'Build Inventory Image' + - name: 'build inventory image' run: | docker build . --tag ghcr.io/ngn13/ctorm:latest ghcr.io/ngn13/ctorm:${GITHUB_REF##*/} docker push ghcr.io/ngn13/ctorm:${GITHUB_REF##*/} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..97517f9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Run Test Scripts +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: 'checkout the source code' + uses: actions/checkout@v3 + + - name: 'install dependencies' + run: | + sudo apt-get update + sudo apt-get install -y gcc make libcjson-dev + + - name: 'build the library' + run: make + + - name: 'install the library' + run: sudo make install + + - name: 'build the examples' + run: make example + + - name: 'run example #1' + run: | + ./dist/example_hello & + ./scripts/hello_test.sh + killall -9 example_hello + + - name: 'run example #2' + run: | + ./dist/example_echo & + ./scripts/echo_test.sh + killall -9 example_echo + + - name: 'run example #3' + run: | + ./dist/example_middleware & + ./scripts/echo_middleware.sh + killall -9 example_middleware diff --git a/Dockerfile b/Dockerfile index 989690b..2915614 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu as build RUN apt update -RUN apt install -y gcc make libcjson-dev libevent-dev dumb-init +RUN apt install -y gcc make libcjson-dev dumb-init WORKDIR /pkg COPY src ./src @@ -12,5 +12,6 @@ RUN make RUN make install WORKDIR / -RUN rm -r /pkg +RUN rm -fr /pkg + ENTRYPOINT ["/usr/bin/dumb-init", "--"] diff --git a/README.md b/README.md index cc83ba6..bf0870c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ # ctorm | simple web framework for C ![](https://img.shields.io/github/actions/workflow/status/ngn13/ctorm/docker.yml) +![](https://img.shields.io/github/actions/workflow/status/ngn13/ctorm/docker.yml?label=tests) ![](https://img.shields.io/github/v/tag/ngn13/ctorm?label=version) ![](https://img.shields.io/github/license/ngn13/ctorm) diff --git a/docs/req.md b/docs/req.md index 844b31f..afa698f 100644 --- a/docs/req.md +++ b/docs/req.md @@ -1,5 +1,5 @@ -# Request functions -> [!IMPORTANT] +# Request functions +> [!IMPORTANT] > You should **NOT** `free()` any data returned by this functions > **UNLESS** it's explicitly told to do so. @@ -35,61 +35,50 @@ If you are using the request object inside a middleware handler, you can cancel the request, making so will prevent it from reaching the other middlewares and the other routes: ```c +REQ_CANCEL(); +``` +Or you can cancel it manually: +```c req->cancel = true; ``` ### Working with request body -HTTP request body is non-null terminated (not printable), raw -data section of the `req_t` structure, you can directly access -it with the help of the `bodysize` section, however **you should -not directly modify it**. - -Before using the request body, you should also make sure that the -request actually contains a body: +To access the HTTP request body, you can read it into a buffer +you provide: ```c -if(req->bodysize == 0 || req->body == NULL){ - error("request does not contain a body"); - return; -} +// get the body size +uint64_t size = REQ_BODY_SIZE(); +// uint64_t size = req_body_size(req); -// the following will (potentially) read arbitrary data -// because "req->body" is not null terminated -// info("body: %s", req->body); - -char body_copy[req->bodysize]; -memcpy(body_copy, req->body, req->bodysie); -``` - -To get the null terminated body (printable), you can use the `REQ_BODY` -macro or the `req_body` function: -```c -size_t bodysize = REQ_BODY_SIZE(); -if(bodysize == 0){ +// check if body size is valid +if(size == 0){ error("request does not contain a body"); return; } -char body[bodysize]; -REQ_BODY(body); +// allocate buffer for the body data +char *body = malloc(size); -info("body: %s", body); +// read "size" bytes of body into the "buffer" +REQ_BODY(body, size); +// req_body(req, body, size); ``` ctrom also contains few helper functions to work with certain body formats: ```c // parse the form encoded body -form_t *form = REQ_FORM(); -//form_t *form = req_form(req); -char *username = req_form_get(form, "username"); // do not free or directly modify -req_form_free(form); // "username" now points to an invalid address +enc_url_t *form = REQ_FORM(); +// enc_url_t *form = req_form(req); +char *username = enc_url_get(form, "username"); // do not free or directly modify +enc_url_free(form); // "username" now points to an invalid address // parse the JSON encoded body cJSON *json = REQ_JSON(); -//cJSON *json = req_json(req); -cJSON *useritem = cJSON_GetObjectItem(json, "username"); -char *username = cJSON_GetStringValue(useritem); // do not free or directly modify -req_json_free(json); // "username" and "useritem" now points to an invalid address +// cJSON *json = req_json(req); +cJSON *un_item = cJSON_GetObjectItem(json, "username"); +char *un = cJSON_GetStringValue(un_item); // do not free or directly modify +enc_json_free(json); // "un" and "un_item" now points to an invalid address ``` ### Request queries @@ -97,6 +86,8 @@ To get URL decoded request queries, you can use the `REQ_QUERY` macro or the `req_query` function: ```c char *username = REQ_QUERY("username"); // do not free or directly modify +// char *username = req_query(req, "username"); + if(NULL == username){ error("username query is not specified"); return; @@ -112,7 +103,9 @@ To get HTTP headers, you can use the `REQ_HEADER` macro or the Also, if the client sent multiple headers with the same name, this macro/function will return the first one in the header list. ```c -char* agent = REQ_HEADER("User-Agent"); +char* agent = REQ_GET("User-Agent"); +// char *agent = req_get(req, "User-Agent"); + if(NULL == agent){ error("user-agent header is not set"); return; diff --git a/docs/res.md b/docs/res.md index d27f8e1..ae054ee 100644 --- a/docs/res.md +++ b/docs/res.md @@ -10,7 +10,11 @@ ### Setting the response code The default response code is `200 OK`. You can change this -by directly accessing the `code` section of the response object: +using the `RES_CODE()` macro: +```c +RES_CODE(403); +``` +Or you can directly modify the response code: ```c res->code = 403; ``` diff --git a/example/echo/main.c b/example/echo/main.c index bb26908..c7e2b7e 100644 --- a/example/echo/main.c +++ b/example/echo/main.c @@ -7,8 +7,8 @@ void handle_notfound(req_t *req, res_t *res) { } void handle_post(req_t *req, res_t *res) { - urlenc_t *form = NULL; - char *msg = NULL; + enc_url_t *form = NULL; + char *msg = NULL; if ((form = REQ_FORM()) == NULL) { res->code = 400; diff --git a/include/all.h b/include/all.h index c1d0e8f..14017e9 100644 --- a/include/all.h +++ b/include/all.h @@ -40,14 +40,16 @@ #define MIDDLEWARE_DELETE(app, path, func) app_add(app, "DELETE", true, path, func) #define MIDDLEWARE_OPTIONS(app, path, func) app_add(app, "OPTIONS", true, path, func) -#define REQ_METHOD() req_method(req) -#define REQ_BODY_SIZE() req_body_size(req) -#define REQ_BODY(data) req_body(req, data) -#define REQ_GET(header) req_get(req, header) -#define REQ_QUERY(query) req_query(req, query) -#define REQ_FORM() req_form(req) -#define REQ_JSON() req_json(req) - +#define REQ_METHOD() req_method(req) +#define REQ_BODY_SIZE() req_body_size(req) +#define REQ_BODY(buffer, size) req_body(req, buffer, size) +#define REQ_GET(header) req_get(req, header) +#define REQ_QUERY(query) req_query(req, query) +#define REQ_FORM() req_form(req) +#define REQ_JSON() req_json(req) +#define REQ_CANCEL() (req->cancel = true) + +#define RES_CODE(code) (res->code = code) #define RES_SEND(text) res_send(res, text, 0) #define RES_SENDFILE(path) res_sendfile(res, path) #define RES_SET(header, value) res_set(res, header, value) diff --git a/include/encoding.h b/include/encoding.h index 221299c..d475b48 100644 --- a/include/encoding.h +++ b/include/encoding.h @@ -2,14 +2,14 @@ #include // URL encoding (application/x-www-form-urlencoded) -typedef struct urlenc { - struct urlenc *pre; - char *key, *value; -} urlenc_t; +typedef struct enc_url { + struct enc_url *pre; + char *key, *value; +} enc_url_t; -urlenc_t *enc_url_parse(char *, uint64_t); // parse URL encoded data from the byte array -char *enc_url_get(urlenc_t *, char *); // get a value from the URL encoded data -void enc_url_free(urlenc_t *); // free the URL encoded data +enc_url_t *enc_url_parse(char *, uint64_t); // parse URL encoded data from the byte array +char *enc_url_get(enc_url_t *, char *); // get a value from the URL encoded data +void enc_url_free(enc_url_t *); // free the URL encoded data // JSON encoding (application/json) #if __has_include() diff --git a/include/req.h b/include/req.h index c0cf135..baa1755 100644 --- a/include/req.h +++ b/include/req.h @@ -17,10 +17,10 @@ typedef struct req_t { char *path; // url decoded path (does not include queries) const char *version; // HTTP version number (for example "HTTP/1.1") - headers_t headers; // HTTP headers - bool received_headers; // did we receive all the HTTP headers - urlenc_t *queries; // HTTP queries (for example "?key=1") - int64_t bodysize; // size of the HTTP body + headers_t headers; // HTTP headers + bool received_headers; // did we receive all the HTTP headers + enc_url_t *queries; // HTTP queries (for example "?key=1") + int64_t bodysize; // size of the HTTP body } req_t; void req_init(req_t *, connection_t *); // setup a request @@ -39,5 +39,5 @@ uint64_t req_body_size(req_t *); // get the body size char *req_ip(req_t *, char *); // get the requester IPv4/IPv6 address as string #define req_addr(r) ((r)->con->addr) // get the requester address as sockaddr -urlenc_t *req_form(req_t *); // parse URL encoded form body -cJSON *req_json(req_t *); // parse JSON encoded form body +enc_url_t *req_form(req_t *); // parse URL encoded form body +cJSON *req_json(req_t *); // parse JSON encoded form body diff --git a/src/ctorm.c b/src/ctorm.c index 3d3c510..210b748 100644 --- a/src/ctorm.c +++ b/src/ctorm.c @@ -147,7 +147,7 @@ bool app_run(app_t *app, const char *addr) { return false; } - char *save, *ip = NULL, *ports = NULL; + char *save, *ip = NULL, *_port = NULL; size_t addrsize = strlen(addr) + 1; char addrcpy[addrsize]; bool ret = false; @@ -160,20 +160,18 @@ bool app_run(app_t *app, const char *addr) { return ret; } - if (NULL == (ports = strtok_r(NULL, ":", &save))) { + if (NULL == (_port = strtok_r(NULL, ":", &save))) { errno = BadAddress; return ret; } - port = atoi(ports); + port = atoi(_port); if (port > UINT16_MAX || port <= 0) { errno = BadPort; return ret; } - info("Starting the application on %s:%d", ip, port); - app->running = true; signal_app = app; diff --git a/src/encoding/json.c b/src/encoding/json.c index b1955f6..85a99e4 100644 --- a/src/encoding/json.c +++ b/src/encoding/json.c @@ -1,33 +1,41 @@ #include "../../include/encoding.h" #include "../../include/options.h" +#include "../../include/errors.h" #include +#include -cJSON *enc_json_parse(char *data) { #if CTORM_JSON_SUPPORT + +cJSON *enc_json_parse(char *data) { return NULL == data ? NULL : cJSON_Parse(data); -#else - errno = NoJSONSupport; - return NULL; -#endif } char *enc_json_dump(cJSON *json, uint64_t *size) { -#if CTORM_JSON_SUPPORT char *res = cJSON_Print(json); *size = NULL == res ? 0 : strlen(res); return res; -#else - errno = NoJSONSupport; - return NULL; -#endif } void enc_json_free(cJSON *json) { -#if CTORM_JSON_SUPPORT if (NULL != json) cJSON_Delete(json); +} + #else + +cJSON *enc_json_parse(char *data) { errno = NoJSONSupport; -#endif + return NULL; } + +char *enc_json_dump(cJSON *json, uint64_t *size) { + errno = NoJSONSupport; + return NULL; +} + +void enc_json_free(cJSON *json) { + errno = NoJSONSupport; +} + +#endif diff --git a/src/encoding/url.c b/src/encoding/url.c index d5089a2..c099211 100644 --- a/src/encoding/url.c +++ b/src/encoding/url.c @@ -8,9 +8,9 @@ #define BUF_SIZE 20 -urlenc_t *__enc_url_add(urlenc_t *url, char *key, char *val) { - urlenc_t *new = malloc(sizeof(urlenc_t)); - bzero(new, sizeof(urlenc_t)); +enc_url_t *__enc_url_add(enc_url_t *url, char *key, char *val) { + enc_url_t *new = malloc(sizeof(enc_url_t)); + bzero(new, sizeof(enc_url_t)); new->pre = url; new->key = key; @@ -19,7 +19,7 @@ urlenc_t *__enc_url_add(urlenc_t *url, char *key, char *val) { return new; } -urlenc_t *enc_url_parse(char *data, uint64_t len) { +enc_url_t *enc_url_parse(char *data, uint64_t len) { if (NULL == data) return NULL; @@ -27,10 +27,10 @@ urlenc_t *enc_url_parse(char *data, uint64_t len) { for (char *c = data; *c != 0; c++) len++; - uint64_t key_size = 0, val_size = 0, indx = 0; - char *key_buf = NULL, *val_buf = NULL; - urlenc_t *url = NULL; - bool is_key = true; + uint64_t key_size = 0, val_size = 0, indx = 0; + char *key_buf = NULL, *val_buf = NULL; + enc_url_t *url = NULL; + bool is_key = true; key_size = val_size = len > BUF_SIZE * 2 ? BUF_SIZE : len; @@ -86,7 +86,7 @@ urlenc_t *enc_url_parse(char *data, uint64_t len) { return url; } -char *enc_url_get(urlenc_t *url, char *key) { +char *enc_url_get(enc_url_t *url, char *key) { while (NULL != url) { if (strcmp(url->key, key) == 0) return url->value; @@ -96,8 +96,8 @@ char *enc_url_get(urlenc_t *url, char *key) { return NULL; } -void enc_url_free(urlenc_t *url) { - urlenc_t *pre = NULL; +void enc_url_free(enc_url_t *url) { + enc_url_t *pre = NULL; while (NULL != url) { pre = url->pre; diff --git a/src/req.c b/src/req.c index 0fc8fc7..616e1eb 100644 --- a/src/req.c +++ b/src/req.c @@ -207,10 +207,10 @@ char *req_query(req_t *req, char *name) { return enc_url_get(req->queries, name); } -urlenc_t *req_form(req_t *req) { - char *contentt = req_get(req, "content-type"); - urlenc_t *form = NULL; - uint64_t size = 0; +enc_url_t *req_form(req_t *req) { + char *contentt = req_get(req, "content-type"); + enc_url_t *form = NULL; + uint64_t size = 0; if (!startswith(contentt, "application/x-www-form-urlencoded")) { errno = InvalidContentType; diff --git a/src/socket.c b/src/socket.c index d5e7183..d754181 100644 --- a/src/socket.c +++ b/src/socket.c @@ -7,6 +7,7 @@ #include "../include/req.h" #include "../include/res.h" +#include #include #include @@ -56,21 +57,18 @@ bool socket_set_opts(app_t *app, int sockfd) { bool socket_start(app_t *app, char *addr, uint16_t port) { int sockfd = -1, flag = 1; - struct addrinfo *info = NULL, *cur = NULL; - struct sockaddr saddr; - socklen_t saddr_len = sizeof(saddr); - bool ret = false; - connection_t *con = NULL; - - bzero(&saddr, sizeof(saddr)); + struct addrinfo *ainfo = NULL, *cur = NULL; + socklen_t addr_len = 0; + bool ret = false; + connection_t *con = NULL; if (0 == port) goto end; - if (getaddrinfo(addr, NULL, NULL, &info) != 0 || NULL == info) + if (getaddrinfo(addr, NULL, NULL, &ainfo) != 0 || NULL == ainfo) goto end; - for (cur = info; cur != NULL; cur = cur->ai_next) { + for (cur = ainfo; cur != NULL; cur = cur->ai_next) { switch (cur->ai_family) { case AF_INET: ((struct sockaddr_in *)cur->ai_addr)->sin_port = htons(port); @@ -86,9 +84,7 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { goto end; found_addr: - memcpy(&saddr, cur->ai_addr, sizeof(saddr)); - - if ((sockfd = socket(saddr.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0) { + if ((sockfd = socket(cur->ai_family, SOCK_STREAM, IPPROTO_TCP)) < 0) { debug("Failed to create socket: %s", strerror(errno)); goto end; } @@ -99,7 +95,7 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { goto end; } - if (bind(sockfd, &saddr, sizeof(saddr)) != 0) { + if (bind(sockfd, cur->ai_addr, cur->ai_addrlen) != 0) { debug("Failed to bind the socket: %s", strerror(errno)); goto end; } @@ -109,6 +105,9 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { goto end; } + // start the server + info("Starting the application on %s:%u", addr, port); + // new connection handler loop do { if (NULL == con) @@ -131,12 +130,12 @@ bool socket_start(app_t *app, char *addr, uint16_t port) { debug("Failed to create a new connection: %s", app_geterror()); goto end; } - } while (app->running && (con->socket = accept(sockfd, &con->addr, &saddr_len)) > 0); + } while (app->running && (con->socket = accept(sockfd, &con->addr, &addr_len)) > 0); ret = true; end: - if (NULL != info) - freeaddrinfo(info); + if (NULL != ainfo) + freeaddrinfo(ainfo); if (sockfd > 0) close(sockfd); From 0759551767ad1a56a51593dacb00953dd0c94419 Mon Sep 17 00:00:00 2001 From: ngn Date: Fri, 29 Nov 2024 05:22:44 +0300 Subject: [PATCH 5/5] fix test workflow for the middleware example --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97517f9..b9196dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,5 +37,5 @@ jobs: - name: 'run example #3' run: | ./dist/example_middleware & - ./scripts/echo_middleware.sh + ./scripts/middleware_test.sh killall -9 example_middleware