December 18, 2019

Rust - опять

Есть у меня pet-проект, который тянется уже больше года и, постепенно, подходит к финальному аккорду.

В какой-то момент было решено изменить способ хранения данных у клиента на простые файлы => понадобилась некая скачивался данных из базы для этого, всё работало почти отлично - данные качались чанк за чанком, прогрес-бар показывал сколько осталось. Кроме одного - данные не сжаты и их много - отдавать потоком - остаться без Content-Length - пользователь будет в неведении сколько осталось. Сжимать сразу много - пользователю очень долго ждать этого Content-Length.

Было решено просто - собирать по 1000, сжимать и отдавать - всё опять же работало, но что-то было странно - иногда некоторые чанки приходили битыми. Посмотрев на пакеты стало понятно - что пакеты нормальные, а вот способ их обработки у hyper такой - если chunk не влезает в буффер (8kb) - то выдавать chunk с тем что есть, а остальное - как будто это другой чанк. Насколько это корректное поведение не берусь судить, возможно и верное. Хотел было переехать на web-sockets, и тут, actix-web, казавшийся нормальным в качестве http показал, что структура для ws должна делаться так: Client<SinkWrite<Message, SplitSink<Framed<T, Codec>, Message>>> - просто же, и удобно - все кишки видны, а то мало ли нужно будет из client.0.0.0 извлечь codec.

Дальше надо описать актор, потом Handler, потом ещё StreamHandler, в который надо передать mut-контекст этого клиента, после чего из него дёргать руками методы типа ctx.0.write или обёртки. Но это не заработает со фьючовыми стримами, потому как при создании этого клиента - надо вызвать мутабельный add_stream на нём, в котором связать Stream и контекст - видимо иначе он не будет знать что надо дёргать этот handler, при этом, даже если совершить все эти действия - это только для обработки выходного потока из stream - как писать в него из другого стрима без своего impl Stream поверх - останется загадкой.

В общем, от такого юзать ws как-то расхотелось. Знающие люди говорили, что надо просто накрутить length_delimited кодек на чанки http и у меня будут нормальные фреймы данных - попробую в эту сторону. Кодеки работают через ASyncRead, а hyper/actix выдают Stream<Item=Vec<u8>> - хотя в общем-то понятно, что результат их работы в этом случае одинаков, и только в futures 0.3 сделали метод into_async_read() который позволяет одну абстракцию преобразовывать в другую => можно просто писать в codec - ура!, но нет, не ура, tokio 0.2 почему-то имеет свои ASyncRead, которые почему-то не совместимы с futures 0.3, а другие мне не подойдут. Есть некий клей - compat, но, бывалые люди, сказали что всё же _надёжнее разобрать байты руками_, чем пробраться через страницу какой-нибудь ошибки, которую покажет что compat(), без compat() ошибки и так по пол-страницы и не сильно проще читать.

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