Version 0.3.0 #1

Merged
vilor merged 9 commits from dev into master 2 years ago
  1. 13
      .clangd
  2. 10
      .drone.yml
  3. 16
      Makefile
  4. 23
      README.md
  5. 110
      c/app.c
  6. 5
      c/keyval.c
  7. 8
      c/query.c
  8. 40
      c/request.c
  9. 63
      c/response.c
  10. 10
      c/url.c
  11. 29
      c/utils.c
  12. 4
      c/utils.h
  13. 20
      config.mk
  14. 2
      docs/src/development.dox
  15. 18
      docs/src/get_started.dox
  16. 10
      docs/src/index.dox
  17. 6
      examples/c/example.c
  18. 10
      examples/c/minimal.c
  19. 5
      examples/cxx/example.cxx
  20. 6
      examples/cxx/minimal.cxx
  21. 27
      include/App.hxx
  22. 6
      include/Query.hxx
  23. 1
      include/Response.hxx
  24. 13
      include/app.h
  25. 3
      include/request.h
  26. 28
      include/response.h
  27. 31
      include/servers/fcgi.h
  28. 30
      include/servers/tcp.h
  29. 188
      servers/fcgi.c
  30. 102
      servers/tcp.c

13
.clangd

@ -1,14 +1,19 @@
CompileFlags: CompileFlags:
Add: [-Wall, -pedantic, -I../include, -DEXTENSIONS_INJA, Add:
-DDIST_PATH="/var/www/html"] - -Wall
- -pedantic
- -I../include
- -DMT_ENABLED
- -DNTHREADS=8
- -DEXTENSIONS_INJA,
- -DDIST_PATH="/var/www/html"
--- ---
# Specific C sources # Specific C sources
If: If:
PathMatch: [.*\.c, .*\.h] PathMatch: [.*\.c, .*\.h]
CompileFlags: CompileFlags:
Add: [-std=gnu99] Add: [-std=c99]
--- ---

10
.drone.yml

@ -14,7 +14,9 @@ steps:
image: gcc:11-bullseye image: gcc:11-bullseye
commands: commands:
- apt update -y - apt update -y
- apt install -y libfcgi-dev libfcgi-bin doxygen - apt install -y libfcgi-dev libfcgi-bin doxygen git
- git clone https://github.com/cesanta/mongoose.git
- cd mongoose && make install && cd ..
- make all DEBUG=1 - make all DEBUG=1
- make docs - make docs
@ -38,6 +40,7 @@ steps:
target: target:
from_secret: deploy_docs_dev_target from_secret: deploy_docs_dev_target
source: docs/dist/html/ source: docs/dist/html/
delete: true
--- ---
@ -57,7 +60,9 @@ steps:
image: gcc:11-bullseye image: gcc:11-bullseye
commands: commands:
- apt update -y - apt update -y
- apt install -y libfcgi-dev libfcgi-bin doxygen - apt install -y libfcgi-dev libfcgi-bin doxygen git
- git clone https://github.com/cesanta/mongoose.git
- cd mongoose && make install && cd ..
- make all DEBUG=1 - make all DEBUG=1
- make docs - make docs
@ -81,3 +86,4 @@ steps:
target: target:
from_secret: deploy_docs_target from_secret: deploy_docs_target
source: docs/dist/html/ source: docs/dist/html/
delete: true

16
Makefile

@ -2,6 +2,13 @@ include config.mk
CXXSRC := $(shell find cxx -type f -name '*.cxx') CXXSRC := $(shell find cxx -type f -name '*.cxx')
CSRC += $(shell find c -type f -name '*.c') CSRC += $(shell find c -type f -name '*.c')
ifeq ($(TCP_SERVER), 1)
CSRC += servers/tcp.c
endif
ifeq ($(FCGI_SERVER), 1)
CSRC += servers/fcgi.c
endif
CXXOBJ := $(CXXSRC:.cxx=.o) CXXOBJ := $(CXXSRC:.cxx=.o)
COBJ := $(CSRC:.c=.o) COBJ := $(CSRC:.c=.o)
@ -29,7 +36,7 @@ install: all
ln -s $(PREFIX)/lib/librapida.so.$(VERSION) $(PREFIX)/lib/librapida.so ln -s $(PREFIX)/lib/librapida.so.$(VERSION) $(PREFIX)/lib/librapida.so
ln -s $(PREFIX)/lib/librapidaxx.so.$(VERSION) $(PREFIX)/lib/librapidaxx.so ln -s $(PREFIX)/lib/librapidaxx.so.$(VERSION) $(PREFIX)/lib/librapidaxx.so
mkdir -p $(PREFIX)/include/rapida mkdir -p $(PREFIX)/include/rapida
cp -f include/* $(PREFIX)/include/rapida cp -rf include/* $(PREFIX)/include/rapida
uninstall: uninstall:
rm -f $(PREFIX)/lib/librapida.a rm -f $(PREFIX)/lib/librapida.a
@ -58,6 +65,9 @@ librapida.a: $(COBJ)
librapidaxx.a: $(COBJ) $(CXXOBJ) librapidaxx.a: $(COBJ) $(CXXOBJ)
ar rvs $@ $^ ar rvs $@ $^
servers/%.o: servers/%.c
$(CC) $(CFLAGS) -fPIC $< -c -o $@
c/%.o: c/%.c c/%.o: c/%.c
$(CC) $(CFLAGS) -fPIC $< -c -o $@ $(CC) $(CFLAGS) -fPIC $< -c -o $@
@ -82,9 +92,7 @@ docs:
#target Format code #target Format code
format: format:
clang-format -Werror -i $(CSRC) $(CXXSRC) clang-format -Werror -i **/*.c* **/*.h*
clang-format -Werror -i examples/*.c*
clang-format -Werror -i tests/*.c* tests/*.h*
#target Show this help #target Show this help
help: help:

23
README.md

@ -2,29 +2,34 @@ Rapida
====== ======
[![Build Status](http://drone.vilor.one/api/badges/Rapida/rapida/status.svg)](http://drone.vilor.one/Rapida/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) [![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 Dependencies
------------ ------------
* libfcgi * mongoose (if you need a TCP server)
* libfcgi (if you need a FastCGI server)
* doxygen (to make docs) * doxygen (to make docs)
* catch2 (to run tests) * catch2 (to run tests)
Extensions Installation
---------- ------------
* [inja](https://github.com/pantor/inja) (template rendering,
requires [nlohmann/json](https://github.com/nlohmann/json))
Building
--------
You need: You need:
* GNU Make * GNU Make
* C compiler with C99 support * C compiler with C99 support
* C++ compiler with C++98 and C++17 support * C++ compiler with C++98 and C++17 support
```sh
make install clean
```
Run `make help` to see targets, flags and arguments. Run `make help` to see targets, flags and arguments.
Extensions
----------
* [inja](https://github.com/pantor/inja) (template rendering,
requires [nlohmann/json](https://github.com/nlohmann/json))
Examples Examples
-------- --------
See [examples](examples). See [examples](examples).

110
c/app.c

@ -5,24 +5,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.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. * \brief Selects a route handler based on requested path.
* \param app Application instance. * \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); 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->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; return 0;
} }
@ -79,69 +35,15 @@ int rpd_app_add_route(rpd_app *app, const char *path, rpd_route_cb cb,
return 0; 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; rpd_route *route = routes_fabric(app, req);
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);
if (!route) { if (!route) {
res->status = rpd_res_st_not_found;
return; return;
} }
route->cb(&req, &res, route->userdata); route->cb(req, res, route->userdata);
rpd_res_send(&res);
rpd_req_cleanup(&req);
rpd_res_cleanup(&res);
} }
static rpd_route *routes_fabric(rpd_app *app, rpd_req *req) static rpd_route *routes_fabric(rpd_app *app, rpd_req *req)

5
c/keyval.c

@ -2,6 +2,7 @@
/* Copyright 2022 Ivan Polyakov */ /* Copyright 2022 Ivan Polyakov */
#include "keyval.h" #include "keyval.h"
#include "utils.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -57,8 +58,8 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value)
keyval->size++; keyval->size++;
} }
item->key = strdup(key); item->key = rpd_strdup(key);
item->val = strdup(value); item->val = rpd_strdup(value);
return item->key && item->val ? 0 : 2; return item->key && item->val ? 0 : 2;
} }

8
c/query.c

