The Silicon C++ Web Framework

Write Fast and Robust Web APIs with C++

Tweet

Get Started

Upgrade to Lithium::http_backend

This project has been rewritten and moved inside the Lithium libraries: https://github.com/matt-42/lithium/tree/master/libraries/http_backend

What is Silicon?

Silicon is a C++ abstraction built on top of high-performance networking libraries. Its goal is to ease the writing of web APIs without compromising on performance.

Quick Tour

###Hello world

A simple mono-procedure API serving a static string under the route /hello/world.

auto my_api = http_api(GET / _hello / _world  = [] () { return "hello world";});
mhd_json_serve(my_api, 8080);

mhd_json_serve is a Silicon backend. It takes an API and serves it with the microhttpd C library. It relies on the JSON format whenever it has to encode or decode objects (procedures parameters and return values).

###Returning JSON objects

Silicon relies on static objects (i.e. not hashmaps) to model objects. They embed static introspection in their type such that the JSON serializer knows about their members names and types.

GET / _hi = [] () { return D(_name = "John", _age = 42); }

###Passing Parameters

In Silicon, parameters are strongly typed and declared next to the procedure route. If any error occur during the decoding (i.e. a missing or ill-formated parameter), the framework does not call the procedure and send an error to the client. Note that GET and POST parameter have the type std::string by default such that get_parameters(_name = std::string()) is equivalent to


```c++
POST / _hello / _id[int()] // Url parameter
   * get_parameters(_name) // GET parameter
   * post_parameters(_age = int()) // POST parameter

   = [] (auto p) // p holds the 3 typed parameters
   {
     std::ostringstream ss;
     ss << p.name << p.age << p.id;
     return ss.str();
   }

###Optional Parameters

All parameters are required by default. GET and POST parameters can be set as optional.

GET / _hello * get_parameters(_id = optional(int(42)))

###Middlewares

Middlewares provide access to the external world (databases, sessions, …). To require an access to a middleware, the API procedures simply declare it as argument. If needed, a factory is passed to the backend in order to instantiate the middleware. For example, the mysql_connection_factory handles the database information needed to create a connection.

auto my_api = http_api(
  GET / _username / _id[int()]
  = [] (auto p, mysql_connection& db) {
    std::string name;
    db("SELECT name from User where id = ?")(id) >> name;
    return D(_name = name);
  }
);

auto middlewares = std::make_tuple(
   mysql_connection_factory("localhost", "user", "password", "database_name")
);
mhd_json_serve(my_api, middlewares, 8080);

Procedures can take any number of middlewares in any order. The framework takes care of instantiating and passing the middlewares to the procedure in the right order. If an instantiation fails, Silicon does not call the procedure and send an error to the client.

###MySQL and Sqlite middlewares

Silicon provides an abstraction around the low level C client libraries of MySQL and Sqlite.

GET / _mysql = [] (auto p, mysql_connection& db) {
    // Read one scalar.
    int s;
    c("SELECT 1+2")() >> s;


    // Read a record.
    int age; std::string name;
    c("SELECT name, age from users LIMIT 1")() >> std::tie(name, age);
    // name == "first_user_name"
    // age == first_user_age


    // Iterate on a list of records:
    c("SELECT name, age from users")() | [] (std::string& name, int& age) {
      std::cout << name << " " << age << std::endl;
    };

    // Read a record using a IOD object.
    auto r = D(_name = std::string(), _age = int());
    c("SELECT name, age from users LIMIT 1")() >> r;
    // r.name == "first_user_name"
    // r.age == first_user_age


    // Iterate on a list of records using a IOD object:
    typedef decltype(r) R;
    c("SELECT name, age from users")() | [] (R r) {
      std::cout << r.name << " " << r.age << std::endl;
    };

    // Inject variables into a SQL request.
    int id = 42;
    int age = 42;
    std::string name;
    db("SELECT name from User where id = ? and age = ?")(id, age) >> name;
}

###Errors

When a procedure cannot complete because of an error, it throws an exception describing the cause of the error.

GET / _test / _id[int()] = [] (auto p)
{
  if (p.id != 42)
    // Send an error 401 Unauthorized to the client.
    throw error::unauthorized("Wrong ID");
  return "success";
}

###Sessions

The framework also provides session middlewares. It enables an API to store information about the current user in a database, or in an in-memory hashtable.

struct session
{
  int id;
};

auto api = http_api(

    GET / _set_id / _id[int()] = [] (auto p, session& s)
    {
      s.id = p.id;
    },

    GET / _get_id = [] (session& s) {
      return D(_id = s.id);
    }
);

auto middlewares = std::make_tuple(
   hashmap_session_factory<session>()
);

mhd_json_serve(my_api, middlewares, 8080);

###Testing Silicon APIs

Testing Silicon APIs is done with the libcurl_json_client. It introspects an API to generate at compile time the C++ functions mapping the API procedures and their parameters. A typical test first starts the server in a non blocking manner (via the _non_blocking argument) and then create a client to test the API.

// Define an API.
auto my_api = http_api(
    POST / _hello / _world / _id[int()]
    * get_parameters(_name, _city)
    = [] (auto p) { return D(_id = p.id, _name = p.name, _city = p.city); }
);

// Start a server
auto server = sl::mhd_json_serve(hello_api, 8080, _non_blocking);

// Instantiate the client.
auto c = libcurl_json_client(my_api, "127.0.0.1", 8080);

// c.http_get contains GET procedures.
// c.http_post contains POST procedures.
// c.http_put contains PUT procedures.
// c.http_delete contains DELETE  procedures.

auto r = c.http_post.hello.world(_id = 42, _name = "John", _city = "Paris");
// Thanks to introspection, the client knows where to place each parameter in the request.

 // r.status is the http code of the response.
 // r.error is the error message if the code is not 200
 // r.response is the response object returned by the server.

assert(r.status == 200);
assert(r.response.id == 42);
assert(r.response.name == "John");
assert(r.response.city == "Paris");