Фреймворк 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");
Инфопульс Украина
97,00
Creating Value, Delivering Excellence
Поделиться публикацией

Комментарии 35

    +1
    Выглядит круто! Я удивлён, что я вижу это на С++. Автор, на мой взгляд, создал шедевр!
    P.S. В тексте поста мне не хватило ссылки на сам Silicon, оставлю её здесь: http://siliconframework.org/
      +1
      После каждого нового редизайна Хабра чёрта с два поймешь где же ссылка на источник в постах-переводах. Сейчас она находится под строкой с результатами голосования за статью и просмотрами, справа от двух стрелок и ведёт ровно туда же — на http://siliconframework.org/
        +1
        Да, я её там в итоге и нашёл, но ожидал всё-таки увидеть при первом использовании названия фреймворка в тексте, так что можете её туда ещё продублировать, лишним не будет, я думаю.
          0
          Ок
      +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
                                        Напомню что мы начали с обсуждения библиотеки призванной обрабатывать хттп запросы. В протоколе хттп сущесвтвуют ошибки, о которых нужно сообщать клиентам. Авторы вышеописанной библиотеки предлагают об ошибках сообщать через выбрасывание исключений.
                                        А это означает что любой дурацкий скрипт, который будет дергать несуществующую страницу будет гарантированно выбрасывать исключение, и тратить процессорное время веб сервера на кучу совершенно ненужных операций.
                                        Приведенный код как раз показывает низкую скорость обработки исключений по сравнению с классическими методами обработки ошибок ( как например возврат кода ошибки ).
                                        И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?

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

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

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