@ -17,7 +17,7 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
return 0; return 0;
} }
char *query = strdup(src); char *query = rpd_strdup(src);
if (!query) if (!query)
return 1; return 1;
@ -28,7 +28,7 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
char *tokens = query, *p = query; char *tokens = query, *p = query;
int i = 0; int i = 0;
while ((p = strsep(&tokens, "&\n")) && i < len) { while ((p = rpd_strsep(&tokens, "&\n")) && i < len) {
char *param, *val; char *param, *val;
if (*p == '=') if (*p == '=')
@ -37,8 +37,8 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
val = strtok(NULL, "="); val = strtok(NULL, "=");
if (param) { if (param) {
dest->items[i].key = strdup(param); dest->items[i].key = rpd_strdup(param);
dest->items[i].val = val ? strdup(val) : NULL; dest->items[i].val = val ? rpd_strdup(val) : NULL;
i++; i++;
} }
} }

40
c/request.c

@ -5,8 +5,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.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) enum rpd_req_methods rpd_req_smethod(const char *method)
{ {
if (!strcmp(method, "GET")) if (!strcmp(method, "GET"))
@ -30,44 +28,6 @@ enum rpd_req_methods rpd_req_smethod(const char *method)
return UNKNOWN; 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) void rpd_req_cleanup(rpd_req *req)
{ {
rpd_keyval_cleanup(&req->params); rpd_keyval_cleanup(&req->params);

63
c/response.c

@ -6,12 +6,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
static size_t calc_res_buff_sz(const rpd_res *res); static size_t calc_res_headers_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->status = rpd_res_st_ok;
dest->location = dest->content_type = NULL; dest->location = dest->content_type = NULL;
dest->body = NULL; dest->body = NULL;
@ -19,27 +17,54 @@ void rpd_res_init(rpd_res *dest, FCGX_Request *req)
rpd_keyval_init(&dest->cookie, 0); rpd_keyval_init(&dest->cookie, 0);
} }
int rpd_res_send(rpd_res *res) int rpd_res_headers_str(char **dest, const rpd_res *src)
{ {
size_t size = calc_res_buff_sz(res); size_t size = calc_res_headers_sz(src);
char *ptr;
char *buff = (char *) malloc(sizeof(char) * size); *dest = (char *) malloc(sizeof(char) * size);
if (!buff) { if (!*dest) {
perror("malloc");
return 1; return 1;
} }
/* header */ ptr = *dest;
char *ptr = buff; if (src->content_type) {
ptr += sprintf(ptr, "Status: %d\r\n", res->status); ptr += sprintf(ptr, "Content-Type: %s\r\n", src->content_type);
}
if (res->content_type) { if (src->location) {
ptr += sprintf(ptr, "Content-Type: %s\r\n", res->content_type); ptr += sprintf(ptr, "Location: %s\r\n", src->location);
} }
if (res->location) { return 0;
ptr += sprintf(ptr, "Location: %s\r\n", res->location); }
int rpd_res_str(char **dest, const rpd_res *res)
{
size_t headers_size, size;
char *ptr, *headers;
size = headers_size = calc_res_headers_sz(res);
if (res->body)
size += 2 + strlen(res->body);
*dest = (char *) malloc(sizeof(char) * size);
if (!*dest) {
perror("malloc");
return 1;
} }
/* header */
ptr = *dest;
ptr += sprintf(ptr, "Status: %d\r\n", res->status);
if (rpd_res_headers_str(&headers, res)) {
return 1;
}
memcpy(ptr, headers, headers_size);
free(headers);
ptr += headers_size;
memcpy(ptr, "\r\n", 2); memcpy(ptr, "\r\n", 2);
ptr += 2; ptr += 2;
@ -50,14 +75,12 @@ int rpd_res_send(rpd_res *res)
ptr += bodylen; ptr += bodylen;
} }
FCGX_PutS(buff, res->out);
free(buff);
return 0; return 0;
} }
static size_t calc_res_buff_sz(const rpd_res *res) static size_t calc_res_headers_sz(const rpd_res *res)
{ {
size_t size = res->body ? strlen(res->body) + 2 : 0; size_t size = 0;
size += strlen("Status: \r\n") + 3; size += strlen("Status: \r\n") + 3;
if (res->location) { if (res->location) {
@ -76,8 +99,6 @@ static size_t calc_res_buff_sz(const rpd_res *res)
} }
} }
size += 2;
return size; return size;
} }

