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 @@ @@ -1,14 +1,19 @@
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
If:
PathMatch: [.*\.c, .*\.h]
CompileFlags:
Add: [-std=gnu99]
Add: [-std=c99]
---

10
.drone.yml

@ -14,7 +14,9 @@ steps: @@ -14,7 +14,9 @@ steps:
image: gcc:11-bullseye
commands:
- 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 docs
@ -38,6 +40,7 @@ steps: @@ -38,6 +40,7 @@ steps:
target:
from_secret: deploy_docs_dev_target
source: docs/dist/html/
delete: true
---
@ -57,7 +60,9 @@ steps: @@ -57,7 +60,9 @@ steps:
image: gcc:11-bullseye
commands:
- 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 docs
@ -81,3 +86,4 @@ steps: @@ -81,3 +86,4 @@ steps:
target:
from_secret: deploy_docs_target
source: docs/dist/html/
delete: true

16
Makefile

@ -2,6 +2,13 @@ include config.mk @@ -2,6 +2,13 @@ include config.mk
CXXSRC := $(shell find cxx -type f -name '*.cxx')
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)
COBJ := $(CSRC:.c=.o)
@ -29,7 +36,7 @@ install: all @@ -29,7 +36,7 @@ install: all
ln -s $(PREFIX)/lib/librapida.so.$(VERSION) $(PREFIX)/lib/librapida.so
ln -s $(PREFIX)/lib/librapidaxx.so.$(VERSION) $(PREFIX)/lib/librapidaxx.so
mkdir -p $(PREFIX)/include/rapida
cp -f include/* $(PREFIX)/include/rapida
cp -rf include/* $(PREFIX)/include/rapida
uninstall:
rm -f $(PREFIX)/lib/librapida.a
@ -58,6 +65,9 @@ librapida.a: $(COBJ) @@ -58,6 +65,9 @@ librapida.a: $(COBJ)
librapidaxx.a: $(COBJ) $(CXXOBJ)
ar rvs $@ $^
servers/%.o: servers/%.c
$(CC) $(CFLAGS) -fPIC $< -c -o $@
c/%.o: c/%.c
$(CC) $(CFLAGS) -fPIC $< -c -o $@
@ -82,9 +92,7 @@ docs: @@ -82,9 +92,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:

23
README.md

@ -2,29 +2,34 @@ Rapida @@ -2,29 +2,34 @@ 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
* mongoose (if you need a TCP server)
* libfcgi (if you need a FastCGI server)
* doxygen (to make docs)
* catch2 (to run tests)
Extensions
----------
* [inja](https://github.com/pantor/inja) (template rendering,
requires [nlohmann/json](https://github.com/nlohmann/json))
Building
--------
Installation
------------
You need:
* GNU Make
* C compiler with C99 support
* C++ compiler with C++98 and C++17 support
```sh
make install clean
```
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
--------
See [examples](examples).

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)

5
c/keyval.c

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
/* Copyright 2022 Ivan Polyakov */
#include "keyval.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -57,8 +58,8 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value) @@ -57,8 +58,8 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value)
keyval->size++;
}
item->key = strdup(key);
item->val = strdup(value);
item->key = rpd_strdup(key);
item->val = rpd_strdup(value);
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) @@ -17,7 +17,7 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
return 0;
}
char *query = strdup(src);
char *query = rpd_strdup(src);
if (!query)
return 1;
@ -28,7 +28,7 @@ int rpd_query_parse(rpd_keyval *dest, const char *src) @@ -28,7 +28,7 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
char *tokens = query, *p = query;
int i = 0;
while ((p = strsep(&tokens, "&\n")) && i < len) {
while ((p = rpd_strsep(&tokens, "&\n")) && i < len) {
char *param, *val;
if (*p == '=')
@ -37,8 +37,8 @@ int rpd_query_parse(rpd_keyval *dest, const char *src) @@ -37,8 +37,8 @@ int rpd_query_parse(rpd_keyval *dest, const char *src)
val = strtok(NULL, "=");
if (param) {
dest->items[i].key = strdup(param);
dest->items[i].val = val ? strdup(val) : NULL;
dest->items[i].key = rpd_strdup(param);
dest->items[i].val = val ? rpd_strdup(val) : NULL;
i++;
}
}

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);

63
c/response.c

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

10
c/url.c

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

29
c/utils.c

@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
/* Copyright 2022 Ivan Polyakov */
#include "utils.h"
#include <stdlib.h>
#include <string.h>
int count_char_entries(const char *str, char ch)
{
@ -14,3 +16,30 @@ int count_char_entries(const char *str, char ch) @@ -14,3 +16,30 @@ int count_char_entries(const char *str, char ch)
}
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 @@ @@ -6,4 +6,8 @@
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 */

20
config.mk

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

2
docs/src/development.dox

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

18
docs/src/get_started.dox

@ -2,6 +2,8 @@ @@ -2,6 +2,8 @@
\page get_started Get Started
[TOC]
Installation
------------
Run the following commands:
@ -19,7 +21,6 @@ C example: @@ -19,7 +21,6 @@ C example:
C++ example:
------------
\include minimal.cxx
More examples you can find at http://git.vilor.one/rapida/rapida/examples
@ -37,4 +38,19 @@ gcc myapp.c -lrapida # link C library @@ -37,4 +38,19 @@ gcc myapp.c -lrapida # 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 @@ @@ -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:
1. \subpage get_started
2. \subpage development
[TOC]
- \subpage get_started
- \subpage development
*/

6
examples/c/example.c

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

10
examples/c/minimal.c

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
#include "../../include/rapida.h"
#include "../../include/servers/tcp.h"
#include <string.h> /* for strdup() */
#include <stdio.h>
/*
* \brief Process home page request.
@ -15,7 +14,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -15,7 +14,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata)
{
/* Check request method */
switch (req->method) {
case GET:
case HEAD:
/* Process GET request */
res->status = rpd_res_st_ok;
@ -24,6 +23,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -24,6 +23,7 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata)
* Rapida will free it all.
*/
res->content_type = strdup("text/plain");
case GET:
res->body = strdup("Hello World!");
break;
default:
@ -44,11 +44,11 @@ int main() @@ -44,11 +44,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_tcp_server_start(&app, "http://localhost:8080");
}

5
examples/cxx/example.cxx

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
#include "../../include/rapida.hxx"
#include "../../include/servers/tcp.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_tcp_server_start(app.c_app(), "http://localhost:8080");
}

6
examples/cxx/minimal.cxx

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
#include "../../include/rapida.hxx"
#include "../../include/servers/tcp.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_tcp_server_start(app.c_app(), "http://localhost:8080");
}

27
include/App.hxx

@ -16,29 +16,24 @@ namespace rpd { @@ -16,29 +16,24 @@ namespace rpd {
class App {
public:
/*!
* Creates an rpd app and initializes FastCGI.
* \brief Constructor
* \param socket_path UNIX Socket path.
* Creates a Rapida application instance.
* \brief Default constructor
*/
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.

1
include/Response.hxx

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

13
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,18 @@ typedef struct { @@ -29,19 +27,18 @@ 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 Request.
* \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.

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.
*

28
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. */
@ -94,18 +93,31 @@ typedef struct { @@ -94,18 +93,31 @@ typedef struct {
} rpd_res;
/*!
* \brief Initialize response data from FastCGI request.
* \brief Initialize response data.
*
* \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 headers to string buffer.
*
* \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.

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

30
include/servers/tcp.h

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