[Перевод] Библиотека Silicon — WebAPI на C++

Прим. переводчика: в синтаксисе C++ напрочь отсутствуют несколько ограниченны средства построения предметно-ориентированных языков. В итоге их мало кто на С++ пытается использовать, а попытки всё-же это сделать вызывают интерес, тем более, когда в итоге получается нечто стройно выглядящее и практически полезное. Одним из таких открытий для меня стала библиотека Silicon, пытающаяся средствами современного С++ дать возможность быстро и гибко реализовать WebAPI в своём проекте. Давайте посмотрим, насколько просто это выглядит.

Hello World на Silicon — программа, которая на HTTP-запрос к 

http://host/hello/world

ответит кодом 200 с текстом «hello world»:

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

Неплохо, правда? my_api здесь это описание нашего API, а mhd_json_serve — это бекэнд библиотеки Silicon, реализующий данный API с использованием встроенного вебсервера (на выбор microhttpd или LWAN).

Давайте посмотрим, что ещё умеет Silicon.
Возвращаем JSON

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

Обработка параметров всех типов

POST / _hello / _id[int()] // URL-параметры
   * get_parameters(_name) // GET-параметры
   * post_parameters(_age = int()) // POST-параметры

   = [] (auto p) // p содержит три параметра
   {
     std::ostringstream ss;
     ss << p.name << p.age << p.id;
     return ss.str();
   }

Опциональные параметры

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

Связующий слой
Если вы пишете WebAPI, то с большой вероятностью вам может понадобиться доступ к базе данных. На Silicon это выглядит вот так:

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

Поддерживаются MySQL и Sqlite.

Ошибки
Для возврата кодов ошибок HTTP-протокола используются исключения:

GET / _test / _id[int()] = [] (auto p)
{
  if (p.id != 42)
    // Отправляет код 401 (Unauthorized)
    throw error::unauthorized("Wrong ID");
  return "success";
}

Сессии
Мы, конечно же, можем помнить сессии пользователей (в базе данных или в памяти):

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

mhd_json_serve(my_api, middlewares, 8080);

Тестирование созданного WebAPI
Мало толку от WebAPI, если все его методы не протестированы. К счастью, Silicon позволяет на основе описанного API получить клиент на базе libcurl_json_client, с уже готовыми функциями для вызова методов нашего API. Может использоваться как для тестирования, так и в реальном клиенте.

// Описываем 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); }
);

// Запускаем сервер
auto server = sl::mhd_json_serve(hello_api, 8080, _non_blocking);

// Создаём клиент
auto c = libcurl_json_client(my_api, "127.0.0.1", 8080);

// c.http_get содержит GET-процедуры
// c.http_post содержит POST-процедуры
// c.http_put содержит PUT-процедуры
// c.http_delete содержит DELETE-процедуры

// Благодаря интроспекции клиент знает пути и параметры запроса
auto r = c.http_post.hello.world(_id = 42, _name = "John", _city = "Paris");

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

© Habrahabr.ru