[Перевод] Библиотека 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");