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<session>()
);
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");
Комментарии (35)
Symphel
28.03.2016 11:22+1Не могли бы дать ссылку? Что-то не гуглится.
В целом интересно, особенно использование пользовательских литералов, но возникает несколько вопросов.
Поддерживаются MySQL и Sqlite.
А для других свою реализацию можно написать или допускается только со строенными работать? Судя по тому, что соединение с БД — это функтор, интерфейс должен быть минимальным.
Вообще, не очень понятно, зачем в этой библиотеке встроенная реализация поддержки конкретных баз данных. Это уже фреймворк получается.
auto middlewares = std::make_tuple(hashmap_session_factory());
mhd_json_serve(my_api, middlewares, 8080);
…
auto middlewares = std::make_tuple(mysql_connection_factory(«localhost», «user», «password», «database_name»));
mhd_json_serve(my_api, middlewares, 8080);
Это они потом элементы по типу достают из кортежа?
А если больше одного соединения с базой будет?
А свои middleware добавить можно?tangro
28.03.2016 11:24+1На счёт ссылки отписался выше. На счёт "зачем в этой библиотеке..." — её суть это дать возможность поднять бэкенд на С++ в 5 минут и 10 строк. Бэкенд это в большинстве случаев HTTP + JSON + сессии + БД. Каждый из этих компонентов, в общем-то, не обязателен и может быть заменён на что-то ещё, но мода диктует нынче такой набор и Silicon даёт его "из коробки". На счёт middleware — посмотрите код и доки (http://siliconframework.org/docs/middlewares.html)
frol
28.03.2016 11:31Это уже фреймворк получается.
Так это и есть фреймворк (Silicon Framework) о чём официальный сайт и гласит:
The Silicon C++ Web Framework
Это с подачи автора перевода он стал «библиотекой».tangro
28.03.2016 12:07+1Ну ок, исправил. Как по мне, маловато у него функционала для "фреймворка", но если авторы так называют, то пусть так и будет.
angru
28.03.2016 11:25+1вот тоже в тему: "Crow is C++ microframework for web. (inspired by Python Flask)" https://github.com/ipkn/crow
AxisPod
28.03.2016 15:00А вот захотел я знак минус в урле и можно менять фреймворк? Да и ведь эти плейсхолдеры _hello и т… д. где-то ведь определить требуется. Ну и да, судя по их полному примеру из доки так и есть, портянище этих плейсхолдеров.
Делал на базе регулярок и делал маппинг аргументов функций, В итоге можно для параметров задавать очень гибкие правила, можно задавать порядок маппинга параметров и опять же можно было цеплять множественные условия.
d.add(r:regex == "/hello/calc/{a:\d+}/{b:\d+}" && r::method == «GET» && r::get(«method») = «add», [](int a, int b) { return a + b; });
d.add(r:regex == "/hello/calc/{a:\d+}/{b:\d+}" && r::method == «GET» && r::get(«method») = «minus», [](int a, int b) { return a — b; });
А у данного проекта презенташка интересная, а как идешь в глубь документации и видешь, сколько же еще барахла требуется и если прикинуть что будет в большом проекте, становится страшно.
PyTiMa
28.03.2016 15:38-1И всё же, мне кажется что у каждого языка есть своя ниша и вряд ли на backend'е начнут использовать подобное. Но в качестве развлечения довольно интересно.
Как думаете, есть еще перспектива «c++ в вебе, Или все окончательно умрет из-за избыточной сложности?tangro
28.03.2016 15:42+2Странно слышать о "вряд ли на backend'е начнут использовать подобное". На чём, по Вашему, написаны NGinx и Apache? Что обрабатывает Ваши запросы в какой-нибудь экономящей каждый байт и такт компании, типа Гугла и Фейсбука?
prefrontalCortex
28.03.2016 16:28Справедливости ради, за Nginx/Apache обычно бежит что-то написанное на php/python/ruby/что-в-моде-в-этом-сезоне-для-серверсайда.
tangro
28.03.2016 17:45+1Бежит, но может и не бежать, если основной язык разработчика С++, вся задача сводится к "отдать JSON с результатом селекта по базе", а разбираться, что в моде в этом сезоне нет охоты.
StrangerInRed
28.03.2016 16:33-2Nginx и Apache написаны на чистом С. Без С++. Потрудитесь погуглить исходники сначала прежде чем делать такие громкие заявления.
tangro
28.03.2016 17:52+2Где я писал, что они написаны на С++? Я ответил на "вряд ли на backend'е начнут использовать подобное". HTTP-стек в силиконе реализован на базе microhttpd, который написан на том же С, что и nginx с апачем и в некоторых ответах на Stackoverflow его рекомендуют к применению наряду с nginx. Так что утверждение о неиспользовании "подобного" в вебе неверно.
PyTiMa
28.03.2016 18:55Я имел ввиду, что те, кто сейчас пишет бэкенд, привыкли поднять сервер на том же Ruby и с++ вряд ли заменит им "северные языки".
Ясно, что в основе основ лежат те же C, C++ и они развиваются, Но на поверхности их почему то не используют ( тот же API мало кто захочет писать на плюсах). Может из-за сложности, некого неудобства. Люди не хотят вникать в c++, потому что он тяжелее для большинства людей. Из-за этого он все реже используется там, где сейчас весь высокоуровневый веб крутиться и вряд ли есть вероятность, что c++ может заменить тех, кто когда то заменил его.tangro
28.03.2016 21:49+1Всякие там Ruby\Python\PHP пришли в веб благодаря своей динамичности, удобным строковым операциям, низкому порогу вхождения. Но сегодня мы видим ряд новых трендов:
- Динамичность приходит и в языки типа С++ — лямбды всякие там, auto и т.д.
- Много кода уходит из бекэнда в Javascript. При этом самому бекэнду остаются лишь требования скорости обработки запросов, что для С++ самое то.
- В браузеры приходит WebAssembley, что даёт нам потенциал создания клиент-серверных приложений, оперирующих одними структурами данных (а возможно и алгоритмами) С++ на обеих сторонах.
- Для многих крупных компаний этап «лишь бы чё побыстрее написать чтобы урвать новый рынок» прошел и настал этап «надо экономить ресурсы в наших огромных датацентрах» — и мы снова приходим к С и С++
Всё может очень по-разному сложиться.PyTiMa
28.03.2016 22:11Пожалуй да… Спасибо за то, что заставили меня поправить свое представление о вебе.
rotor
28.03.2016 18:39В последнее время появилось много подобных библиотек и фреймворков. Но всем им не хватает полноценного шаблонизатора. Сейчас они все в основном нацелены на отдачу json.
На самом деле, забабашить именно бэкенд не так уж сложно, благо асинхронных библиотек хватает. Я не умаляю заслуг Матье Гарригес — он действительно проделал огромную работу.
А вот генерация страничек, задача по-сложнее. Хотелось бы чего нибудь эдакого, но что бы не тянуть телегу зависимостей. Вот тогда можно про динамические языки и не вспоминать.
bogolt
30.03.2016 21:14+1db("SELECT name from User where id = ?")(id) >> name;
По-моему плохо. Если уж писать на си++ то ради выигрыша в скорости обработки. Тут же в этом коде нет никакой проверки на ошибки от базы данных, что сразу наводит на мысль об исключениях, что сразу же сильно ухудшает скорость работы программы. А ведь то что записи нет в базе данных это стандартная ситуация.
Да и вообще — все эти переиспользования битовых сдвигов если по-моему только затрудняют чтение.
Вот еще один прекрасный пример:
c("SELECT name, age from users")() | [] (std::string& name, int& age) { std::cout << name << " " << age << std::endl; };
Вертикальная черта просто прекрасно на мой взгляд иллюстрирует желание авторов сделать код "красивее" но при этом делающая его еще менее понятным.
Для возврата кодов ошибок HTTP-протокола используются исключения:
Ну конечно, зачем нам глупый return — исключения гораздо круче. А то что они тормозят и вообще созданы для исключительных ситуаций — ну это ерунда, дело житейское.Trrrrr
01.04.2016 11:30Исключения сильно тормозят программу?
Пустословное заявление.
Покажите ваши тесты, которые это показывают.StrangerInRed
01.04.2016 11:45+1Условно
return — 1 такт процессора
exception — 2-3-4 вызова функций с внутренней обработкой ответсвтенности за исключение ~<20 тактовtangro
01.04.2016 13:45Всё так, но в плане обработки HTTP-запросов эти 20 тактов (да ещё и только на "плохих" запросах) — потеряются где-то в сотых долях процента производительности сервера. Когда (и если) дойдут руки до профилирования с целью повышения производительности — будет 150 более важных частей программы для оптимизации, чем замена исключений на return.
StrangerInRed
01.04.2016 15:57+2Все бы так, но не даром была у гугла проблема 10К. И адская оптимизация epol, kqueue, select в open source системах типа Linux, *BSD.
И ничего ж, профилировали. И переписывали узкие места, и добавляли работы ядру.Trrrrr
01.04.2016 16:44А вы сравните еще сколько тактов процессора займет обращение к бд. Исключение на этом фоне даже не смешно.
rotor
01.04.2016 18:15+1Раз уж речь о высокопроизводительном решении, то совсем не обязательно, что будет использована внешняя БД.
БД может быть встроенной. Или это вообще могут быть данные размещенные в памяти.
Ну и потом, вы учитывайте, что в режиме однопоточного сервера (он ведь однопоточный?) ответы должны быть очень быстрыми. И отказ от исключений в том случае, когда без них легко обойтись, таки имеет смысл.
bogolt
01.04.2016 20:02+1В приведенной библиотеке предлагается возвращать ошибку 404 ( как и любую другую хттп ошибку ) через бросание исключения. Тут нет обращения к базе.
bogolt
01.04.2016 20:06+1Что ж, показываю тесты:
Одна и та же программа, собранная в одном случае с выбрасыванием исключения, а в другом с вызовом continue.
int main() { int sum = 0; for (int i = 0;i<99999;i++) { try { if (i%2==0) continue; //throw i; sum += i; } catch(...) {} } return sum; }
Версия с исключением:
real 0m0.107s
user 0m0.103s
sys 0m0.004s
и без:
real 0m0.002s
user 0m0.002s
sys 0m0.000sStrangerInRed
01.04.2016 21:55ну подумаешь в 50 раз медленее, зато паттерны, классы, исключения и шаблоны
все по канону
Trrrrr
03.04.2016 00:23Ваш тест доказывает что вы совсем не понимаете как работают исключения и зачем они.
Если вы пишите рабочий код который должен 99999 раз выбрасывать исключение то я даже не знаю что сказать.
И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?
Я ожидал хотя бы демонстрацию кода с try catch против кода без и сравнение их. Но то что вы проверили полный бред.bogolt
03.04.2016 00:42+1Напомню что мы начали с обсуждения библиотеки призванной обрабатывать хттп запросы. В протоколе хттп сущесвтвуют ошибки, о которых нужно сообщать клиентам. Авторы вышеописанной библиотеки предлагают об ошибках сообщать через выбрасывание исключений.
А это означает что любой дурацкий скрипт, который будет дергать несуществующую страницу будет гарантированно выбрасывать исключение, и тратить процессорное время веб сервера на кучу совершенно ненужных операций.
Приведенный код как раз показывает низкую скорость обработки исключений по сравнению с классическими методами обработки ошибок ( как например возврат кода ошибки ).
И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?
Пример из жизни. Программа рисующая кучу графики на экране. Проекции, кривые, нормали. Пользователь тянет за какую-то точку и все моментально перестраивается на экране. До того момента пока в какой-то волшебной точке все вдруг не начинает тормозить так, что работать с программой становится невозможно. Беглое расследование показало, что в определенных точках нельзя было простроить какую-то кривую, не помню уже точно почему, кажется деление на ноль, в результате код выбрасывал исключение. Логично же? После перевода на коды ошибок тормоза пропали полностью.
frol
Выглядит круто! Я удивлён, что я вижу это на С++. Автор, на мой взгляд, создал шедевр!
P.S. В тексте поста мне не хватило ссылки на сам Silicon, оставлю её здесь: http://siliconframework.org/
tangro
После каждого нового редизайна Хабра чёрта с два поймешь где же ссылка на источник в постах-переводах. Сейчас она находится под строкой с результатами голосования за статью и просмотрами, справа от двух стрелок и ведёт ровно туда же — на http://siliconframework.org/
frol
Да, я её там в итоге и нашёл, но ожидал всё-таки увидеть при первом использовании названия фреймворка в тексте, так что можете её туда ещё продублировать, лишним не будет, я думаю.
tangro
Ок