Фреймворк Silicon — WebAPI на C++

http://siliconframework.org/
  • Перевод
  • Tutorial
Прим. переводчика: в синтаксисе 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<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");
Инфопульс Украина 84,45
Creating Value, Delivering Excellence
Поделиться публикацией
Комментарии 35
  • +1
    Выглядит круто! Я удивлён, что я вижу это на С++. Автор, на мой взгляд, создал шедевр!
    P.S. В тексте поста мне не хватило ссылки на сам Silicon, оставлю её здесь: http://siliconframework.org/
    • +1
      После каждого нового редизайна Хабра чёрта с два поймешь где же ссылка на источник в постах-переводах. Сейчас она находится под строкой с результатами голосования за статью и просмотрами, справа от двух стрелок и ведёт ровно туда же — на http://siliconframework.org/
      • +1
        Да, я её там в итоге и нашёл, но ожидал всё-таки увидеть при первом использовании названия фреймворка в тексте, так что можете её туда ещё продублировать, лишним не будет, я думаю.
  • +1
    Лаконично, но сложно для понимания, много магии.
    • +1
      Всей магии — пара перегруженных операторов. О ней можно и не задумываться, просто использовать как есть.
    • +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 добавить можно?
      • +1
        На счёт ссылки отписался выше. На счёт "зачем в этой библиотеке..." — её суть это дать возможность поднять бэкенд на С++ в 5 минут и 10 строк. Бэкенд это в большинстве случаев HTTP + JSON + сессии + БД. Каждый из этих компонентов, в общем-то, не обязателен и может быть заменён на что-то ещё, но мода диктует нынче такой набор и Silicon даёт его "из коробки". На счёт middleware — посмотрите код и доки (http://siliconframework.org/docs/middlewares.html)
        • 0
          Это уже фреймворк получается.
          Так это и есть фреймворк (Silicon Framework) о чём официальный сайт и гласит:
          The Silicon C++ Web Framework

          Это с подачи автора перевода он стал «библиотекой».
          • +1
            Ну ок, исправил. Как по мне, маловато у него функционала для "фреймворка", но если авторы так называют, то пусть так и будет.
        • +1
          вот тоже в тему: "Crow is C++ microframework for web. (inspired by Python Flask)" https://github.com/ipkn/crow
          • 0
            А вот захотел я знак минус в урле и можно менять фреймворк? Да и ведь эти плейсхолдеры _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; });

            А у данного проекта презенташка интересная, а как идешь в глубь документации и видешь, сколько же еще барахла требуется и если прикинуть что будет в большом проекте, становится страшно.
            • –1
              И всё же, мне кажется что у каждого языка есть своя ниша и вряд ли на backend'е начнут использовать подобное. Но в качестве развлечения довольно интересно.
              Как думаете, есть еще перспектива «c++ в вебе, Или все окончательно умрет из-за избыточной сложности?
              • +2
                Странно слышать о "вряд ли на backend'е начнут использовать подобное". На чём, по Вашему, написаны NGinx и Apache? Что обрабатывает Ваши запросы в какой-нибудь экономящей каждый байт и такт компании, типа Гугла и Фейсбука?
                • 0
                  Справедливости ради, за Nginx/Apache обычно бежит что-то написанное на php/python/ruby/что-в-моде-в-этом-сезоне-для-серверсайда.
                  • +1
                    Бежит, но может и не бежать, если основной язык разработчика С++, вся задача сводится к "отдать JSON с результатом селекта по базе", а разбираться, что в моде в этом сезоне нет охоты.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Но никак не на пхп и тому подобных языках, это и имел ввиду tangro.
                      • +2
                        Где я писал, что они написаны на С++? Я ответил на "вряд ли на backend'е начнут использовать подобное". HTTP-стек в силиконе реализован на базе microhttpd, который написан на том же С, что и nginx с апачем и в некоторых ответах на Stackoverflow его рекомендуют к применению наряду с nginx. Так что утверждение о неиспользовании "подобного" в вебе неверно.
                      • 0
                        Я имел ввиду, что те, кто сейчас пишет бэкенд, привыкли поднять сервер на том же Ruby и с++ вряд ли заменит им "северные языки".
                        Ясно, что в основе основ лежат те же C, C++ и они развиваются, Но на поверхности их почему то не используют ( тот же API мало кто захочет писать на плюсах). Может из-за сложности, некого неудобства. Люди не хотят вникать в c++, потому что он тяжелее для большинства людей. Из-за этого он все реже используется там, где сейчас весь высокоуровневый веб крутиться и вряд ли есть вероятность, что c++ может заменить тех, кто когда то заменил его.
                        • +1
                          Всякие там Ruby\Python\PHP пришли в веб благодаря своей динамичности, удобным строковым операциям, низкому порогу вхождения. Но сегодня мы видим ряд новых трендов:
                          1. Динамичность приходит и в языки типа С++ — лямбды всякие там, auto и т.д.
                          2. Много кода уходит из бекэнда в Javascript. При этом самому бекэнду остаются лишь требования скорости обработки запросов, что для С++ самое то.
                          3. В браузеры приходит WebAssembley, что даёт нам потенциал создания клиент-серверных приложений, оперирующих одними структурами данных (а возможно и алгоритмами) С++ на обеих сторонах.
                          4. Для многих крупных компаний этап «лишь бы чё побыстрее написать чтобы урвать новый рынок» прошел и настал этап «надо экономить ресурсы в наших огромных датацентрах» — и мы снова приходим к С и С++

                          Всё может очень по-разному сложиться.
                          • 0
                            Пожалуй да… Спасибо за то, что заставили меня поправить свое представление о вебе.
                    • 0
                      В последнее время появилось много подобных библиотек и фреймворков. Но всем им не хватает полноценного шаблонизатора. Сейчас они все в основном нацелены на отдачу json.
                      На самом деле, забабашить именно бэкенд не так уж сложно, благо асинхронных библиотек хватает. Я не умаляю заслуг Матье Гарригес — он действительно проделал огромную работу.
                      А вот генерация страничек, задача по-сложнее. Хотелось бы чего нибудь эдакого, но что бы не тянуть телегу зависимостей. Вот тогда можно про динамические языки и не вспоминать.
                      • +1
                        db("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 — исключения гораздо круче. А то что они тормозят и вообще созданы для исключительных ситуаций — ну это ерунда, дело житейское.
                        • 0
                          Исключения сильно тормозят программу?
                          Пустословное заявление.
                          Покажите ваши тесты, которые это показывают.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • 0
                              Всё так, но в плане обработки HTTP-запросов эти 20 тактов (да ещё и только на "плохих" запросах) — потеряются где-то в сотых долях процента производительности сервера. Когда (и если) дойдут руки до профилирования с целью повышения производительности — будет 150 более важных частей программы для оптимизации, чем замена исключений на return.
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • 0
                                  А вы сравните еще сколько тактов процессора займет обращение к бд. Исключение на этом фоне даже не смешно.
                                  • +1
                                    Раз уж речь о высокопроизводительном решении, то совсем не обязательно, что будет использована внешняя БД.
                                    БД может быть встроенной. Или это вообще могут быть данные размещенные в памяти.
                                    Ну и потом, вы учитывайте, что в режиме однопоточного сервера (он ведь однопоточный?) ответы должны быть очень быстрыми. И отказ от исключений в том случае, когда без них легко обойтись, таки имеет смысл.
                                • +1
                                  В приведенной библиотеке предлагается возвращать ошибку 404 ( как и любую другую хттп ошибку ) через бросание исключения. Тут нет обращения к базе.
                              • +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.000s
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • 0
                                    Ваш тест доказывает что вы совсем не понимаете как работают исключения и зачем они.
                                    Если вы пишите рабочий код который должен 99999 раз выбрасывать исключение то я даже не знаю что сказать.
                                    И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?
                                    Я ожидал хотя бы демонстрацию кода с  try catch  против кода без и сравнение их. Но то что вы проверили полный бред.
                                    • +1
                                      Напомню что мы начали с обсуждения библиотеки призванной обрабатывать хттп запросы. В протоколе хттп сущесвтвуют ошибки, о которых нужно сообщать клиентам. Авторы вышеописанной библиотеки предлагают об ошибках сообщать через выбрасывание исключений.
                                      А это означает что любой дурацкий скрипт, который будет дергать несуществующую страницу будет гарантированно выбрасывать исключение, и тратить процессорное время веб сервера на кучу совершенно ненужных операций.
                                      Приведенный код как раз показывает низкую скорость обработки исключений по сравнению с классическими методами обработки ошибок ( как например возврат кода ошибки ).
                                      И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?

                                      Пример из жизни. Программа рисующая кучу графики на экране. Проекции, кривые, нормали. Пользователь тянет за какую-то точку и все моментально перестраивается на экране. До того момента пока в какой-то волшебной точке все вдруг не начинает тормозить так, что работать с программой становится невозможно. Беглое расследование показало, что в определенных точках нельзя было простроить какую-то кривую, не помню уже точно почему, кажется деление на ноль, в результате код выбрасывал исключение. Логично же? После перевода на коды ошибок тормоза пропали полностью.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое