Прим. переводчика: в синтаксисе 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");

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


  1. frol
    28.03.2016 11:03
    +1

    Выглядит круто! Я удивлён, что я вижу это на С++. Автор, на мой взгляд, создал шедевр!
    P.S. В тексте поста мне не хватило ссылки на сам Silicon, оставлю её здесь: http://siliconframework.org/


    1. tangro
      28.03.2016 11:18
      +1

      После каждого нового редизайна Хабра чёрта с два поймешь где же ссылка на источник в постах-переводах. Сейчас она находится под строкой с результатами голосования за статью и просмотрами, справа от двух стрелок и ведёт ровно туда же — на http://siliconframework.org/


      1. frol
        28.03.2016 11:21
        +1

        Да, я её там в итоге и нашёл, но ожидал всё-таки увидеть при первом использовании названия фреймворка в тексте, так что можете её туда ещё продублировать, лишним не будет, я думаю.


        1. tangro
          28.03.2016 11:26

          Ок


  1. angru
    28.03.2016 11:21
    +1

    Лаконично, но сложно для понимания, много магии.


    1. tangro
      28.03.2016 11:21
      +1

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


  1. 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 добавить можно?


    1. tangro
      28.03.2016 11:24
      +1

      На счёт ссылки отписался выше. На счёт "зачем в этой библиотеке..." — её суть это дать возможность поднять бэкенд на С++ в 5 минут и 10 строк. Бэкенд это в большинстве случаев HTTP + JSON + сессии + БД. Каждый из этих компонентов, в общем-то, не обязателен и может быть заменён на что-то ещё, но мода диктует нынче такой набор и Silicon даёт его "из коробки". На счёт middleware — посмотрите код и доки (http://siliconframework.org/docs/middlewares.html)


    1. frol
      28.03.2016 11:31

      Это уже фреймворк получается.
      Так это и есть фреймворк (Silicon Framework) о чём официальный сайт и гласит:
      The Silicon C++ Web Framework

      Это с подачи автора перевода он стал «библиотекой».


      1. tangro
        28.03.2016 12:07
        +1

        Ну ок, исправил. Как по мне, маловато у него функционала для "фреймворка", но если авторы так называют, то пусть так и будет.


  1. angru
    28.03.2016 11:25
    +1

    вот тоже в тему: "Crow is C++ microframework for web. (inspired by Python Flask)" https://github.com/ipkn/crow


  1. 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; });

    А у данного проекта презенташка интересная, а как идешь в глубь документации и видешь, сколько же еще барахла требуется и если прикинуть что будет в большом проекте, становится страшно.


  1. PyTiMa
    28.03.2016 15:38
    -1

    И всё же, мне кажется что у каждого языка есть своя ниша и вряд ли на backend'е начнут использовать подобное. Но в качестве развлечения довольно интересно.
    Как думаете, есть еще перспектива «c++ в вебе, Или все окончательно умрет из-за избыточной сложности?


    1. tangro
      28.03.2016 15:42
      +2

      Странно слышать о "вряд ли на backend'е начнут использовать подобное". На чём, по Вашему, написаны NGinx и Apache? Что обрабатывает Ваши запросы в какой-нибудь экономящей каждый байт и такт компании, типа Гугла и Фейсбука?


      1. prefrontalCortex
        28.03.2016 16:28

        Справедливости ради, за Nginx/Apache обычно бежит что-то написанное на php/python/ruby/что-в-моде-в-этом-сезоне-для-серверсайда.


        1. tangro
          28.03.2016 17:45
          +1

          Бежит, но может и не бежать, если основной язык разработчика С++, вся задача сводится к "отдать JSON с результатом селекта по базе", а разбираться, что в моде в этом сезоне нет охоты.


      1. StrangerInRed
        28.03.2016 16:33
        -2

        Nginx и Apache написаны на чистом С. Без С++. Потрудитесь погуглить исходники сначала прежде чем делать такие громкие заявления.


        1. Trrrrr
          28.03.2016 17:43

          Но никак не на пхп и тому подобных языках, это и имел ввиду tangro.


        1. tangro
          28.03.2016 17:52
          +2

          Где я писал, что они написаны на С++? Я ответил на "вряд ли на backend'е начнут использовать подобное". HTTP-стек в силиконе реализован на базе microhttpd, который написан на том же С, что и nginx с апачем и в некоторых ответах на Stackoverflow его рекомендуют к применению наряду с nginx. Так что утверждение о неиспользовании "подобного" в вебе неверно.


      1. PyTiMa
        28.03.2016 18:55

        Я имел ввиду, что те, кто сейчас пишет бэкенд, привыкли поднять сервер на том же Ruby и с++ вряд ли заменит им "северные языки".
        Ясно, что в основе основ лежат те же C, C++ и они развиваются, Но на поверхности их почему то не используют ( тот же API мало кто захочет писать на плюсах). Может из-за сложности, некого неудобства. Люди не хотят вникать в c++, потому что он тяжелее для большинства людей. Из-за этого он все реже используется там, где сейчас весь высокоуровневый веб крутиться и вряд ли есть вероятность, что c++ может заменить тех, кто когда то заменил его.


        1. tangro
          28.03.2016 21:49
          +1

          Всякие там Ruby\Python\PHP пришли в веб благодаря своей динамичности, удобным строковым операциям, низкому порогу вхождения. Но сегодня мы видим ряд новых трендов:

          1. Динамичность приходит и в языки типа С++ — лямбды всякие там, auto и т.д.
          2. Много кода уходит из бекэнда в Javascript. При этом самому бекэнду остаются лишь требования скорости обработки запросов, что для С++ самое то.
          3. В браузеры приходит WebAssembley, что даёт нам потенциал создания клиент-серверных приложений, оперирующих одними структурами данных (а возможно и алгоритмами) С++ на обеих сторонах.
          4. Для многих крупных компаний этап «лишь бы чё побыстрее написать чтобы урвать новый рынок» прошел и настал этап «надо экономить ресурсы в наших огромных датацентрах» — и мы снова приходим к С и С++

          Всё может очень по-разному сложиться.


          1. PyTiMa
            28.03.2016 22:11

            Пожалуй да… Спасибо за то, что заставили меня поправить свое представление о вебе.


  1. rotor
    28.03.2016 18:39

    В последнее время появилось много подобных библиотек и фреймворков. Но всем им не хватает полноценного шаблонизатора. Сейчас они все в основном нацелены на отдачу json.
    На самом деле, забабашить именно бэкенд не так уж сложно, благо асинхронных библиотек хватает. Я не умаляю заслуг Матье Гарригес — он действительно проделал огромную работу.
    А вот генерация страничек, задача по-сложнее. Хотелось бы чего нибудь эдакого, но что бы не тянуть телегу зависимостей. Вот тогда можно про динамические языки и не вспоминать.


  1. bogolt
    30.03.2016 21:14
    +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 — исключения гораздо круче. А то что они тормозят и вообще созданы для исключительных ситуаций — ну это ерунда, дело житейское.


    1. Trrrrr
      01.04.2016 11:30

      Исключения сильно тормозят программу?
      Пустословное заявление.
      Покажите ваши тесты, которые это показывают.


      1. StrangerInRed
        01.04.2016 11:45
        +1

        Условно
        return — 1 такт процессора
        exception — 2-3-4 вызова функций с внутренней обработкой ответсвтенности за исключение ~<20 тактов


        1. tangro
          01.04.2016 13:45

          Всё так, но в плане обработки HTTP-запросов эти 20 тактов (да ещё и только на "плохих" запросах) — потеряются где-то в сотых долях процента производительности сервера. Когда (и если) дойдут руки до профилирования с целью повышения производительности — будет 150 более важных частей программы для оптимизации, чем замена исключений на return.


          1. StrangerInRed
            01.04.2016 15:57
            +2

            Все бы так, но не даром была у гугла проблема 10К. И адская оптимизация epol, kqueue, select в open source системах типа Linux, *BSD.
            И ничего ж, профилировали. И переписывали узкие места, и добавляли работы ядру.


            1. Trrrrr
              01.04.2016 16:44

              А вы сравните еще сколько тактов процессора займет обращение к бд. Исключение на этом фоне даже не смешно.


              1. rotor
                01.04.2016 18:15
                +1

                Раз уж речь о высокопроизводительном решении, то совсем не обязательно, что будет использована внешняя БД.
                БД может быть встроенной. Или это вообще могут быть данные размещенные в памяти.
                Ну и потом, вы учитывайте, что в режиме однопоточного сервера (он ведь однопоточный?) ответы должны быть очень быстрыми. И отказ от исключений в том случае, когда без них легко обойтись, таки имеет смысл.


          1. bogolt
            01.04.2016 20:02
            +1

            В приведенной библиотеке предлагается возвращать ошибку 404 ( как и любую другую хттп ошибку ) через бросание исключения. Тут нет обращения к базе.


      1. 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.000s


        1. StrangerInRed
          01.04.2016 21:55

          ну подумаешь в 50 раз медленее, зато паттерны, классы, исключения и шаблоны
          все по канону


        1. Trrrrr
          03.04.2016 00:23

          Ваш тест доказывает что вы совсем не понимаете как работают исключения и зачем они.
          Если вы пишите рабочий код который должен 99999 раз выбрасывать исключение то я даже не знаю что сказать.
          И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?
          Я ожидал хотя бы демонстрацию кода с  try catch  против кода без и сравнение их. Но то что вы проверили полный бред.


          1. bogolt
            03.04.2016 00:42
            +1

            Напомню что мы начали с обсуждения библиотеки призванной обрабатывать хттп запросы. В протоколе хттп сущесвтвуют ошибки, о которых нужно сообщать клиентам. Авторы вышеописанной библиотеки предлагают об ошибках сообщать через выбрасывание исключений.
            А это означает что любой дурацкий скрипт, который будет дергать несуществующую страницу будет гарантированно выбрасывать исключение, и тратить процессорное время веб сервера на кучу совершенно ненужных операций.
            Приведенный код как раз показывает низкую скорость обработки исключений по сравнению с классическими методами обработки ошибок ( как например возврат кода ошибки ).

            И что вы доказали — что если постоянно бросать исключения то это будет медленно? Какой в этом смысл?

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