10
c/url.c

@ -23,11 +23,11 @@ int rpd_url_parse(rpd_url *dest, const char *src)
i = 0; i = 0;
char *tmp, *token; char *tmp, *token;
tmp = strdup(src); tmp = rpd_strdup(src);
while ((token = strsep(&tmp, "/"))) { while ((token = rpd_strsep(&tmp, "/"))) {
if (!strlen(token)) if (!strlen(token))
continue; continue;
dest->parts[i] = strdup(token); dest->parts[i] = rpd_strdup(token);
i++; i++;
} }
free(tmp); free(tmp);
@ -65,7 +65,7 @@ int rpd_url_params_parse_keys(rpd_keyval *dest, const rpd_url *tpl)
for (int i = 0; i < tpl->parts_len; i++) { for (int i = 0; i < tpl->parts_len; i++) {
if (*tpl->parts[i] == ':') { if (*tpl->parts[i] == ':') {
dest->items[dest->size].key = strdup(tpl->parts[i] + 1); dest->items[dest->size].key = rpd_strdup(tpl->parts[i] + 1);
dest->size++; dest->size++;
} }
} }
@ -78,7 +78,7 @@ int rpd_url_params_parse_vals(rpd_keyval *dest, const rpd_url *url, const rpd_ur
int i = 0, j = 0; int i = 0, j = 0;
while (i < tpl->parts_len) { while (i < tpl->parts_len) {
if (*tpl->parts[i] == ':') { if (*tpl->parts[i] == ':') {
dest->items[j++].val = strdup(url->parts[i]); dest->items[j++].val = rpd_strdup(url->parts[i]);
} }
i++; i++;
} }

29
c/utils.c

@ -2,6 +2,8 @@
/* Copyright 2022 Ivan Polyakov */ /* Copyright 2022 Ivan Polyakov */
#include "utils.h" #include "utils.h"
#include <stdlib.h>
#include <string.h>
int count_char_entries(const char *str, char ch) int count_char_entries(const char *str, char ch)
{ {
@ -14,3 +16,30 @@ int count_char_entries(const char *str, char ch)
} }
return cnt; return cnt;
} }
char *rpd_strdup(const char *src)
{
size_t size = strlen(src) + 1;
char *dest = (char *) malloc(sizeof(char) * size);
if (!dest) {
return NULL;
}
return memcpy(dest, src, size);
}
char *rpd_strsep(char **str, const char *sep)
{
char *s = *str, *end;
if (!s) {
return NULL;
}
end = s + strcspn(s, sep);
if (*end) {
*end++ = 0;
} else {
end = 0;
}
*str = end;
return s;
}

4
c/utils.h

@ -6,4 +6,8 @@
int count_char_entries(const char *str, char ch); int count_char_entries(const char *str, char ch);
char *rpd_strdup(const char *src);
char *rpd_strsep(char **str, const char *sep);
#endif /* RAPIDA_UTILS_H_ENTRY */ #endif /* RAPIDA_UTILS_H_ENTRY */

20
config.mk

@ -1,13 +1,13 @@
VERSION=0.2.1 VERSION=0.3.0
#arg Installation prefix #arg Installation prefix
PREFIX=/usr/local PREFIX=/usr/local
CC=gcc CC=gcc
CFLAGS=-std=gnu99 -pedantic -Iinclude CFLAGS=-std=c99 -pedantic -Iinclude -DRPD_VERSION=\"$(VERSION)\"
CXX=c++ CXX=c++
CXXFLAGS=-pedantic -Iinclude CXXFLAGS=-pedantic -Iinclude
CXXSTD=-ansi CXXSTD=-ansi
LDFLAGS=-lfcgi LDFLAGS=
#flag Debug mode #flag Debug mode
DEBUG ?= 0 DEBUG ?= 0
@ -19,13 +19,17 @@ else
CXXFLAGS+=-O3 CXXFLAGS+=-O3
endif endif
#flag Add TCP server
#arg Additional CXX flags TCP_SERVER ?= 1
ADD_CXXFLAGS = ifeq ($(TCP_SERVER), 1)
ifdef ADD_CXXFLAGS LDFLAGS += -lmongoose
CXXFLAGS+=$(ADD_CXXFLAGS)
endif endif
#flag Add FastCGI server
FCGI_SERVER ?= 1
ifeq ($(FCGI_SERVER), 1)
LDFLAGS += -lfcgi
endif
#flag Enable inja extension #flag Enable inja extension
EXTENSIONS_INJA ?= 0 EXTENSIONS_INJA ?= 0

