Your high performance web application C framework

GitHub Build Status Codacy Badge codecov

facil.io is a C micro-framework for web applications. facil.io includes:

  • A fast HTTP/1.1 and Websocket static file + application server.
  • Support for custom network protocols for both server and client connections.
  • Dynamic types designed with web applications in mind (Strings, Hashes, Arrays etc').
  • Performant JSON parsing and formatting for easy network communication.
  • A pub/sub process cluster engine for local and Websocket pub/sub.
  • Optional connectivity with Redis.

facil.io provides high performance TCP/IP network services to Linux / BSD (and macOS) by using an evented design (as well as thread pool and forking support) and provides an easy solution to the C10K problem.

You can read more about facil.io on the facil.io website.

Important to Note

The master branch on the git repo is the development branch and is likely to be broken at any given time (especially when working on major revisions, as I am at the moment).

Please select a release version for any production needs.

Who's running on facil.io

An HTTP example

#include "http.h" /* the HTTP facil.io extension */

// We'll use this callback in `http_listen`, to handles HTTP requests
void on_request(http_s *request);

// These will contain pre-allocated values that we will use often
FIOBJ HTTP_X_DATA;

// Listen to HTTP requests and start facil.io
int main(int argc, char const **argv) {
  // allocating values we use often
  HTTP_X_DATA = fiobj_str_new("X-Data", 6);
  // listen on port 3000 and any available network binding (NULL == 0.0.0.0)
  http_listen("3000", NULL, .on_request = on_request, .log = 1);
  // start the server
  facil_run(.threads = 1);
  // deallocating the common values
  fiobj_free(HTTP_X_DATA);
}

// Easy HTTP handling
void on_request(http_s *request) {
  http_set_cookie(request, .name = "my_cookie", .name_len = 9, .value = "data",
                  .value_len = 4);
  http_set_header(request, HTTP_HEADER_CONTENT_TYPE,
                  http_mimetype_find("txt", 3));
  http_set_header(request, HTTP_X_DATA, fiobj_str_new("my data", 7));
  http_send_body(request, "Hello World!\r\n", 14);
}

Using facil.io in your project

It's possible to either start a new project with facil.io or simply add it to an existing one. GNU make is the default build system and CMake is also supported.

facil.io should be C99 compatible.

Starting a new project with facil.io

To start a new project using the facil.io framework, run the following command in the terminal (change appname to whatever you want):

 $ bash <(curl -s https://raw.githubusercontent.com/boazsegev/facil.io/master/scripts/new/app) appname

You can review the script here. In short, it will create a new folder, download a copy of the stable branch, add some demo boiler plate code and run make clean (which is required to build the tmp folder structure).

Next, edit the makefile to remove any generic features you don't need, such as the DUMP_LIB feature, the DEBUG flag or the DISAMS disassembler and start development.

Credit to @benjcal for suggesting the script.

Notice: The master branch is the development branch. Please select the latest release tag for the latest stable release version.

Adding facil.io to an existing project

facil.io is a source code library, so it's easy to copy the source code into an existing project and start using the library right away.

The make libdump command will dump all the relevant files in a single folder called libdump, and you can copy them all or divide them into header ands source files.

It's also possible to compile the facil.io library separately using the make lib command.

Using facil.io as a CMake submodule

facil.io also supports both git and CMake submodules. Credit to @OwenDelahoy (PR#8).

First, add the repository as a submodule using git:

git submodule add https://github.com/boazsegev/facil.io.git

Then add the following line the project's CMakeLists.txt

add_subdirectory(facil.io)

Using facil.io with Meson

facil.io is available at Meson Wrap DB.

First, install the wrap file:

meson wrap install facil

Then add the following line to your project's meson.build:

facil_dep = subproject('facil').get_variable('facil_dep')

More Examples

The examples folder includes code examples for a telnet echo protocol, a Simple Hello World server, an example for Websocket pub/sub with (optional) Redis, etc'.

You can find more information on the facil.io website


Forking, Contributing and all that Jazz

The contribution guide can be found here.

Sure, why not. If you can add Solaris or Windows support to evio and sock, that could mean facil would become available for use on these platforms as well.

If you encounter any issues, open an issue (or, even better, a pull request with a fix) - that would be great :-)

Hit me up if you want to:

  • Write tests... I always need more tests...

  • Help me write HPACK / HTTP2 protocol support.

  • Help me design / write a generic HTTP routing helper library for the http_s struct.

  • If you want to help me write a new SSL/TLS library or have an SSL/TLS solution we can fit into facil (as source code)... Note: SSL/TLS solutions should fit both client and server modes.

  • If you want to help promote the library, that would be great as well. Perhaps publish benchmarks or share your story.

  • Writing documentation into the facil.io website would be great. I keep the source code documentation fairly updated, but the documentation should be copied to the docs folder to get the documentation website up and running.

Owner
Bo
Musician and Programmer by love and trade. My intention is to leave this world in a more compassionate, loving and free state than it was when I entered it.
Bo
Comments
  • Support for redis UNIX sockets

    Support for redis UNIX sockets

    Maybe this is yet another documentation issue, but I'm curious how to connect a facil.io application to a redis socket file - my redis does not have "a port". Is that already supported?

  • fiobj_mustache.c: No folder argument

    fiobj_mustache.c: No folder argument

    From the source code:

    /**
     * Loads a mustache template, converting it into an opaque instruction array.
     *
     * Returns a pointer to the instruction array.
     *
     * The `folder` argument should contain the template's root folder which would
     * also be used to search for any required partial templates.
     *
     * The `filename` argument should contain the template's file name.
     */
    mustache_s *fiobj_mustache_load(fio_str_info_s filename) {
      return mustache_load(.filename = filename.data, .filename_len = filename.len);
    }
    

    There is no folder argument.

  • A route handler for Facil.io

    A route handler for Facil.io

    I saw that a generic HTTP routing helper library for the http_s struct was on your wishlist. I've been developing a collection of higher-level abstractions for Facil.io called Sencillo, which adds (among other things) a route handler to Facil.io.

    Sencillo's route handler does quite a bit that is far outside the scope of Facil.io (https://github.com/epixoip/sencillo/blob/main/sencillo.h#L1030-L1166), therefore a modified implementation that is more inline with Facil.io's goals is provided below for your consideration:

    bool http_path_matches(http_s *request, char *__route) {
        // make a mutable copy of the route
        char *route = strdup(__route);
    
        if (!route) {
            FIO_LOG_ERROR("Failed to allocate memory!");
            return false;
        }
    
        char *path = fiobj_obj2cstr(request->path).data;
    
        // truncate the path at the query string delimiter,
        // as we only care about the path itself and the 
        // query string is parsed by http_parse_query()
        path = strtok(path, "?");
    
        // does the route contain any inline path variables?
        if (strchr(route, ':') == null) {
            // no - perform direct string comparison
            if (strcmp(route, path) == 0) {
                free(route);
                return true;
            } else {
                free(route);
                return false;
            }
        }
    
        int route_part_cnt = 0;
        int path_part_cnt  = 0;
    
        // count the number of parts in the route and the path
        for (int i = 0; route[i]; route[i] == '/' ? route_part_cnt++, i++ : i++);
        for (int i = 0; path[i];  path[i]  == '/' ? path_part_cnt++,  i++ : i++);
    
        // do we have an equal number of parts?
        if (route_part_cnt != path_part_cnt) {
            return false;
        }
    
        char *route_parts[route_part_cnt];
        char *path_parts[path_part_cnt];
    
        char *route_remaining;
        char *path_remaining;
    
        int matches = 0;
    
        // loop through each part
    
        char *route_next_part = strtok_r(route, "/", &route_remaining);
        char *path_next_part  = strtok_r(path,  "/", &path_remaining);
    
        while (route_next_part && path_next_part) {
            // if the route part is an inline variable, extract the variable name and its value
            if (route_next_part[0] == ':') {
                route_parts[matches]  = route_next_part + 1;
                path_parts[matches++] = path_next_part;
            } else {
                // the route part is literal, does it match the path part?
                if (strcmp(route_next_part, path_next_part)) {
                    free(route);
                    return false;
                }
            }
    
            route_next_part = strtok_r(null, "/", &route_remaining);
            path_next_part  = strtok_r(null, "/", &path_remaining);
        }
    
        free(route);
    
        // add the inline path variable names and values to the request params
        for (int i = 0; i < matches; i++) {
            http_add2hash(request->params, route_parts[i], strlen(route_parts[i]), path_parts[i], strlen(path_parts[i]), 1);
        }
    
        return true;
    }
    

    There are a couple ways you could implement http_path_matches depending on the desired experience.

    First, you could create macros for simple conditionals:

    #define http_route_get(_h, _route) \
        if (strcmp(fiobj_obj2cstr(request->method).data, "GET") == 0 && http_path_matches(_h, _route))
    
    #define http_route_post(_h, _route) \
        if (strcmp(fiobj_obj2cstr(request->method).data, "POST") == 0 && http_path_matches(_h, _route))
    
    #define http_route_put(_h, _route) \
        if (strcmp(fiobj_obj2cstr(request->method).data, "PUT") == 0 && http_path_matches(_h, _route))
    
    #define http_route_delete(_h, _route) \
        if (strcmp(fiobj_obj2cstr(request->method).data, "DELETE") == 0 && http_path_matches(_h, _route))
    

    Use of these macros would be straightforward:

    static void on_http_request(http_s *h) {
        http_parse_query(h);
        http_parse_body(h);
    
        http_route_get(h, "/user/:id") {
            FIOBJ id_str = fiobj_str_new("id", 2);
            FIOBJ id_val = fiobj_hash_get(request->params, id_str);
    
            char *id = fiobj_obj2cstr(id_val).data;
            
            char greeting[32];
            snprintf(greeting, sizeof greeting, "Welcome user %s!", id);
    
            fiobj_free(id_str);
            fiobj_free(id_val);
    
            http_send_body(h, greeting, strlen(greeting));
            return;
        }
    }
    

    Another way would be to create macros that map each route to a function:

    #define http_route_handler(_h, _method, _route, _func) {                                                \
        if (strcmp(fiobj_obj2cstr(request->method).data, _method) == 0 && http_path_matches(_h, _route)) {  \
            FIO_LOG_DEBUG("Matched route %s %s", _method, _route);                                          \
            _func(_h);                                                                                      \
            return;                                                                                         \
        }                                                                                                   \
    }
    
    #define http_route_get(_h, _route, _func) \
        http_route_handler(_h, "GET", _route, _func)
    
    #define http_route_post(_h, _route, _func) \
        http_route_handler(_h, "POST", _route, _func)
    
    #define http_route_put(_h, _route, _func) \
        http_route_handler(_h, "PUT", _route, _func)
    
    #define http_route_delete(_h, _route, _func) \
        http_route_handler(_h, "DELETE", _route, _func)
    

    Which could simply be incorporated as:

    static void on_http_request(http_s *h) {
        http_parse_query(h);
        http_parse_body(h);
    
        http_route_get(h, "/user/:id", getUserById);
    }
    
  • Support for WebSocket Protocol header

    Support for WebSocket Protocol header

    The problem I was facing was that I'm using Javascript running in a browser. Therefore I need to connect to a WebSocket with the default JS functions and unable to use a 3rd party alternative for NodeJS. I needed to authorize before the WebSocket connection is opened else it leaves you vulnerable to DDoS attacks. This leaves one secure option: passing the token in the sec-websocket-protocol header. I found out there was no support in Facil.io yet.

    There are three different types of requests coming in:

    1. Has no sec-websocket-protocol header header, so no header will be in the response.
    2. Has a single string as value and passes the same value back in the response.
    3. Has an array of values as a string separated by colons. Takes the first value and passes it in the response.

    Any other way would fail to connect. Normally would there be checks if the WebSocket Server actually supports those protocols. This code just makes it possible that no matter the value you are still able to connect.

  • OpenSSL Not Detected

    OpenSSL Not Detected

    Debian Testing (11), openssl 1.1.1i-1 is installed (normal and dev packages)

    The makefile says that it can't find any suitable SSL library, it doesn't give any helpful info beyond that (is there a way to get the makefile to be noisier?)

    Building with cmake includes a fio_tls_openssl.o file in the archive, so I assume that it works correctly there.

🌱Light and powerful C++ web framework for highly scalable and resource-efficient web application. It's zero-dependency and easy-portable.
🌱Light and powerful C++ web framework for highly scalable and resource-efficient web application. It's zero-dependency and easy-portable.

Oat++ News Hey, meet the new oatpp version 1.2.5! See the changelog for details. Check out the new oatpp ORM - read more here. Oat++ is a modern Web F

May 16, 2022
CppCMS - High Performance C++ Web Framework

CppCMS - High Performance C++ Web Framework What is CppCMS? CppCMS is a Free High Performance Web Development Framework (not a CMS) aimed at Rapid Web

May 3, 2022
A high performance, middleware oriented C++14 http web framework please use matt-42/lithium instead

A high performance, middleware oriented C++14 http web framework please use matt-42/lithium instead

May 19, 2022
Tntnet is a web application server for web applications written in C++.

Tntnet is a web application server for web applications written in C++.

Nov 4, 2021
Drogon: A C++14/17 based HTTP web application framework running on Linux/macOS/Unix/Windows
Drogon: A C++14/17 based HTTP web application framework running on Linux/macOS/Unix/Windows

English | 简体中文 | 繁體中文 Overview Drogon is a C++14/17-based HTTP application framework. Drogon can be used to easily build various types of web applicat

May 20, 2022
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.

Cutelyst - The Qt Web Framework A Web Framework built on top of Qt, using the simple and elegant approach of Catalyst (Perl) framework. Qt's meta obje

May 3, 2022
Experimental, scalable, high performance HTTP server

Lwan Web Server Lwan is a high-performance & scalable web server. The project web site contains more details. Build status OS Arch Release Debug Stati

May 12, 2022
A high-performance REST Toolkit written in C++

Pistache Pistache is a modern and elegant HTTP and REST framework for C++. It is entirely written in pure-C++14 and provides a clear and pleasant API.

May 18, 2022
Cetus is a high performance, stable, protocol aware proxy for MySQL Group Replication.

Introduction Cetus is a high performance, stable, protocol aware proxy for MySQL Group Replication. Getting started 1. Prerequisites cmake gcc glib2-d

Mar 17, 2022
Crow is very fast and easy to use C++ micro web framework (inspired by Python Flask)
Crow is very fast and easy to use C++ micro web framework (inspired by Python Flask)

Crow is C++ microframework for web. (inspired by Python Flask) #include "crow.h" int main() { crow::SimpleApp app; CROW_ROUTE(app, "/")([]()

May 20, 2022
QDjango, a Qt-based C++ web framework

QDjango - a Qt-based C++ web framework Copyright (c) 2010-2015 Jeremy Lainé About QDjango is a web framework written in C++ and built on top of the Qt

May 12, 2022
This is a proof-of-concept of a modern C web-framework that compiles to WASM and is used for building user interfaces.
This is a proof-of-concept of a modern C web-framework that compiles to WASM and is used for building user interfaces.

DanCing Web ?? ?? (DCW) Getting Started Dancing Web is now distributed with the Tarantella Package Manager — a tool I've made to simplify setup of pro

Sep 11, 2021
C++ application development framework, to help developers create and deploy applications quickly and simply

ULib - C++ library Travis CI: Coverity Scan: ULib is a highly optimized class framework for writing C++ applications. I wrote this framework as my too

May 14, 2022
The application framework for developer module of EdgeGallery platform

crane-framework crane-framework将可复用的计算和软件功能抽象成插件,APP开发者面向使用插件进行MEC APP开发。这样屏蔽了和MEC平台交互的细节,实现MCE APP和MEC平台的松耦合。而且插件框架基础能力可裁剪,按需提供最小的APP系统。 特性介绍 为了方便开发者

Aug 30, 2021
Embedded C/C++ web server

CivetWeb The official home of CivetWeb is https://github.com/civetweb/civetweb Continuous integration for Linux and macOS (Travis CI): Continuous inte

May 16, 2022
C library to create simple HTTP servers and Web Applications.

Onion http server library Travis status Coverity status Onion is a C library to create simple HTTP servers and Web Applications. master the developmen

May 12, 2022
A C++11 RESTful web server library
A C++11 RESTful web server library

Served Overview Served is a C++ library for building high performance RESTful web servers. Served builds upon Boost.ASIO to provide a simple API for d

May 12, 2022
cserv is an event-driven and non-blocking web server

cserv is an event-driven and non-blocking web server. It ideally has one worker process per cpu or processor core, and each one is capable of handling thousands of incoming network connections per worker. There is no need to create new threads or processes for each connection.

Apr 3, 2022