Mark for release.
1 file changed
tree: 574e5259991a07a85422be0c342e0c539962d117
  1. .clang_complete
  2. .gitignore
  3. .gitmodules
  4. CHANGELOG.md
  5. Doxyfile
  6. LICENSE
  7. NOTICE
  8. README.md
  9. clean
  10. dbg
  11. demo run
  12. docs/
  13. main.c
  14. makefile
  15. src/
  16. tests/
  17. unused/
README.md

facil.io - the C WebApp mini-framework

Notice: facil.io has been rewritten from the core. The API was changed to prevent surprising errors, but it's easy to port from he previous version to the current version.

facil.io is a C mini-framework for web applications and includes a fast HTTP and Websocket server, as well as support for custom protocols.

facil.io powers the HTTP/Websockets Ruby Iodine server and it can easily power your application as well.

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

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

Notice: The master branch is the development branch. Please select the latest release version by choosing the latest version available as a tag in the branch list.

Three Quick Examples

Writing HTTP and Websocket services in C is easy with facil.io.

HTTP/1.1

The simplest example, of course, would be the famous “Hello World” application... this is so easy, it's practically boring (so we add custom headers and cookies):

#include "http.h"

void on_request(http_request_s* request) {
  http_response_s * response = http_response_create(request);
  // http_response_log_start(response); // logging ?
  http_response_set_cookie(response, .name = "my_cookie", .value = "data");
  http_response_write_header(response, .name = "X-Data", .value = "my data");
  http_response_write_body(response, "Hello World!\r\n", 14);
  http_response_finish(response);
}

int main() {
  char* public_folder = NULL;
  // listen on port 3000, any available network binding (NULL == 0.0.0.0)
  http_listen("3000", NULL, .on_request = on_request,
               .public_folder = public_folder, .log_static = 0);
  // start the server
  facil_run(.threads = 16);
}

Websockets

facil.io really shines when it comes to Websockets and real-time applications, where the kqueue/epoll engine gives the framework a high performance running start.

Here's a full-fledge example of a Websocket echo server, a Websocket broadcast server and an HTTP “Hello World” (with an optional static file service) all rolled into one:

#include "websockets.h" // includes the "http.h" header

#include <stdio.h>
#include <stdlib.h>

/* ******************************
The Websocket echo implementation
*/

void ws_open(ws_s * ws) {
  fprintf(stderr, "Opened a new websocket connection (%p)\n", (void * )ws);
}

void ws_echo(ws_s * ws, char * data, size_t size, uint8_t is_text) {
  // echos the data to the current websocket
  websocket_write(ws, data, size, is_text);
}

void ws_shutdown(ws_s * ws) { websocket_write(ws, "Shutting Down", 13, 1); }

void ws_close(ws_s * ws) {
  fprintf(stderr, "Closed websocket connection (%p)\n", (void * )ws);
}

/* ********************
The HTTP implementation
*/

void on_request(http_request_s * request) {
  http_response_s * response = http_response_create(request);
  http_response_log_start(response); // logging
  // websocket upgrade.
  if (request->upgrade) {
    websocket_upgrade(.request = request, .on_message = ws_echo,
                      .on_open = ws_open, .on_close = ws_close, .timeout = 40,
                      .on_shutdown = ws_shutdown, .response = response);
    return;
  }
  // HTTP response
  http_response_write_body(response, "Hello World!", 12);
  http_response_finish(response);
}

/****************
The main function
*/

#define THREAD_COUNT 1
int main(void) {
  const char* public_folder = NULL;
  http_listen("3000", NULL, .on_request = on_request,
               .public_folder = public_folder, .log_static = 1);
  facil_run(.threads = THREAD_COUNT);
  return 0;
}

A Custom Protocol (an Echo example)

facil.io's API is designed for both simplicity and an object oriented approach, using network protocol objects and structs to avoid bloating function arguments and to provide sensible default behavior.

Here's a simple Echo example (test with telnet to port "3000").

#include "facil.h" // the core library header

// Performed whenever there's pending incoming data on the socket
static void perform_echo(intptr_t uuid, protocol_s * prt) {
  (void)prt;
  char buffer[1024] = {'E', 'c', 'h', 'o', ':', ' '};
  ssize_t len;
  while ((len = sock_read(uuid, buffer + 6, 1018)) > 0) {
    sock_write(uuid, buffer, len + 6);
    if ((buffer[6] | 32) == 'b' && (buffer[7] | 32) == 'y' &&
        (buffer[8] | 32) == 'e') {
      sock_write(uuid, "Goodbye.\n", 9);
      sock_close(uuid); // closes after `write` had completed.
      return;
    }
  }
}
// performed whenever "timeout" is reached.
static void echo_ping(intptr_t uuid, protocol_s * prt) {
  (void)prt;
  sock_write(uuid, "Server: Are you there?\n", 23);
}
// performed during server shutdown, before closing the socket.
static void echo_on_shutdown(intptr_t uuid, protocol_s *prt) {
  (void)prt;
  sock_write(uuid, "Echo server shutting down\nGoodbye.\n", 35);
}
// performed after the socket was closed and the currently running task had
// completed.
static void destroy_echo_protocol(protocol_s * echo_proto) {
  if (echo_proto) // always error check, even if it isn't needed.
    free(echo_proto);
  fprintf(stderr, "Freed Echo protocol at %p\n", (void * )echo_proto);
}
// performed whenever a new connection is accepted.
static inline protocol_s *create_echo_protocol(intptr_t uuid, void *arg) {
  // create a protocol object
  protocol_s *echo_proto = malloc(sizeof( * echo_proto));
  // set the callbacks
  * echo_proto = (protocol_s){
      .service = "echo",
      .on_data = perform_echo,
      .on_shutdown = echo_on_shutdown,
      .ping = echo_ping,
      .on_close = destroy_echo_protocol,
  };
  // write data to the socket and set timeout
  sock_write(uuid, "Echo Service: Welcome. Say \"bye\" to disconnect.\n", 48);
  facil_set_timeout(uuid, 10);
  // print log
  fprintf(stderr, "New Echo connection %p for socket UUID %p\n",
          (void * )echo_proto, (void * )uuid);
  // return the protocol object to attach it to the socket.
  return echo_proto;
  (void)arg; // we don't use this
}
// creates and runs the server
int main(void) {
  // listens on port 3000 for echo services.
  facil_listen(.port = "3000", .on_open = create_echo_protocol);
  // starts and runs the server
  facil_run(.threads = 10);
  return 0;
}

Forking, Contributing and all that Jazz

Sure, why not. If you can add Solaris or Windows support to libreact, that could mean lib-server would become available for use on these platforms as well (as well as the HTTP protocol implementation and all the niceties).

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:

  • Help me write HPACK / HTTP2 protocol support.

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

  • If you want to help me write a new SSL/TLS library or have an SSL/TLS solution we can fit into lib-server (as source code).