2
docs/src/development.dox

@ -2,6 +2,8 @@
\page development Development \page development Development
[TOC]
Overview Overview
-------- --------
We use: We use:

18
docs/src/get_started.dox

@ -2,6 +2,8 @@
\page get_started Get Started \page get_started Get Started
[TOC]
Installation Installation
------------ ------------
Run the following commands: Run the following commands:
@ -19,7 +21,6 @@ C example:
C++ example: C++ example:
------------ ------------
\include minimal.cxx \include minimal.cxx
More examples you can find at http://git.vilor.one/rapida/rapida/examples More examples you can find at http://git.vilor.one/rapida/rapida/examples
@ -37,4 +38,19 @@ gcc myapp.c -lrapida # link C library
g++ myapp.cxx -lrapidaxx # link C++ library g++ myapp.cxx -lrapidaxx # link C++ library
``` ```
Servers
-------
Rapida has focused on the implementation of the framework,
and servers are logically separated form the core.
By default, when you building Rapida, all servers will be linked
to the library, but you can change this by setting the _make_ flags.
For example, to disable FastCGI server, you need to pass `FCGI_SERVER=0`
flag to `make` command like this:
```
make FCGI_SERVER=0
```
To see other flags you can run `make help`.
*/ */

10
docs/src/index.dox

@ -1,11 +1,11 @@
/** /**
\mainpage Rapida Manual \mainpage Manual
Rapida is the fast web framework written in C and C++. Web framework written in C and C++.
Table of contents: [TOC]
1. \subpage get_started - \subpage get_started
2. \subpage development - \subpage development
*/ */

6
examples/c/example.c

@ -1,4 +1,5 @@
#include "../../include/rapida.h" #include "../../include/rapida.h"
#include "../../include/servers/tcp.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -47,10 +48,9 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata)
int main() int main()
{ {
rpd_app app; 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_add_route(&app, "/products/:category/:id", &products_handler, NULL);
rpd_app_start(&app); return rpd_tcp_server_start(&app, "http://localhost:8080");
return 0;
} }

10
examples/c/minimal.c

