Browse Source

fastcgi server is separated from the main library and is now optional

pull/1/head
Ivan Polyakov 2 years ago
parent
commit
d2eaecc786
  1. 11
      .clangd
  2. 8
      Makefile
  3. 5
      README.md
  4. 110
      c/app.c
  5. 40
      c/request.c
  6. 14
      c/response.c
  7. 14
      config.mk
  8. 2
      docs/src/index.dox
  9. 5
      examples/c/example.c
  10. 5
      examples/c/minimal.c
  11. 5
      examples/cxx/example.cxx
  12. 6
      examples/cxx/minimal.cxx
  13. 24
      include/App.hxx
  14. 6
      include/Query.hxx
  15. 12
      include/app.h
  16. 3
      include/request.h
  17. 15
      include/response.h
  18. 31
      include/servers/fcgi.h
  19. 185
      servers/fcgi.c

11
.clangd

@ -1,7 +1,12 @@ @@ -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

8
Makefile

@ -2,6 +2,10 @@ include config.mk @@ -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: @@ -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:

5
README.md

@ -2,12 +2,13 @@ Rapida @@ -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)

110
c/app.c

@ -5,24 +5,6 @@ @@ -5,24 +5,6 @@
#include <stdlib.h>
#include <string.h>
#ifdef MT_ENABLED
#include <pthread.h>
#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); @@ -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, @@ -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)

40
c/request.c

@ -5,8 +5,6 @@ @@ -5,8 +5,6 @@
#include <stdlib.h>
#include <string.h>
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) @@ -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);

14
c/response.c

@ -8,10 +8,8 @@ @@ -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) @@ -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) @@ -50,8 +48,6 @@ int rpd_res_send(rpd_res *res)
ptr += bodylen;
}
FCGX_PutS(buff, res->out);
free(buff);
return 0;
}

14
config.mk

@ -1,4 +1,4 @@ @@ -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 @@ -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 @@ -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.

2
docs/src/index.dox

@ -2,7 +2,7 @@ @@ -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

5
examples/c/example.c

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
#include "../../include/rapida.h"
#include "../../include/servers/fcgi.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -47,10 +48,10 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -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;
}

5
examples/c/minimal.c

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
#include "../../include/rapida.h"
#include "../../include/servers/fcgi.h"
#include <string.h> /* for strdup() */
#include <stdio.h>
@ -44,11 +45,11 @@ int main() @@ -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");
}

5
examples/cxx/example.cxx

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
#include "../../include/rapida.hxx"
#include "../../include/servers/fcgi.h"
#include <sstream>
class ProductRoute : public rpd::Route {
@ -40,7 +41,7 @@ protected: @@ -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() @@ -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");
}

6
examples/cxx/minimal.cxx

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
#include "../../include/rapida.hxx"
#include "../../include/servers/fcgi.h"
/*
* \brief Home page route handler.
@ -33,9 +35,9 @@ protected: @@ -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");
}

24
include/App.hxx

@ -17,28 +17,24 @@ class App { @@ -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;
}
/*!

6
include/Query.hxx

@ -20,7 +20,8 @@ public: @@ -20,7 +20,8 @@ public:
*/
Query()
: KeyVal()
{}
{
}
/*!
* \brief Constructor.
@ -31,7 +32,8 @@ public: @@ -31,7 +32,8 @@ public:
*/
Query(rpd_keyval *keyval)
: KeyVal(keyval)
{}
{
}
/*!
* \brief Parse query string into the key-val pairs.

12
include/app.h

@ -19,8 +19,6 @@ extern "C" { @@ -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 { @@ -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.

3
include/request.h

@ -10,7 +10,6 @@ @@ -10,7 +10,6 @@
#include "query.h"
#include "url.h"
#include <fcgiapp.h>
#ifdef __cplusplus
extern "C" {
@ -45,8 +44,6 @@ typedef struct { @@ -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.
*

15
include/response.h

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
#define RAPIDA_RESPONSE_H_ENTRY
#include "keyval.h"
#include <fcgiapp.h>
#include "request.h"
#ifdef __cplusplus
extern "C" {
@ -86,7 +86,6 @@ enum rpd_res_statuses { @@ -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 { @@ -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.

31
include/servers/fcgi.h

@ -0,0 +1,31 @@ @@ -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 <fcgiapp.h>
#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 */

185
servers/fcgi.c

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
#include "../include/servers/fcgi.h"
#include <stdlib.h>
#ifdef MT_ENABLED
#include <pthread.h>
#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;
}
Loading…
Cancel
Save