diff --git a/Makefile b/Makefile index 003c797..12a3ebe 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ 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 diff --git a/README.md b/README.md index c8871f4..9844b1d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Web framework written in C and C++. Dependencies ------------ +* mongoose (if you need a TCP server) * libfcgi (if you need a FastCGI server) * doxygen (to make docs) * catch2 (to run tests) diff --git a/c/response.c b/c/response.c index 2bd1e7d..66c963a 100644 --- a/c/response.c +++ b/c/response.c @@ -6,7 +6,7 @@ #include #include -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) { @@ -17,27 +17,54 @@ void rpd_res_init(rpd_res *dest) rpd_keyval_init(&dest->cookie, 0); } +int rpd_res_headers_str(char **dest, const rpd_res *src) +{ + size_t size = calc_res_headers_sz(src); + char *ptr; + + *dest = (char *) malloc(sizeof(char) * size); + if (!*dest) { + perror("malloc"); + return 1; + } + + ptr = *dest; + if (src->content_type) { + ptr += sprintf(ptr, "Content-Type: %s\r\n", src->content_type); + } + + if (src->location) { + ptr += sprintf(ptr, "Location: %s\r\n", src->location); + } + + return 0; +} + int rpd_res_str(char **dest, const rpd_res *res) { - size_t size = calc_res_buff_sz(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 */ - char *ptr = *dest; + ptr = *dest; ptr += sprintf(ptr, "Status: %d\r\n", res->status); - if (res->content_type) { - ptr += sprintf(ptr, "Content-Type: %s\r\n", res->content_type); - } - - if (res->location) { - ptr += sprintf(ptr, "Location: %s\r\n", res->location); + 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; @@ -51,9 +78,9 @@ int rpd_res_str(char **dest, const rpd_res *res) 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) { @@ -72,8 +99,6 @@ static size_t calc_res_buff_sz(const rpd_res *res) } } - size += 2; - return size; } diff --git a/config.mk b/config.mk index 9f9c983..707cb00 100644 --- a/config.mk +++ b/config.mk @@ -1,9 +1,9 @@ -VERSION=0.2.2 +VERSION=0.3.0 #arg Installation prefix PREFIX=/usr/local CC=gcc -CFLAGS=-std=c99 -pedantic -Iinclude +CFLAGS=-std=c99 -pedantic -Iinclude -DRPD_VERSION=\"$(VERSION)\" CXX=c++ CXXFLAGS=-pedantic -Iinclude CXXSTD=-ansi @@ -19,6 +19,12 @@ else CXXFLAGS+=-O3 endif +#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) diff --git a/docs/src/development.dox b/docs/src/development.dox index bcae8c2..5aea286 100644 --- a/docs/src/development.dox +++ b/docs/src/development.dox @@ -2,6 +2,8 @@ \page development Development +[TOC] + Overview -------- We use: diff --git a/docs/src/get_started.dox b/docs/src/get_started.dox index 1986fcb..24ac1f2 100644 --- a/docs/src/get_started.dox +++ b/docs/src/get_started.dox @@ -2,6 +2,8 @@ \page get_started Get Started +[TOC] + Installation ------------ Run the following commands: @@ -37,4 +39,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: +```sh +make FCGI_SERVER=0 +``` + +To see other flags you can run `make help`. + */ diff --git a/docs/src/index.dox b/docs/src/index.dox index a311f28..709885b 100644 --- a/docs/src/index.dox +++ b/docs/src/index.dox @@ -1,11 +1,11 @@ /** -\mainpage Rapida Manual +\mainpage Manual Web framework written in C and C++. -Table of contents: -1. \subpage get_started -2. \subpage development +[TOC] +- \subpage get_started +- \subpage development */ diff --git a/examples/c/example.c b/examples/c/example.c index e071506..b3a2089 100644 --- a/examples/c/example.c +++ b/examples/c/example.c @@ -1,5 +1,5 @@ #include "../../include/rapida.h" -#include "../../include/servers/fcgi.h" +#include "../../include/servers/tcp.h" #include #include #include @@ -52,6 +52,5 @@ int main() rpd_app_add_route(&app, "/products/:category/:id", &products_handler, NULL); - return rpd_fcgi_server_start(&app, "/tmp/webapp.socket"); - return 0; + return rpd_tcp_server_start(&app, "http://localhost:8080"); } diff --git a/examples/c/minimal.c b/examples/c/minimal.c index 536292b..ec4583a 100644 --- a/examples/c/minimal.c +++ b/examples/c/minimal.c @@ -1,8 +1,6 @@ #include "../../include/rapida.h" -#include "../../include/servers/fcgi.h" +#include "../../include/servers/tcp.h" #include /* for strdup() */ -#include - /* * \brief Process home page request. @@ -16,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; @@ -25,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: @@ -51,5 +50,5 @@ int main() rpd_app_add_route(&app, "/", &home_page_handler, NULL); /* Run the application and return its status code. */ - return rpd_fcgi_server_start(&app, "/tmp/webapp.socket"); + return rpd_tcp_server_start(&app, "http://localhost:8080"); } diff --git a/examples/cxx/example.cxx b/examples/cxx/example.cxx index 2742e9c..85480b3 100644 --- a/examples/cxx/example.cxx +++ b/examples/cxx/example.cxx @@ -1,5 +1,5 @@ #include "../../include/rapida.hxx" -#include "../../include/servers/fcgi.h" +#include "../../include/servers/tcp.h" #include class ProductRoute : public rpd::Route { @@ -49,5 +49,5 @@ int main() */ app.add_route("/products/:category/:id", new ProductRoute()); - return rpd_fcgi_server_start(app.c_app(), "/tmp/webapp.socket"); + return rpd_tcp_server_start(app.c_app(), "http://localhost:8080"); } diff --git a/examples/cxx/minimal.cxx b/examples/cxx/minimal.cxx index fa98c1b..d8febda 100644 --- a/examples/cxx/minimal.cxx +++ b/examples/cxx/minimal.cxx @@ -1,5 +1,5 @@ #include "../../include/rapida.hxx" -#include "../../include/servers/fcgi.h" +#include "../../include/servers/tcp.h" /* @@ -39,5 +39,5 @@ int main() app.add_route("/", new Home()); - return rpd_fcgi_server_start(app.c_app(), "/tmp/webapp.socket"); + return rpd_tcp_server_start(app.c_app(), "http://localhost:8080"); } diff --git a/include/App.hxx b/include/App.hxx index b31a63d..9a8655f 100644 --- a/include/App.hxx +++ b/include/App.hxx @@ -16,9 +16,8 @@ namespace rpd { class App { public: /*! - * Creates an rpd app and initializes FastCGI. + * Creates a Rapida application instance. * \brief Default constructor - * \param socket_path UNIX Socket path. */ App() { diff --git a/include/Response.hxx b/include/Response.hxx index 2b204a8..75db59a 100644 --- a/include/Response.hxx +++ b/include/Response.hxx @@ -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 diff --git a/include/app.h b/include/app.h index 7475f1c..3250767 100644 --- a/include/app.h +++ b/include/app.h @@ -35,7 +35,8 @@ int rpd_app_create(rpd_app *app); /*! * Handle accepted request. * \param app Application instance. - * \param req FastCGI request. + * \param req Request. + * \param res Response. */ void rpd_app_handle_request(rpd_app *app, rpd_req *req, rpd_res *res); diff --git a/include/response.h b/include/response.h index c567797..6e6d64e 100644 --- a/include/response.h +++ b/include/response.h @@ -93,13 +93,22 @@ 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); +/*! + * \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. * diff --git a/include/servers/tcp.h b/include/servers/tcp.h new file mode 100644 index 0000000..0ed2a28 --- /dev/null +++ b/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 */ diff --git a/servers/fcgi.c b/servers/fcgi.c index d02579a..1d3d335 100644 --- a/servers/fcgi.c +++ b/servers/fcgi.c @@ -1,3 +1,6 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* Copyright 2022 Ivan Polyakov */ + #include "../include/servers/fcgi.h" #include diff --git a/servers/tcp.c b/servers/tcp.c new file mode 100644 index 0000000..89e4df6 --- /dev/null +++ b/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 +#include + +/* 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; +}