@ -1,7 +1,6 @@
#include "../../include/rapida.h" #include "../../include/rapida.h"
#include "../../include/servers/tcp.h"
#include <string.h> /* for strdup() */ #include <string.h> /* for strdup() */
#include <stdio.h>
/* /*
* \brief Process home page request. * \brief Process home page request.
@ -15,7 +14,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata)
{ {
/* Check request method */ /* Check request method */
switch (req->method) { switch (req->method) {
case GET: case HEAD:
/* Process GET request */ /* Process GET request */
res->status = rpd_res_st_ok; res->status = rpd_res_st_ok;
@ -24,6 +23,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata)
* Rapida will free it all. * Rapida will free it all.
*/ */
res->content_type = strdup("text/plain"); res->content_type = strdup("text/plain");
case GET:
res->body = strdup("Hello World!"); res->body = strdup("Hello World!");
break; break;
default: default:
@ -44,11 +44,11 @@ int main()
{ {
rpd_app app; rpd_app app;
/* Initialize application. */ /* Initialize application. */
rpd_app_create(&app, "/tmp/webapp.socket"); rpd_app_create(&app);
/* Add home "/" page handler. */ /* Add home "/" page handler. */
rpd_app_add_route(&app, "/", &home_page_handler, NULL); rpd_app_add_route(&app, "/", &home_page_handler, NULL);
/* Run the application and return its status code. */ /* Run the application and return its status code. */
return rpd_app_start(&app); return rpd_tcp_server_start(&app, "http://localhost:8080");
} }

5
examples/cxx/example.cxx

@ -1,4 +1,5 @@
#include "../../include/rapida.hxx" #include "../../include/rapida.hxx"
#include "../../include/servers/tcp.h"
#include <sstream> #include <sstream>
class ProductRoute : public rpd::Route { class ProductRoute : public rpd::Route {
@ -40,7 +41,7 @@ protected:
int main() int main()
{ {
rpd::App app("/tmp/rapida.example.socket"); rpd::App app;
/* /*
* `category` and `id` are dynamic parameters * `category` and `id` are dynamic parameters
@ -48,5 +49,5 @@ int main()
*/ */
app.add_route("/products/:category/:id", new ProductRoute()); app.add_route("/products/:category/:id", new ProductRoute());
return app.start(); return rpd_tcp_server_start(app.c_app(), "http://localhost:8080");
} }

6
examples/cxx/minimal.cxx

@ -1,4 +1,6 @@
#include "../../include/rapida.hxx" #include "../../include/rapida.hxx"
#include "../../include/servers/tcp.h"
/* /*
* \brief Home page route handler. * \brief Home page route handler.
@ -33,9 +35,9 @@ protected:
int main() int main()
{ {
rpd::App app("/tmp/webapp.socket"); rpd::App app;
app.add_route("/", new Home()); app.add_route("/", new Home());
return app.start(); return rpd_tcp_server_start(app.c_app(), "http://localhost:8080");
} }

27
include/App.hxx

@ -16,29 +16,24 @@ namespace rpd {
class App { class App {
public: public:
/*! /*!
* Creates an rpd app and initializes FastCGI. * Creates a Rapida application instance.
* \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. * \brief Returns C implementation of the application.
*/ *
int start() * Usable when you need to start C server.
{ *
return rpd_app_start(&app); * \return C implementation of the application.
}
/*!
* \brief Stops the requests handling loop.
*/ */
void stop() rpd_app *c_app() const
{ {
app.running = false; return (rpd_app *) &app;
} }
/*! /*!

6
include/Query.hxx

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

1
include/Response.hxx

@ -114,7 +114,6 @@ public:
* \brief Render data to HTML template. * \brief Render data to HTML template.
* \param path Path to HTML template relative to dist location. * \param path Path to HTML template relative to dist location.
* \param data Template data to interpolate. * \param data Template data to interpolate.
* \param out FastCGI output stream.
*/ */
void render(const char *path, nlohmann::json data); void render(const char *path, nlohmann::json data);
#endif #endif

13
include/app.h

@ -19,8 +19,6 @@ extern "C" {
*/ */
typedef struct { typedef struct {
int running; /**< Application will be running while this flag is true. */ 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. */ int routes_len; /**< Length of the rpd_app::routes array. */
rpd_route *routes; /**< Array of the active routes. */ rpd_route *routes; /**< Array of the active routes. */
} rpd_app; } rpd_app;
@ -29,19 +27,18 @@ typedef struct {
* \brief Creates Rapida application. * \brief Creates Rapida application.
* *
* \param app Pointer to application instance. * \param app Pointer to application instance.
* \param sock_path UNIX Socket path.
* *
* \return Status. 0 is success. * \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. * \param app Application instance.
* * \param req Request.
* \return Status. 0 is success. * \param res Response.
*/ */
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. * \brief Adds route to application.

3
include/request.h

@ -10,7 +10,6 @@
#include "query.h" #include "query.h"
#include "url.h" #include "url.h"
#include <fcgiapp.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -45,8 +44,6 @@ typedef struct {
rpd_keyval params; /**< Dynamic parameters. */ rpd_keyval params; /**< Dynamic parameters. */
} rpd_req; } rpd_req;
int rpd_req_parse(rpd_req *dest, FCGX_Request *req);
/*! /*!
* \brief Parser string request method to enumeration value. * \brief Parser string request method to enumeration value.
* *

28
include/response.h

@ -9,7 +9,7 @@
#define RAPIDA_RESPONSE_H_ENTRY #define RAPIDA_RESPONSE_H_ENTRY
#include "keyval.h" #include "keyval.h"
#include <fcgiapp.h> #include "request.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -86,7 +86,6 @@ enum rpd_res_statuses {
*/ */
typedef struct { typedef struct {
enum rpd_res_statuses status; /**< Response status code. */ enum rpd_res_statuses status; /**< Response status code. */
FCGX_Stream *out; /**< Output stream. */
char *location; /**< Location field. */ char *location; /**< Location field. */
char *content_type; /**< Content type. */ char *content_type; /**< Content type. */
char *body; /**< Response body. */ char *body; /**< Response body. */
@ -94,18 +93,31 @@ typedef struct {
} rpd_res; } rpd_res;
/*! /*!
* \brief Initialize response data from FastCGI request. * \brief Initialize response data.
* *
* \param dest Response instance. * \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. * \brief Write response headers to string buffer.
* \param res Response instance. *
* \param dest Destination buffer.
* \param src Response.
*
* \return Status code. 0 is success.
*/
int rpd_res_headers_str(char **dest, const rpd_res *src);
/*!
* \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. * \brief Cleans response instance.

31
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 <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 */

30
include/servers/tcp.h

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2022 Ivan Polyakov */
/*!
* \file tcp.h
* \brief Rapida TCP server
*/
#ifndef RAPIDA_SERVERS_TCP_H_ENTRY
#define RAPIDA_SERVERS_TCP_H_ENTRY
#include "../app.h"
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \brief Starts Rapida TCP server.
* \param app Application instance.
* \param addr URL address to listen.
*
* \return Status. 0 is success.
*/
int rpd_tcp_server_start(rpd_app *app, const char *addr);
#ifdef __cplusplus
}
#endif
#endif /* RAPIDA_SERVERS_TCP_H_ENTRY */

188
servers/fcgi.c

@ -0,0 +1,188 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2022 Ivan Polyakov */
#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;
}

102
servers/tcp.c

@ -0,0 +1,102 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2022 Ivan Polyakov */
#include "servers/tcp.h"
#include "../c/utils.h"
#include <mongoose.h>
#include <signal.h>
/* Handle interrupts, like Ctrl-C */
static int s_signo;
static void signal_handler(int signo)
{
s_signo = signo;
}
static int mg_str_alloc(char **dest, const struct mg_str str)
{
*dest = (char *) realloc(*dest, sizeof(char) * (str.len + 1));
if (!*dest) {
perror("realloc");
return 1;
}
memcpy(*dest, str.ptr, str.len);
(*dest)[str.len] = '\0';
return 0;
}
static int mg_to_rpd_req(rpd_req *req, struct mg_http_message *msg)
{
char *tmp = NULL;
if (mg_str_alloc(&tmp, msg->method))
return 1;
req->method = rpd_req_smethod(tmp);
req->body = msg->body.len ? rpd_strdup(msg->body.ptr) : NULL;
if (mg_str_alloc(&tmp, msg->uri))
return 1;
rpd_url_parse(&req->path, tmp);
rpd_keyval_init(&req->query, 0);
if (msg->query.len) {
if (mg_str_alloc(&tmp, msg->query))
return 1;
rpd_query_parse(&req->query, tmp);
}
free(tmp);
return 0;
}
static void handle_request(struct mg_connection *conn, int ev, void *ev_data, void *app)
{
if (ev == MG_EV_HTTP_MSG) {
rpd_req *req;
rpd_res *res;
char *headers_buff;
req = (rpd_req *) malloc(sizeof(rpd_req));
res = (rpd_res *) malloc(sizeof(rpd_res));
mg_to_rpd_req(req, (struct mg_http_message *) ev_data);
rpd_res_init(res);
rpd_app_handle_request((rpd_app *) app, req, res);
rpd_res_headers_str(&headers_buff, res);
mg_http_reply(conn, res->status, headers_buff, res->body ? res->body : "");
free(headers_buff);
rpd_req_cleanup(req);
rpd_res_cleanup(res);
free(req);
free(res);
}
}
int rpd_tcp_server_start(rpd_app *app, const char *addr)
{
struct mg_mgr mgr;
struct mg_connection *c;
app->running = 1;
/* setup signals handler */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
mg_mgr_init(&mgr);
if ((c = mg_http_listen(&mgr, addr, handle_request, app)) == NULL) {
MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT", addr));
return EXIT_FAILURE;
}
MG_INFO(("Mongoose version : v%s", MG_VERSION));
MG_INFO(("Rapida version : v%s", RPD_VERSION));
MG_INFO(("Listening on : %s", addr));
while (s_signo == 0 && app->running)
mg_mgr_poll(&mgr, 1000);
app->running = 0;
MG_INFO(("Exiting on signal %d", s_signo));
mg_mgr_free(&mgr);
return 0;
}
Loading…
Cancel
Save