RESTinio — это асинхронный HTTP-сервер. Простой пример из практики: отдача большого объема данных в ответ


    Недавно мне довелось поработать над приложением, которое должно было контролировать скорость своих исходящих подключений. Например, подключаясь к одному URL приложение должно было ограничить себя, скажем, 200KiB/sec. А подключаясь к другому URL — всего 30KiB/sec.


    Самым интересным моментом здесь оказалось тестирование этих самых ограничений. Мне потребовался HTTP-сервер, который бы отдавал трафик с какой-то заданной скоростью, например, 512KiB/sec. Тогда бы я мог видеть, действительно ли приложение выдерживает скорость 200KiB/sec или же оно срывается на более высокие скорости.


    Но где взять такой HTTP-сервер?


    Поскольку я имею некоторое отношение к встраиваемому в С++ приложения HTTP-серверу RESTinio, то не придумал ничего лучше, чем быстренько набросать на коленке простой тестовый HTTP-сервер, который способен отдавать клиенту длинный поток исходящих данных.


    О том, насколько это было просто и хотелось бы рассказать в статье. Заодно узнать в комментариях, действительно ли это просто или же я сам себя обманываю. В принципе, данную статью можно рассматривать как продолжение предыдущей статьи про RESTinio под названием "RESTinio — это асинхронный HTTP-сервер. Асинхронный". Посему, если кому-то интересно прочитать о реальном, пусть и не очень серьезном применении RESTinio, то милости прошу под кат.


    Общая идея


    Общая идея упомянутого выше тестового сервера очень проста: когда клиент подключается к серверу и выполняет HTTP GET запрос, то взводится таймер, срабатывающий раз в секунду. Когда таймер срабатывает, то клиенту отсылается очередной блок данных заданного размера.


    Но все несколько сложнее


    Если клиент вычитывает данные с меньшим темпом, нежели отсылает сервер, то просто отсылать по N килобайт раз в секунду не есть хорошая идея. Поскольку данные начнут скапливаться в сокете и ни к чему хорошему это не приведет.


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


    Звучит разумно, но ведь операции ввода-вывода скрыты в потрохах RESTinio… Как тут узнать, можно ли записывать следующую порцию данных или нет?


    Из данной ситуации можно выйти, если использовать after-write нотификаторы, которые есть в RESTinio. Например, мы можем написать так:


    void request_handler(restinio::request_handle_t req) {
       req->create_response() // Начинаем формировать ответ.
          ... // Наполняем ответ содержимым.
          .done([](const auto & ec) {
              ... // Вот этот код будет вызван когда запись ответа закончится.
          });
    }

    Лямбда, переданная в метод done() будет вызвана когда RESTinio завершит запись исходящих данных. Соответственно, если сокет какое-то время был не готов к записи, то лямбда будет вызвана не сразу, а после того, как сокет придет в должное состояние и примет все исходящие данные.


    За счет использования after-write нотификаторов логика работы тестового сервера будет такой:


    • отсылаем очередную порцию данных, вычисляем время, когда нам нужно было бы отослать следующую порцию при нормальном развитии событий;
    • вешаем after-write нотификатор на очередную порцию данных;
    • когда after-write нотификатор вызывается, мы проверяем, наступило ли время отсылки следующей порции. Если наступило, то сразу же инициируем отсылку следующей порции. Если не наступило, то взводим таймер.

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


    И еще немного сложного: chunked_output


    RESTinio поддерживает три способа формирования ответа на HTTP-запрос. Самый простой способ, который применяется по умолчанию, в данном случае не подходит, т.к. мне требуется практически бесконечный поток исходящих данных. И такой поток, естественно, нельзя отдать в единственный вызов метода set_body.


    Поэтому в описываемом тестовом сервере используется т.н. chunked_output. Т.е. при создании ответа я указываю RESTinio, что ответ будет формироваться частями. После чего просто периодически вызываю методы append_chunk для добавления к ответу очередной части и flush для записи накопленных частей в сокет.


    А давайте уже посмотрим в код!


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


    Функция request_processor и её подручные


    Функция request_processor вызывается для обработки нужных мне HTTP GET запросов. Ей в качестве аргументов передаются:


    • Asio-шный io_context, на котором ведется вся работа (он потребуется, например, для взведения таймеров);
    • размер одной части ответа. Т.е. если мне нужно отдавать исходящий поток с темпом в 512KiB/sec, то в качестве этого параметра будет передано значение 512KiB;
    • количество частей в ответе. На случай, если поток должен иметь какую-то ограниченную длину. Например, если нужно отдавать поток с темпом 512KiB/sec в течении 5 минут, то в качестве этого параметра будет передано значение 300 (60 блоков в минуту в течении 5 минут);
    • ну и сам входящий запрос для обработки.

    Внутри request_processor создается объект с информацией о запросе и параметрах его обработки, после чего эта самая обработка и начинается:


    void request_processor(
            asio_ns::io_context & ctx,
            std::size_t chunk_size,
            std::size_t count,
            restinio::request_handle_t req) {
        auto data = std::make_shared<response_data>(
                ctx,
                chunk_size,
                req->create_response<output_t>(),
                count);
    
        data->response_
            .append_header(restinio::http_field::server, "RESTinio")
            .append_header_date_field()
            .append_header(
                    restinio::http_field::content_type,
                    "text/plain; charset=utf-8")
            .flush();
    
        send_next_portion(data);
    }

    Тип response_data, содержащий все относящиеся к запросу параметры, выглядит следующим образом:


    struct response_data {
        asio_ns::io_context & io_ctx_;
        std::size_t chunk_size_;
        response_t response_;
        std::size_t counter_;
    
        response_data(
            asio_ns::io_context & io_ctx,
            std::size_t chunk_size,
            response_t response,
            std::size_t counter)
            : io_ctx_{io_ctx}
            , chunk_size_{chunk_size}
            , response_{std::move(response)}
            , counter_{counter}
        {}
    };

    Тут нужно заметить, что одна из причин появления структуры response_data состоит в том, что объект типа restinio::response_builder_t<restinio::chunked_output_t> (а именно этот тип спрятан за коротким псевдонимом response_t) является moveable-, но не copyable-типом (по аналогии с std::unique_ptr). Поэтому этот объект нельзя просто так захватить в лямбда-функции, которая затем оборачивается в std::function. Но если объект-response поместить в динамически созданный экземпляр response_data, то умный указатель на экземпляр reponse_data уже можно без проблем захватывать в лямбда-функции с последующим сохранением этой лямбды в std::function.


    Функция send_next_portion


    Функция send_next_portion вызывается каждый раз, когда требуется отослать клиенту очередную часть ответа. Ничего сложного в ней не происходит, поэтому выглядит она достаточно просто и лаконично:


    void send_next_portion(response_data_shptr data) {
        data->response_.append_chunk(make_buffer(data->chunk_size_));
    
        if(1u == data->counter_) {
            data->response_.flush();
            data->response_.done();
        }
        else {
            data->counter_ -= 1u;
            data->response_.flush(make_done_handler(data));
        }
    }

    Т.е. отсылаем очередную часть. И, если эта часть была последней, то завершаем обработку запроса. А если не последняя, то в метод flush передается after-write нотификатор, который создается, пожалуй, наиболее сложной функцией данного примера.


    Функция make_done_handler


    Функция make_done_handler отвечает за создание лямбды, которая будет передана в RESTinio в качестве after-write нотификатора. Этот нотификатор должен проверить, завершилась ли запись очередной части ответа успешно. Если да, то нужно разобраться, следует ли следующую часть отослать сразу же (т.е. были "тормоза" в сокете и темп отсылки выдерживать не получается), либо же после некоторой паузы. Если нужна пауза, то она обеспечивается через взведение таймера.


    В общем-то, несложные действия, но в коде получается лямбда внутри лямбды, что может смутить людей, не привыкших к "современному" С++. Которому не так уж и мало лет чтобы называться современным ;)


    auto make_done_handler(response_data_shptr data) {
        const auto next_timepoint = steady_clock::now() + 1s;
        return [=](const auto & ec) {
            if(!ec) {
                const auto now = steady_clock::now();
                if(now < next_timepoint) {
                    auto timer = std::make_shared<asio_ns::steady_timer>(data->io_ctx_);
                    timer->expires_after(next_timepoint - now);
                    timer->async_wait([timer, data](const auto & ec) {
                            if(!ec)
                                send_next_portion(data);
                        });
                }
                else
                    data->io_ctx_.post([data] { send_next_portion(data); });
            }
        };
    }

    На мой взгляд, основная сложность в этом коде проистекает из-за особенностей создания и "взвода" таймеров в Asio. По-моему, получается как-то слишком уж многословно. Но тут уж что есть, то есть. Зато не нужно никаких дополнительных библиотек привлекать.


    Подключение express-like роутера


    Показанные выше request_processor, send_next_portion и make_done_handler в общем-то и составляли самую первую версию моего тестового сервера, написанного буквально за 15 или 20 минут.


    Но через пару дней использования этого тестового сервера оказалось, что в нем есть серьезный недостаток: он всегда отдает ответный поток с одинаковой скоростью. Скомпилировал со скоростью 512KiB/sec — отдает всем 512KiB/sec. Перекомпилировал со скоростью 20KiB/sec — будет отдавать всем 20KiB/sec и никак иначе. Что было неудобно, т.к. стало нужно иметь возможность получать ответы разной "толщины".


    Тогда и появилась идея: а что, если скорость отдачи будет запрашиваться прямо в URL? Например, сделали запрос на localhost:8080/ и получили ответ с заранее заданной скоростью. А если сделали запрос на localhost:8080/128K, то стали получать ответ со скоростью 128KiB/sec.


    Потом мысль пошла еще дальше: в URL также можно задавать и количество отдельных частей в ответе. Т.е. запрос localhost:8080/128K/3000 приведет к выдаче потока из 3000 частей со скоростью 128KiB/sec.


    Нет проблем. В RESTinio есть возможность использовать маршрутизатор запросов, сделанный под влиянием ExpressJS. В итоге появилась вот такая функция описания обработчиков входящих HTTP-запросов:


    auto make_router(asio_ns::io_context & ctx) {
        auto router = std::make_unique<router_t>();
    
        router->http_get("/", [&ctx](auto req, auto) {
                request_processor(ctx, 100u*1024u, 10000u, std::move(req));
                return restinio::request_accepted();
            });
    
        router->http_get(
                    R"(/:value(\d+):multiplier([MmKkBb]?))",
                    [&ctx](auto req, auto params) {
    
                const auto chunk_size = extract_chunk_size(params);
    
                if(0u != chunk_size) {
                    request_processor(ctx, chunk_size, 10000u, std::move(req));
                    return restinio::request_accepted();
                }
                else
                    return restinio::request_rejected();
            });
    
        router->http_get(
                    R"(/:value(\d+):multiplier([MmKkBb]?)/:count(\d+))",
                    [&ctx](auto req, auto params) {
    
                const auto chunk_size = extract_chunk_size(params);
                const auto count = restinio::cast_to<std::size_t>(params["count"]);
    
                if(0u != chunk_size && 0u != count) {
                    request_processor(ctx, chunk_size, count, std::move(req));
                    return restinio::request_accepted();
                }
                else
                    return restinio::request_rejected();
            });
    
        return router;
    }

    Здесь формируются обработчики HTTP GET запросов для URL трех типов:


    • вида http://localhost/;
    • вида http://localhost/<speed>[<U>]/;
    • вида http://localhost/<speed>[<U>]/<count>/

    Где speed — это число, определяющее скорость, а U — это опциональный мультипликатор, который указывает, в каких единицах задана скорость. Так 128 или 128b означает скорость в 128 байт в секунду. А 128k — 128 килобайт в секунду.


    На каждый URL вешается своя лямбда функция, которая разбирается с полученными параметрами, если все нормально, вызывает уже показанную выше функцию request_processor.


    Вспомогательная функция extract_chunk_size выглядит следующим образом:


    std::size_t extract_chunk_size(const restinio::router::route_params_t & params) {
        const auto multiplier = [](const auto sv) noexcept -> std::size_t {
            if(sv.empty() || "B" == sv || "b" == sv) return 1u;
            else if("K" == sv || "k" == sv) return 1024u;
            else return 1024u*1024u;
        };
    
        return restinio::cast_to<std::size_t>(params["value"]) *
                multiplier(params["multiplier"]);
    }

    Здесь C++ная лямбда используется для эмуляции локальных функций из других языков программирования.


    Функция main


    Осталось посмотреть, как все это запускается в функции main:


    using router_t = restinio::router::express_router_t<>;
    ...
    int main() {
        struct traits_t : public restinio::default_single_thread_traits_t {
            using logger_t = restinio::single_threaded_ostream_logger_t;
            using request_handler_t = router_t;
        };
    
        asio_ns::io_context io_ctx;
    
        restinio::run(
            io_ctx,
            restinio::on_this_thread<traits_t>()
                .port(8080)
                .address("localhost")
                .write_http_response_timelimit(60s)
                .request_handler(make_router(io_ctx)));
    
        return 0;
    }

    Что здесь происходит:


    1. Поскольку мне нужен не обычный штатный роутер запросов (который вообще ничего делать сам не может и перекладывает всю работу на плечи программиста), то я определяю новые свойства для своего HTTP-сервера. Для этого беру штатные свойства однопоточного HTTP-сервера (тип restinio::default_single_thread_traits_t) и указываю, что в качестве обработчика запросов будет использоваться экземпляр express-like роутера. Заодно, чтобы контролировать, что происходит внутри, указываю, чтобы HTTP-сервер использовал настоящий логгер (по умолчанию используется null_logger_t который вообще ничего не логирует).
    2. Поскольку мне нужно взводить таймеры внутри after-write нотификаторов, то мне нужен экземпляр io_context, с которым я смог бы работать. Поэтому я его создаю сам. Это дает мне возможность передать ссылку на мой io_context в функцию make_router.
    3. Остается только запустить HTTP-сервер в однопоточном варианте на ранее созданном мной io_context-е. Функция restinio::run вернет управление только когда HTTP-сервер завершит свою работу.

    Заключение


    В статье не был показан полный код моего тестового сервера, только его основные моменты. Полный код, которого чуть-чуть больше из-за дополнительных typedef-ов и вспомогательных функций, несколько подлиннее. Увидеть его можно здесь. На момент написания статьи это 185 строк, включая пустые строки и комментарии. Ну и написаны эти 185 строк за пару-тройку подходов суммарной длительностью вряд ли более часа.


    Мне такой результат понравился и задача оказалась интересной. В практическом плане быстро был получен нужный мне вспомогательный инструмент. И в плане дальнейшего развития RESTinio появились кое-какие мысли.


    В общем, если кто-то еще не пробовал RESTinio, то я приглашаю попробовать. Сам проект живет на GitHub. Задать вопрос или высказать свои предложения можно в Google-группе или прямо здесь, в комментариях.

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +3
      Мне потребовался HTTP-сервер, который бы отдавал трафик с какой-то заданной скоростью, например, 512KiB/sec

      Казалось бы, при чём тут man 8 tc-netem ?

        –1

        Не знаю, но может вы расскажите?

          +3

          Ну что-то в духе:


          tc qdisc add dev lo ingress
          ip addr add 127.0.0.42 dev lo # ip for each server
          tc filter add dev lo parent ffff: protocol ip u32 match ip src 127.0.0.42/32 flowid :1 police rate 1.0mbit 

          Дальше биндим любимый веб сервер на заданный адрес и развлекаемся. Так можно ещё и "плохое" соединение тестировать, в том числе и с заданным распределением потери пакетов.

            0
            Дальше биндим любимый веб сервер на заданный адрес и развлекаемся

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

              0

              Отдавать бесконечную статику разумеется. Например тот же /dev/random, да или даже /dev/zero, что бы cpu не насиловать почём зря.


              Я не большой специалист в web технологиях(и пиаре своих поделок), но кажется, что даже:


              python -m SimpleHTTPServer

              вполне себе сдюжил бы.


              Может конечно на прикладном уровне точнее, чем специально обученным инструментом, но вот вопрос — кто вашу наколенную поделку за час написанную протестировал?

                0

                А вот этот набор команд + какой-то сервер, который будет из dev/random брать поток байт и превращать их в корректный HTTP-ответ, кто будет тестировать?

                  +1

                  Наверное, какие-то неудачники из разработчиков ядра linux и coreutils для gnu/linux, а так же — пара полных остолопов трудящихся над стандартной библиотекой пайтон.

                    0

                    Я у вас не про качество реализации tc-netem спрашивал. Но, судя по вашим комментариям, вы не просто так этого не понимаете.

                      0

                      С удовольствием расширю свой кругозор с помощью ваших пояснений.

                        0

                        Дело не в кругозоре.

                          +2
                          Дело не в кругозоре.

                          Ну не в нём, так не в нём, чего флудить то. Проще сказать почему не взлетит, а ваше решение лучше, если вдруг вы обладаете каким-то тайным знанием, чем ограничение скорости http будет отличаться от такого же ограничения для tcp. На уровень ниже это работало вполне себе точно. Вполне себе допускаю, что велосипед оправдан и чего-то не учёл, однако — аргументации не наблюдается.


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

                            0

                            Так если ресурс технический, то что же вы разговор о рекламе переводите?


                            По сути: каждый волен выбирать те инструменты, которые он знает и с помощью которых он может решить задачу быстро и качественно. Для вас это tc-netem и Python. Для меня, который про tc-netem ничего до ваших комментариев не знал, это C++ и RESTinio. Для кого-то Go, для кого-то что-то другое.

                              0

                              Ну я не услышал аргументации, а упоминание одного и того же инструмента раз 50 в одном коротеньком тексте слегка напоминает знаете ли… "Покупайте наших слонов, советские слоны самые лучшие слоны в мире".


                              каждый волен выбирать те инструменты, которые он знает и с помощью которых он может решить задачу быстро и качественно

                              До определённой степени, думаю. Если бы человечество следовало этому принципу безоговорочно, то каждое поколение изобретало бы колесо раз по 5.


                              Меня учили, что хорошая статья должна рассматривать альтернативы или указывать на их отсутвие в разделе "мотивация". Ну либо сразу предупреждать, что это из раздела "смотрите как я могу", например, тут проскакивали реализации игры "Жизнь" на CMake или что-то в таком духе.


                              Также — как разработчику сетевого приложения, крайне рекомендую освоить инструменты вида tshark, ip и tc. Я вот плохо представляю как без подобного тулкита протестировать а как поведёт себя сервер в случае сбоев на строне провайдера например. Вот про что я почитал бы на самом деле — это про методики тестирования и замеров производительности вашего решения(про сам сервер разумеется а не приложение из этой статьи).

                                0
                                Ну я не услышал аргументации

                                Аргументации в чем? Если в том, что это "единственно правильный" подход, так её и не будет, поскольку у статьи не было цели показать, что подобные задачи нужно решать таким и только таким образом.


                                а упоминание одного и того же инструмента раз 50 в одном коротеньком тексте слегка напоминает знаете ли…

                                Если статья рассказывает о том, как что-то сделать с помощью инструмента X, то частое упоминание X в тексте неизбежно. Если вы напишите свою статью о том, как того же самого достичь с помощью tc-netem, то tc-netem будет упоминаться вами в тексте не реже.


                                Меня учили, что хорошая статья должна рассматривать альтернативы или указывать на их отсутвие в разделе "мотивация".

                                А меня учили, что сперва нужно внимательно прочитать то, что вы собирались покрывать праведным гневом. Но раз вы не дали себе такого труда, то придется сделать небольшой экскурс в историю:


                                • некоторое время назад мы стали делать встраиваемый асинхронный HTTP-сервер для C++, поскольку существовавшие тогда варианты нас не устраивали;
                                • основной упор в нашей разработке был сделал именно на асинхронность. Что находило свое отражение и в примерах, и в документации;
                                • тем не менее, мы регулярно сталкивались с вопросами пользователей о том, поддерживает ли наш инструмент асинхронность и как им быть, если они не могут сформировать HTTP-response прямо в момент обработки HTTP-request;
                                • поток этих вопросов был настолько стабильным, что пришлось делать отдельную большую статью на эту тему;
                                • но проблема той большой статьи была в отсутствии примеров из практики;
                                • а данная статья такой пример как раз и приводит. Посему она является непосредственным продолжением предыдущей. Что отражается, во-первых, как в самом названии. Так и, во-вторых, в специальном предупреждении-уведомлении во вступлении:

                                В принципе, данную статью можно рассматривать как продолжение предыдущей статьи про RESTinio под названием "RESTinio — это асинхронный HTTP-сервер. Асинхронный".

                                Но вы, очевидно, увидели в статье что-то другое, то, о чем я вообще не писал. И начали спорить не со мной, а с собственным восприятием прочитанного.


                                Также — как разработчику сетевого приложения, крайне рекомендую освоить инструменты вида tshark, ip и tc. Я вот плохо представляю как без подобного тулкита протестировать а как поведёт себя сервер в случае сбоев на строне провайдера например.

                                Что еще раз говорит о вашем специфическом восприятии текста. Не нужно было тестировать поведение сервера. Ну вот вообще. Задача была в том, чтобы протестировать клиента. Для чего потребовался специализированный HTTP-сервер.


                                Если вы знаете, как такой специализированный сервер собрать меньшими количествами усилий на tc-netem и Python, то напишите свою статью. Наверняка многие разработчики узнают что-то новое.

                                  +1
                                  Аргументации в чем? Если в том, что это "единственно правильный" подход, так её и не будет, поскольку у статьи не было цели показать, что подобные задачи нужно решать таким и только таким образом.

                                  Хотя бы в том, что подход рабочий, как он был протестирован, какая погешность измерений, как точно удаётся выставить ограничение. Вот это всё. Заключение уровня я набросал тут поделку за час, не тестировал но вы мне верьте на слово, что это работает — ну такое себе, у меня симпатии не вызывает например.


                                  Но вы, очевидно, увидели в статье что-то другое, то, о чем я вообще не писал.

                                  Я увидел в статье сомнительный приём показанный широкой аудитории, часть которой свято верит в непогрешимость источника. Куча не очень опытных разработчиков думает, что если на хабре написанно — так обязательно нужно делать, чувак с хабра же так делает. Встречался с этим не раз.


                                  Что еще раз говорит о вашем специфическом восприятии текста. Не нужно было тестировать поведение сервера. Ну вот вообще. Задача была в том, чтобы протестировать клиента.

                                  Вы плохо следите за контекстом дискуссии. Я писал про тестирование вашей разработки. Это действительно интересный и полезный материал, гораздо более интересный, чем притянутое за уши приложение(гвозди, молотки, вот это всё).


                                  Если вы знаете, как такой специализированный сервер собрать меньшими количествами усилий на tc-netem и Python, то напишите свою статью. Наверняка многие разработчики узнают что-то новое.

                                  Согласен, критиковать проще чем создавать контент. Ну я как могу вношу посильный вклад.

                                    0

                                    В основе рассказа лежит опыт создания тестового сервера для тестовых целей. Соответственно, откуда там могут взяться суровые требования к точности объема выдаваемого потока мне лично не ясно.


                                    Проверить работу этого тестового сервера проще простого — натравите на него curl, например, вот так:


                                    curl http://localhost:8080/512K > /dev/null

                                    и посмотрите какие показатели текущей и средней скорости выдает curl. Можете натравить хоть 10, хоть 100 curl-ов одновременно.


                                    Соответственно, ваши комментарии с моей колокольни воспринимаются вот так: "Чувак, я не дал себе труда разобраться в том, что ты сделал, но ты явно пиаришь свою поделку тогда как я бы все сделал через tc-netem". В общем, имею мнение… и далее по тексту.


                                    Ну, OK. Вполне себе позиция. Только обсуждать ее мне вообще не интересно. Ну вот вообще.


                                    Показали бы как тот же результат получить без написания своего специализированного HTTP-сервера., было бы что пообсуждать и читатели этой дискуссии могли бы что-то полезное для себя вынести. Но вы не даете себе труда сделать это, вам проще задвигать про молотки и гвозди.

                                      0
                                      curl http://localhost:8080/512K > /dev/null

                                      Вот это, уже аргумент, я почти доволен.


                                      опыт создания тестового сервера для тестовых целей.

                                      Тестировать непротестированным — это наше всё.


                                      "Чувак, я не дал себе труда разобраться в том, что ты сделал, но ты явно пиаришь свою поделку тогда как я бы все сделал через tc-netem"

                                      Скорее — чувак, тестировать скорость загрузки по http с помощью http сервера это слегка не точно… Хотя тут конечно всё сильно зависит от требуемой точности.


                                      Вполне себе позиция. Только обсуждать ее мне вообще не интересно. Ну вот вообще.

                                      Тем не менее, 90% обсуждения вокруг этого опуса произошло в выше приведённой ветке;) Ну по крайней мере пока не случился понедельник.


                                      Показали бы как тот же результат получить без написания своего специализированного HTTP-сервера

                                      Дык. Добавляем адрес к петле, шейпим траффик на заданный лимит по скорости на этом адресе, запускаем любой web сервер который умеет выдавать файл произвольного размера в директории /dev/ или в директории куда кинута символическая ссылка на /dev/zero. Вроде я ж с этого и начал?

                                        0
                                        Тестировать непротестированным — это наше всё.

                                        Я же говорил, что вы увидели в тексте статьи что-то свое. Вот вы очередное тому подтверждение и привели.


                                        Хотя тут конечно всё сильно зависит от требуемой точности.

                                        Зависит. Но вы не дали себе труд ознакомиться со статьей, поэтому спорите со своим восприятием, а не с тем, что я описывал.


                                        Тем не менее, 90% обсуждения вокруг этого опуса произошло в выше приведённой ветке;)

                                        Я был бы доволен, если бы этого пустопорожнего переливания не было совсем. Но ваше желание донести до меня свое ИМХО слишком велико.


                                        запускаем любой web сервер который умеет выдавать файл произвольного размера в директории /dev/

                                        Ну так проделайте этот шаг. Запустите этот любой сервер, дайте ему какой-нибудь файл на 10GiB или больше, расскажите, как он будет себя вести, когда он будет отдавать ответ с максимально возможной скоростью, а его будут резать до 20KiB/sec. Заодно расскажите как вы сделаете так, чтобы этот сервер мог на разные параллельные запросы отдавать ответы с разной скоростью.


                                        Ну и отдельно распишите (раз уж вас этот момент так волнует), как бы вы тестировали созданную вами конструкцию. Ведь этот набор команд так же может содержать опечатки или даже неверные команды, если у человека нет опыта работы с tc-netem.

                                0

                                Подумал ещё, и понял откуда всё же растёт моё недовольство вашим подходом.


                                Если у вас в руках молоток, то всё вокруг начинает казаться гвоздём. ©


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

          0
          очередное файловое хранилище с платной подпиской?)
            0

            А вы точно статью прочитали?

              0
              Да, я просто не могу понять, для чего нужно ограничивать скорость.
                0

                Условно, если у вас программа читает данные из 100500 источников и каждый источник имеет свой "вес", то может потребовать каждому источнику выдать канал своей "толщины".

                  0
                  Спасибо! Я наверное просто не сталкивался с задачами, которые можно было бы разрулить таким способом…
                    –1

                    Интересно как tarantool c этим справляется

                      0

                      А каким боком тут tarantool?

              0
              Мне потребовался HTTP-сервер, который бы отдавал трафик с какой-то заданной скоростью, например, 512KiB/sec. Тогда бы я мог видеть, действительно ли приложение выдерживает скорость 200KiB/sec или же оно срывается на более высокие скорости.

              Хм, а если ваше приложение "сорвется" до 400KBps, значит ли это, что тест пройден или провален?


              Я к тому, что если вам нужна имплементация тротлинга в клиенте, то зачем тут еще и сервер с тротлингом. КМК достаточно мониторинга соединений клиента, чтобы оценить качество реализации тротлинга в нем.

                0
                Хм, а если ваше приложение "сорвется" до 400KBps, значит ли это, что тест пройден или провален?

                Провален. Значит реализация ограничения скорости некорректная.


                КМК достаточно мониторинга соединений клиента, чтобы оценить качество реализации тротлинга в нем

                Соединений с чем? Чтобы мониторить соединение его нужно сперва создать. И вот к чему коннектиться при автономных тестах?

              0
              И вот к чему коннектиться при автономных тестах?

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

                0
                Да к чему угодно, что способно выдать больше, чем вам нужно в тесте.

                Ну и к чему это?


                Нужен мониторинг в том или ином виде.

                Мониторинг скорости клиента как раз таки есть.

                  0
                  Ну и к чему это?

                  Мне затруднительно ответить на этот вопрос, не зная вашего тестового окружения.
                  Но, очевидно, можно было бы использовать тот же сервер, в который вы добавили тротлинг, который можно было бы не добавлять.


                  Мониторинг скорости клиента как раз таки есть.

                  Прекрасно, этим можно было б и ограничиться, не трогая сервер.

                    0

                    Проблема была в том, что мне эта программа попала на доработку без каких-либо тестов и, уж тем более, тестовых окружений. Поэтому либо нужно было использовать какие-то сайты в Интернете, либо поднимать какой-нибудь существующий сервер и учить его отдавать "бесконечный" трафик, либо делать что-то свое. Я сделал что-то свое, благо много времени это не заняло.


                    можно было бы использовать тот же сервер, в который вы добавили тротлинг

                    Во-первых, такого сервера не было вообще.


                    Во-вторых, в сервере задание скорости не менее полезно. Например, в приложении делается три канала: на 128, 256 и 512 KiB/sec. И они подключаются к разным URL на тестовом сервере: скажем на 80, 256 и 1024KiB/sec. По показателям затем должно выходить 80 на первом канале, 256 на втором и 512 на третьем. И все это можно проверить на одном-единственном тестовом HTTP-сервере. Собственно, изрядная часть кода в описанном примере именно с этим и связана.

                      0

                      min(128,80), min(256,256), min(512,1024) разумеется, дадут 80, 256, 512.
                      Только вот 2 последних случая никакого отношения к тротлингу на клиенте иметь не будут.


                      Я не против того, что вы сделали и описали. Самому приходилось добавлять тротлинг для передачи и получения данных. Вполне понятная и востребованная "фича".


                      Я "придираюсь" исключительно к формулировке исходной задачи, что мол, для тестирования реализации тротлинга в клиенте вам понадобился сервер с аналогичной функциональностью. Для меня же очевидно, что для тестов, т.е. min(client_speed, server_speed) достаточно, чтобы client_speed < server_speed.
                      Условно, если server_speed==inf, то всё Ok ;) Отсюда и вывод, что то, что вы сделали может быть и полезно где-то ещё, но в рамках поставленной задачи бесполезно.

                        0
                        min(128,80), min(256,256), min(512,1024) разумеется, дадут 80, 256, 512.

                        Почему это разумеется? Если реализация тротлинга на клиенте некорректна, то может быть вовсе не 80, 256 и 512, а 60, 128 и 1024, к примеру.


                        Когда вместо inf есть четкое понимание ширины отдачи у сервера, разбираться с клиентом гораздо проще.


                        И это мы еще не рассматриваем случаи, когда тротлинг на клиенте может на длительном интервале времени давать 80KiB/sec, но это будет за счет того, что 10 секунд он будет вообще ничего не читать, а потом за секунду выкачает 880KiB. Что тоже как бы не есть гуд.

                0
                Почему это разумеется?

                Потому, что единственным необходимым условием для корректной реализации является условие client_throttle_speed < server_bandwidth.


                И это мы еще не рассматриваем случаи, когда тротлинг на клиенте может на длительном интервале времени давать 80KiB/sec, но это будет за счет того, что 10 секунд он будет вообще ничего не читать, а потом за секунду выкачает 880KiB.

                Зачем рассматривать заведомо абсурдную постановку?
                Bandwidth принято мерять в *bps, вот и корректный тротлинг должен давать плавный график с разрешением близким к секунде, а не измеряться за "неделю".

                  0
                  Потому, что единственным необходимым условием для корректной реализации является условие client_throttle_speed < server_bandwidth.

                  По вашему ситуация, когда client_bandwidth > server_bandwidth вообще никак не должна тестироваться? ;)


                  Зачем рассматривать заведомо абсурдную постановку?

                  Речь вообще-то не про постановку, а про реализацию. Если клиент держит 100500 подключений и по ошибке на какое-то время "забывает" про часть своих подключений, то еще и не такие фокусы могут происходить.

                  0
                  когда client_bandwidth > server_bandwidth вообще никак не должна тестироваться? ;)

                  Вы, наверное, хотели написать clientthrottlespeed > server_bandwidth? :)
                  В любом случае, что вы ожидаете увидеть при этом?


                  то еще и не такие фокусы могут происходить.

                  Мне кажется, что вы меня уже поняли. Я говорил исключительно об исходной постановке задачи, т.е. о юнит-тестировании реализации тротлинга в каком-то вашем HTTP-клиенте…
                  Предлагаю экзотические фокусы, нагрузочное тестирование и т.д. оставить за рамками обсуждения. Да и обсуждение, КМК, уже выродилось. Предлагаю на этом и закончить.

                    0
                    В любом случае, что вы ожидаете увидеть при этом?

                    Я ожидаю увидеть, что клиент качает с той скоростью, с которой сервер отдает. И чтобы не начал почему-то резать трафик.


                    т.е. о юнит-тестировании

                    Вообще-то речь про юнит-тестирование и не шла.

                      0
                      что клиент качает с той скоростью, с которой сервер отдает. И чтобы не начал почему-то резать трафик.

                      Ну так задайте throttle_speed, допустим, 200Gbps и сравнивайте со скоростью сервера :)

                        0

                        Давайте на пальцах: допустим, у клиента на канал задано ограничение в 128KiB/sec, а сервер может отдавать всего 80KiB/sec. Если реализация ограничителя у клиента правильная, то клиент будет качать со скоростью 80KiB/sec. Если не правильная, то возможны разные варианты, например, 60KiB/sec или 12.8KiB/sec.


                        И это поведение так же должно быть протестировано. Если вы думаете, что не должно, то, пожалуйста, не морочьте мне больше голову.

                          0
                          И это поведение так же должно быть протестировано.

                          Должно. См. кэйс в предыдущем сообщении.


                          не морочьте мне больше голову.

                          И вам того же. :)

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

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