diff --git a/.clangd b/.clangd index 3bcc16d..2a1dc75 100644 --- a/.clangd +++ b/.clangd @@ -1,7 +1,12 @@ CompileFlags: - Add: [-Wall, -pedantic, -I../include, -DEXTENSIONS_INJA, - -DDIST_PATH="/var/www/html"] - + Add: + - -Wall + - -pedantic + - -I../include + - -DMT_ENABLED + - -DNTHREADS=8 + - -DEXTENSIONS_INJA, + - -DDIST_PATH="/var/www/html" --- # Specific C sources diff --git a/Makefile b/Makefile index f71e12c..89ac9c6 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,10 @@ include config.mk CXXSRC := $(shell find cxx -type f -name '*.cxx') CSRC += $(shell find c -type f -name '*.c') +ifeq ($(FCGI_SERVER), 1) + CSRC += servers/fcgi.c +endif + CXXOBJ := $(CXXSRC:.cxx=.o) COBJ := $(CSRC:.c=.o) @@ -82,9 +86,7 @@ docs: #target Format code format: - clang-format -Werror -i $(CSRC) $(CXXSRC) - clang-format -Werror -i examples/*.c* - clang-format -Werror -i tests/*.c* tests/*.h* + clang-format -Werror -i **/*.c* **/*.h* #target Show this help help: diff --git a/README.md b/README.md index 0cca31f..d4f1182 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ Rapida ====== [![Build Status](http://drone.vilor.one/api/badges/Rapida/rapida/status.svg)](http://drone.vilor.one/Rapida/rapida) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[Documentation](http://rapida.vilor.one/docs) -Rapida is the C and C++ web framework based on FastCGI protocol. +Web framework written in C and C++. Dependencies ------------ -* libfcgi +* libfcgi (if you need a FastCGI server) * doxygen (to make docs) * catch2 (to run tests) diff --git a/c/app.c b/c/app.c index 27922a6..3bd8482 100644 --- a/c/app.c +++ b/c/app.c @@ -5,24 +5,6 @@ #include #include -#ifdef MT_ENABLED -#include -#endif /* MT_ENABLED */ - -/*! - * \brief Listens new requests by FastCGI protocol. - * \param app Pointer to current App instance. - * \return Always returns NULL. - */ -static void *listen_requests(void *app); - -/*! - * Handle accepted request. - * \param app Application instance. - * \param req FastCGI request. - */ -static void handle_request(rpd_app *app, FCGX_Request *fcgx_req); - /*! * \brief Selects a route handler based on requested path. * \param app Application instance. @@ -31,36 +13,10 @@ static void handle_request(rpd_app *app, FCGX_Request *fcgx_req); */ static rpd_route *routes_fabric(rpd_app *app, rpd_req *req); -int rpd_app_create(rpd_app *app, const char *sock_path) +int rpd_app_create(rpd_app *app) { - app->running = app->sock_id = app->routes_len = 0; + app->running = app->routes_len = 0; app->routes = NULL; - - app->sock_path = strdup(sock_path); - if (!app->sock_path) - return 1; - - FCGX_Init(); - - return 0; -} - -int rpd_app_start(rpd_app *app) -{ - if ((app->sock_id = FCGX_OpenSocket(app->sock_path, 10)) < 0) { - return 1; - } - -#ifdef MT_ENABLED - pthread_t threads[NTHREADS]; - for (int i = 0; i < NTHREADS; i++) { - pthread_create(&threads[i], 0, listen_requests, (void *) app); - pthread_join(threads[i], 0); - } -#else - listen_requests((void *) app); -#endif - return 0; } @@ -79,69 +35,15 @@ int rpd_app_add_route(rpd_app *app, const char *path, rpd_route_cb cb, return 0; } -static void *listen_requests(void *userdata) +void rpd_app_handle_request(rpd_app *app, rpd_req *req, rpd_res *res) { - rpd_app *app = (rpd_app *) userdata; - - FCGX_Request req; - if (FCGX_InitRequest(&req, app->sock_id, 0)) { - return 0; - } - - app->running = 1; - while (app->running) { -#ifdef MT_ENABLED - static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&accept_mutex); -#endif - - int rc = FCGX_Accept_r(&req); - -#ifdef MT_ENABLED - pthread_mutex_unlock(&accept_mutex); -#endif - - if (rc < 0) { - break; - } - -#ifdef MT_ENABLED - static pthread_mutex_t handle_mutex = PTHREAD_MUTEX_INITIALIZER; - pthread_mutex_lock(&handle_mutex); -#endif - - handle_request(app, &req); - -#ifdef MT_ENABLED - pthread_mutex_unlock(&handle_mutex); -#endif - - FCGX_Finish_r(&req); - } - - return 0; -} - -static void handle_request(rpd_app *app, FCGX_Request *fcgx_req) -{ - rpd_req req; - rpd_res res; - - rpd_req_parse(&req, fcgx_req); - rpd_res_init(&res, fcgx_req); - - // get route and process request - rpd_route *route = routes_fabric(app, &req); + rpd_route *route = routes_fabric(app, req); if (!route) { + res->status = rpd_res_st_not_found; return; } - route->cb(&req, &res, route->userdata); - - rpd_res_send(&res); - - rpd_req_cleanup(&req); - rpd_res_cleanup(&res); + route->cb(req, res, route->userdata); } static rpd_route *routes_fabric(rpd_app *app, rpd_req *req) diff --git a/c/request.c b/c/request.c index 3d242ed..c1d45c5 100644 --- a/c/request.c +++ b/c/request.c @@ -5,8 +5,6 @@ #include #include -static int rpd_req_read_body(char **dest, FCGX_Request *req); - enum rpd_req_methods rpd_req_smethod(const char *method) { if (!strcmp(method, "GET")) @@ -30,44 +28,6 @@ enum rpd_req_methods rpd_req_smethod(const char *method) return UNKNOWN; } -int rpd_req_parse(rpd_req *dest, FCGX_Request *req) -{ - rpd_query_parse(&dest->query, FCGX_GetParam("QUERY_STRING", req->envp)); - - dest->method = rpd_req_smethod(FCGX_GetParam("REQUEST_METHOD", req->envp)); - rpd_url_parse(&dest->path, FCGX_GetParam("DOCUMENT_URI", req->envp)); - dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp); - dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp); - rpd_keyval_init(&dest->params, 0); - - if (dest->method != GET) { - if (rpd_req_read_body(&dest->body, req)) { - return 2; - } - } else { - dest->body = NULL; - } - - return 0; -} - -static int rpd_req_read_body(char **dest, FCGX_Request *req) -{ - char *clen = FCGX_GetParam("CONTENT_LENGTH", req->envp); - if (!clen) - return 1; - size_t len = atoll(clen); - - *dest = (char *) malloc(sizeof(char) * (len + 1)); - if (!*dest) - return 2; - - *dest[len] = '\0'; - FCGX_GetStr(*dest, len, req->in); - - return 0; -} - void rpd_req_cleanup(rpd_req *req) { rpd_keyval_cleanup(&req->params); diff --git a/c/response.c b/c/response.c index eaf1897..2bd1e7d 100644 --- a/c/response.c +++ b/c/response.c @@ -8,10 +8,8 @@ static size_t calc_res_buff_sz(const rpd_res *res); -void rpd_res_init(rpd_res *dest, FCGX_Request *req) +void rpd_res_init(rpd_res *dest) { - dest->out = req->out; - dest->status = rpd_res_st_ok; dest->location = dest->content_type = NULL; dest->body = NULL; @@ -19,17 +17,17 @@ void rpd_res_init(rpd_res *dest, FCGX_Request *req) rpd_keyval_init(&dest->cookie, 0); } -int rpd_res_send(rpd_res *res) +int rpd_res_str(char **dest, const rpd_res *res) { size_t size = calc_res_buff_sz(res); - char *buff = (char *) malloc(sizeof(char) * size); - if (!buff) { + *dest = (char *) malloc(sizeof(char) * size); + if (!*dest) { return 1; } /* header */ - char *ptr = buff; + char *ptr = *dest; ptr += sprintf(ptr, "Status: %d\r\n", res->status); if (res->content_type) { @@ -50,8 +48,6 @@ int rpd_res_send(rpd_res *res) ptr += bodylen; } - FCGX_PutS(buff, res->out); - free(buff); return 0; } diff --git a/config.mk b/config.mk index 98f2e04..c28750c 100644 --- a/config.mk +++ b/config.mk @@ -1,4 +1,4 @@ -VERSION=0.2.1 +VERSION=0.2.2 #arg Installation prefix PREFIX=/usr/local @@ -7,7 +7,7 @@ CFLAGS=-std=gnu99 -pedantic -Iinclude CXX=c++ CXXFLAGS=-pedantic -Iinclude CXXSTD=-ansi -LDFLAGS=-lfcgi +LDFLAGS= #flag Debug mode DEBUG ?= 0 @@ -19,14 +19,12 @@ else CXXFLAGS+=-O3 endif - -#arg Additional CXX flags -ADD_CXXFLAGS = -ifdef ADD_CXXFLAGS - CXXFLAGS+=$(ADD_CXXFLAGS) +#flag Add FastCGI server +FCGI_SERVER ?= 1 +ifeq ($(FCGI_SERVER), 1) + LDFLAGS += -lfcgi endif - #flag Enable inja extension EXTENSIONS_INJA ?= 0 #arg Dist path. Needed only if inja is enabled. diff --git a/docs/src/index.dox b/docs/src/index.dox index 53fffec..a311f28 100644 --- a/docs/src/index.dox +++ b/docs/src/index.dox @@ -2,7 +2,7 @@ \mainpage Rapida Manual -Rapida is the fast web framework written in C and C++. +Web framework written in C and C++. Table of contents: 1. \subpage get_started diff --git a/examples/c/example.c b/examples/c/example.c index a5d9e3a..e071506 100644 --- a/examples/c/example.c +++ b/examples/c/example.c @@ -1,4 +1,5 @@ #include "../../include/rapida.h" +#include "../../include/servers/fcgi.h" #include #include #include @@ -47,10 +48,10 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) int main() { rpd_app app; - rpd_app_create(&app, "/tmp/rapida.example.socket"); + rpd_app_create(&app); rpd_app_add_route(&app, "/products/:category/:id", &products_handler, NULL); - rpd_app_start(&app); + return rpd_fcgi_server_start(&app, "/tmp/webapp.socket"); return 0; } diff --git a/examples/c/minimal.c b/examples/c/minimal.c index 7db18d8..536292b 100644 --- a/examples/c/minimal.c +++ b/examples/c/minimal.c @@ -1,4 +1,5 @@ #include "../../include/rapida.h" +#include "../../include/servers/fcgi.h" #include /* for strdup() */ #include @@ -44,11 +45,11 @@ int main() { rpd_app app; /* Initialize application. */ - rpd_app_create(&app, "/tmp/webapp.socket"); + rpd_app_create(&app); /* Add home "/" page handler. */ rpd_app_add_route(&app, "/", &home_page_handler, NULL); /* Run the application and return its status code. */ - return rpd_app_start(&app); + return rpd_fcgi_server_start(&app, "/tmp/webapp.socket"); } diff --git a/examples/cxx/example.cxx b/examples/cxx/example.cxx index dd2d42a..2742e9c 100644 --- a/examples/cxx/example.cxx +++ b/examples/cxx/example.cxx @@ -1,4 +1,5 @@ #include "../../include/rapida.hxx" +#include "../../include/servers/fcgi.h" #include class ProductRoute : public rpd::Route { @@ -40,7 +41,7 @@ protected: int main() { - rpd::App app("/tmp/rapida.example.socket"); + rpd::App app; /* * `category` and `id` are dynamic parameters @@ -48,5 +49,5 @@ int main() */ app.add_route("/products/:category/:id", new ProductRoute()); - return app.start(); + return rpd_fcgi_server_start(app.c_app(), "/tmp/webapp.socket"); } diff --git a/examples/cxx/minimal.cxx b/examples/cxx/minimal.cxx index 4ab8d02..fa98c1b 100644 --- a/examples/cxx/minimal.cxx +++ b/examples/cxx/minimal.cxx @@ -1,4 +1,6 @@ #include "../../include/rapida.hxx" +#include "../../include/servers/fcgi.h" + /* * \brief Home page route handler. @@ -33,9 +35,9 @@ protected: int main() { - rpd::App app("/tmp/webapp.socket"); + rpd::App app; app.add_route("/", new Home()); - return app.start(); + return rpd_fcgi_server_start(app.c_app(), "/tmp/webapp.socket"); } diff --git a/include/App.hxx b/include/App.hxx index 2cb3db3..b31a63d 100644 --- a/include/App.hxx +++ b/include/App.hxx @@ -17,28 +17,24 @@ class App { public: /*! * Creates an rpd app and initializes FastCGI. - * \brief Constructor + * \brief Default constructor * \param socket_path UNIX Socket path. */ - App(const char *const socket_path) + App() { - rpd_app_create(&app, socket_path); + rpd_app_create(&app); } /*! - * \brief Starts the requests handling loop. - */ - int start() - { - return rpd_app_start(&app); - } - - /*! - * \brief Stops the requests handling loop. + * \brief Returns C implementation of the application. + * + * Usable when you need to start C server. + * + * \return C implementation of the application. */ - void stop() + rpd_app *c_app() const { - app.running = false; + return (rpd_app *) &app; } /*! diff --git a/include/Query.hxx b/include/Query.hxx index 8cfbf4b..81e54e7 100644 --- a/include/Query.hxx +++ b/include/Query.hxx @@ -20,7 +20,8 @@ public: */ Query() : KeyVal() - {} + { + } /*! * \brief Constructor. @@ -31,7 +32,8 @@ public: */ Query(rpd_keyval *keyval) : KeyVal(keyval) - {} + { + } /*! * \brief Parse query string into the key-val pairs. diff --git a/include/app.h b/include/app.h index adf4666..7475f1c 100644 --- a/include/app.h +++ b/include/app.h @@ -19,8 +19,6 @@ extern "C" { */ typedef struct { int running; /**< Application will be running while this flag is true. */ - const char *sock_path; /**< Application UNIX Socket path. */ - int sock_id; /**< Application UNIX Socket id. */ int routes_len; /**< Length of the rpd_app::routes array. */ rpd_route *routes; /**< Array of the active routes. */ } rpd_app; @@ -29,19 +27,17 @@ typedef struct { * \brief Creates Rapida application. * * \param app Pointer to application instance. - * \param sock_path UNIX Socket path. * * \return Status. 0 is success. */ -int rpd_app_create(rpd_app *app, const char *sock_path); +int rpd_app_create(rpd_app *app); /*! - * \brief Starts Rapida main loop. + * Handle accepted request. * \param app Application instance. - * - * \return Status. 0 is success. + * \param req FastCGI request. */ -int rpd_app_start(rpd_app *app); +void rpd_app_handle_request(rpd_app *app, rpd_req *req, rpd_res *res); /*! * \brief Adds route to application. diff --git a/include/request.h b/include/request.h index c47d736..af3eca3 100644 --- a/include/request.h +++ b/include/request.h @@ -10,7 +10,6 @@ #include "query.h" #include "url.h" -#include #ifdef __cplusplus extern "C" { @@ -45,8 +44,6 @@ typedef struct { rpd_keyval params; /**< Dynamic parameters. */ } rpd_req; -int rpd_req_parse(rpd_req *dest, FCGX_Request *req); - /*! * \brief Parser string request method to enumeration value. * diff --git a/include/response.h b/include/response.h index b407276..c567797 100644 --- a/include/response.h +++ b/include/response.h @@ -9,7 +9,7 @@ #define RAPIDA_RESPONSE_H_ENTRY #include "keyval.h" -#include +#include "request.h" #ifdef __cplusplus extern "C" { @@ -86,7 +86,6 @@ enum rpd_res_statuses { */ typedef struct { enum rpd_res_statuses status; /**< Response status code. */ - FCGX_Stream *out; /**< Output stream. */ char *location; /**< Location field. */ char *content_type; /**< Content type. */ char *body; /**< Response body. */ @@ -99,13 +98,17 @@ typedef struct { * \param dest Response instance. * \param req FastCGI request. */ -void rpd_res_init(rpd_res *dest, FCGX_Request *req); +void rpd_res_init(rpd_res *dest); /*! - * \brief Sends response to client. - * \param res Response instance. + * \brief Write response to string buffer. + * + * \param dest Destination buffer. + * \param src Response. + * + * \return Status code. 0 is succes. */ -int rpd_res_send(rpd_res *res); +int rpd_res_str(char **dest, const rpd_res *src); /*! * \brief Cleans response instance. diff --git a/include/servers/fcgi.h b/include/servers/fcgi.h new file mode 100644 index 0000000..d0acf5f --- /dev/null +++ b/include/servers/fcgi.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* Copyright 2022 Ivan Polyakov */ + +/*! + * \file fcgi.h + * \brief Rapida FastCGI server + */ +#ifndef RAPIDA_SERVERS_FCGI_H_ENTRY +#define RAPIDA_SERVERS_FCGI_H_ENTRY + +#include "../app.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*! + * \brief Starts Rapida FastCGI server. + * \param app Application instance. + * \param sock_path UNIX Socket path. + * + * \return Status. 0 is success. + */ +int rpd_fcgi_server_start(rpd_app *app, const char *sock_path); + +#ifdef __cplusplus +} +#endif + +#endif /* RAPIDA_SERVERS_FCGI_H_ENTRY */ diff --git a/servers/fcgi.c b/servers/fcgi.c new file mode 100644 index 0000000..d02579a --- /dev/null +++ b/servers/fcgi.c @@ -0,0 +1,185 @@ +#include "../include/servers/fcgi.h" +#include + +#ifdef MT_ENABLED +#include +#endif /* MT_ENABLED */ + +typedef struct { + rpd_app *app; + int sock_id; +} thread_data; + +/*! + * \brief Run FastCGI requests listening in loop. + * \param data \see thread_data. + * \return NULL; + */ +static void *listen_requests(void *data); + +/*! + * \brief Handle Request. + * + * This function converts FastCGI request into the Rapida request + * and calls Rapida request handler. + * + * \param app Application instance. + * \param fcgx_req FastCGI Request. + */ +static void handle_request(rpd_app *app, FCGX_Request *fcgx_req); + +/*! + * \brief Convert FastCGI request into the Rapida request. + * + * \param dest Rapida request. + * \param req FastCGI request. + * + * \return Status code. 0 is success. + */ +static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req); + +/*! + * Read FastCGI request body. + * \param dest Destination buffer. + * \param req FastCGI request. + * \return Status code. 0 is success. + */ +static int read_fcgx_req_body(char **dest, FCGX_Request *req); + +/*! + * \brief Sends response to client. + * \param res Response instance. + * \param out Output stream. + */ +static void send_response(rpd_res *res, FCGX_Stream *out); + +int rpd_fcgi_server_start(rpd_app *app, const char *sock_path) +{ + FCGX_Init(); + int sock_id = 0; + if ((sock_id = FCGX_OpenSocket(sock_path, 10)) < 0) { + return 1; + } + + thread_data data = { app, sock_id }; +#ifdef MT_ENABLED + pthread_t threads[NTHREADS]; + for (int i = 0; i < NTHREADS; i++) { + pthread_create(&threads[i], 0, &listen_requests, (void *) &data); + pthread_join(threads[i], 0); + } +#else + listen_requests((void *) &data); +#endif + + return 0; +} + +static void *listen_requests(void *data) +{ + thread_data *hdata = (thread_data *) data; + FCGX_Request req; + if (FCGX_InitRequest(&req, hdata->sock_id, 0)) + return 0; + + hdata->app->running = 1; + while (hdata->app->running) { +#ifdef MT_ENABLED + static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&accept_mutex); +#endif + + int rc = FCGX_Accept_r(&req); + +#ifdef MT_ENABLED + pthread_mutex_unlock(&accept_mutex); +#endif + + if (rc < 0) + break; + +#ifdef MT_ENABLED + static pthread_mutex_t handle_mutex = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&handle_mutex); +#endif + + handle_request(hdata->app, &req); + +#ifdef MT_ENABLED + pthread_mutex_unlock(&handle_mutex); +#endif + + FCGX_Finish_r(&req); + } + return NULL; +} + +static void handle_request(rpd_app *app, FCGX_Request *fcgx_req) +{ + rpd_req *req; + rpd_res *res; + + req = (rpd_req *) malloc(sizeof(rpd_req)); + res = (rpd_res *) malloc(sizeof(rpd_res)); + + fcgx_to_rpd_req(req, fcgx_req); + rpd_res_init(res); + + rpd_app_handle_request(app, req, res); + send_response(res, fcgx_req->out); + + rpd_req_cleanup(req); + rpd_res_cleanup(res); + free(req); + free(res); +} + +static void send_response(rpd_res *res, FCGX_Stream *out) +{ + char *buff; + if (rpd_res_str(&buff, res)) { + FCGX_PutS("Status: 500\r\n\r\n", out); + return; + } + + FCGX_PutS(buff, out); + free(buff); +} + +static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req) +{ + rpd_query_parse(&dest->query, FCGX_GetParam("QUERY_STRING", req->envp)); + + dest->method = rpd_req_smethod(FCGX_GetParam("REQUEST_METHOD", req->envp)); + rpd_url_parse(&dest->path, FCGX_GetParam("DOCUMENT_URI", req->envp)); + dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp); + dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp); + rpd_keyval_init(&dest->params, 0); + + if (dest->method != GET) { + if (read_fcgx_req_body(&dest->body, req)) { + return 2; + } + } else { + dest->body = NULL; + } + + return 0; +} + +static int read_fcgx_req_body(char **dest, FCGX_Request *req) +{ + char *clen = FCGX_GetParam("CONTENT_LENGTH", req->envp); + if (!clen) + return 1; + size_t len = atoll(clen); + + *dest = (char *) malloc(sizeof(char) * (len + 1)); + if (!*dest) + return 2; + + *dest[len] = '\0'; + FCGX_GetStr(*dest, len, req->in); + + return 0; +}