RSS Telegram YouTube Apple Яндекс Spotify Google Amazon Почта

8. Не WebSocket'ом единым

15.07.2023

Скачать

К списку выпусков

☄️ Бесконечные респонсы, фул-дуплекс, тройное лейтенси однонаправленных и двунаправленных коммуникаций

Как получать события с сервера максимально быстро? Что можно использовать кроме WebSocket протокола? Темная лошадка SSE - это идеальное решение? HTTP2 упразднит WebSocket'ы?

Автотранскрипция

Это восьмой выпуск подкаста о разработке бэкэнд-приложений. Меня зовут Артём, также в этом подкасте принимал участие Боря. В совсем свежем проекте нам понадобилось на клиенте получать события с сервера и получать их как можно быстрее. Для решения этой задачи существует несколько подходов, но сразу на ум приходят решения с использованием WebSocket протокола. Есть и более простые решения. Мы поговорили про шот и лонгполлинг, тёмную лошадку, SSE, сервер сент-эвент, WebSockets и, в кавычках, убийцу WebSockets, HTTP2. Приятного прослушивания.

У меня возникла потребность получать события с сервера. То есть, представляем себе приложение, в нашем случае на Go. Оно подключается к контроллеру по TCP, получает какие-то события от этого контроллера и сигнализирует о полученных событиях на страничке браузера. Браузер используется как инструмент для отображения, для рендеринга. И возникает вопрос, как лучше всего получать события с сервера.

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

Ну, наверное, в 95% случаях мне бы сказали использовать WebSockets. Это, скорее всего, так бы мне сказали разработчики, которые, так скажем, не суперопытные. Опытные разработчики спросили бы меня о частотности, наверное, получения этих запросов и насколько мне оперативно нужно их отрендерить на страничке. То есть, вдруг такая ситуация, что я могу, знаешь, задержка между тем, как отобразится это событие на сервере, как оно появится на сервере и как оно отобразится, она может составлять, например, минуту. И это для нашей задачи хорошо. Но если нам нужно моментально это сделать, то я думаю, что мне чаще всего посоветуют WebSockets.

А если есть лучшее решение, зачем использовать такое решение, которое будет с большей задержкой тебе передавать события с сервера? То есть, если у тебя есть такой выбор и ты всегда можешь взять что-то лучшее, зачем тебе предпочитать какой-то худший тогда вариант? Мне кажется, тебе большинство бы людей, если так вот на вскидку надо быстро ответить, сказали бы про WebSockets, потому что WebSockets очень раскрученная тема. То вот про WebSockets знают, а про то, что мы в этом подкасте о чем будем рассказывать, наверное, мало кто вообще слышал про такое. Это очень редко об этом говорят. Или, может быть, я не прав? Что думаешь?

Ты прав. Существует же ряд целых стратегий. И, наверное, мы начнем с самых каких-то простых стратегий. Как можно получать события на сервере? На сервере что-то меняется, мы хотим что-то узнать на клиенте. Действительно ли там что-то поменялось? И сделать это как можно быстрее. Задача именно в этом состоит. Как можно быстрее получить события, произошедшие на сервере? Каким тут прибегать нам инструментом?

Мы сейчас говорим именно про общение между браузером и сервером. Не обязательно браузером. Мы говорим, скорее всего, о HTTP протоколе. Потому что, если, например, общение будет между сервером и сервером, например, я разработчик какого-то API, и у меня какой-то другой сервер пользуется этим API, то, наверное, тогда использовались бы WebHooks, скорее всего. Я бы попросил тогда своего клиента предоставить мне, либо зарегистрировать через какой-то метод, его URL, на который я буду ему, как только у меня появляется ивент, я ему буду тут же отправлять. Но если говорим про браузер, то, наверное, самый такой, самый простой, наверное, вот если по простоте их ранжировать, то самый простой это то, что называется Short Polling.

Кстати, я тебе скажу, что большинство не знает, что он так называется. Чаще всего говорят AJAX-запрос. А мы просто будем делать AJAX-запросы. Ну, скажем там, с периодичностью в 5 секунд. На уровне девайсов, вот, кажется, он так и называется Polling. Потому что вот, когда вот внутри компьютера там происходит опрос каких-то узлов, то есть тебе надо узнать, например, там, ну, произошло ли какое-то событие. Обычно одна система опрашивает другую систему. Что-то вроде Поллинга, он так и называется. И стратегия суперпростая.

Окей, да, какая? С периодичностью n секунд мы делаем запрос на наш сервер. Получаем состояние. То есть возникло там какое-то событие или не возникло, каждый раз мы считываем.

И какие здесь главные недостатки. А ты когда-нибудь использовал такой метод?

Я использовал много раз. Это суперпростой способ.

А сколько у тебя вот это время ожидания обычно? Ну, вот, какой примерно порядок вообще?

У меня самый маленький период между запросами 1 секунда.

Да, ты уже получаешься на той грани, когда Short Polling, он уже дает о себе знать по нагрузке. То есть ты очень часто прям делаешь запросы. Вот когда задача Short Polling, ты реализуешь именно этот механизм. Скорее всего, на этом этапе ты не задумываешься вообще ни о каких нагрузках. То есть тебя полностью устраивает то, что я делаю простой запрос, получаю состояние, что-то там рендерю, например, в браузере. Но смотри, а иногда ты без Short Polling не обойдешься. Вот у меня, например, UPS. И у меня есть какие-то трек-номера. И я должен получить... Это курьерка в США. И, по-моему, она международная, в принципе, курьерка, да? Ну, это международная, да. Они много где работают. Мы регистрируем посылку. И у нас появляется трек-номер этой посылки. И что там UPS предлагает? Мне нужно получить события. События, что, где посылка, что с ней там происходит. И UPS по API, они рекомендуют в определенное время делать запросы по определенному пути. И вот раз в сутки я делаю запросы по списку этих трек-номеров. То есть у меня просто по-другому нет никакой возможности. Без такого Short Polling. Правда, у меня вот это время ожидания сутки составляет. Не так, как у тебя секунда. А сутки. То есть я раз в сутки делаю этот запрос. И получаю всю информацию за... Новую информацию, новые события за прошедшие сутки. У тебя несколько другая ситуация. И как раз речь идет о максимально быстром получении событий, возникших на сервере. У тебя, получается, сутки. Я бы вообще убрал все приставки, просто назвал бы это поллингом. То есть тебе надо просто тянуть данные раз в сто лет. Чаще всего, когда Short Polling, подразумевает, что ты делаешь очень частые запросы к серверу. То есть в этом и прикол. Что я хочу получить как можно быстрее события, так что на сервере я делаю очень часто запросы к серверу. Ну окей, давай тогда всегда делать. Будем рекомендовать тогда. Вместо WebSockets используйте всегда Short Polling. Это же классное решение. Реальное решение. Не нужно никакой библиотеки, WebSocket. Зачем вам разбираться с этим новым протоколом? Вы же знаете HTTP, используете HTTP. Ну так вот, Short Polling. Чем он плох? Первое. Во-первых, у тебя всегда есть заголовки. То есть у тебя всегда есть overhead. То есть ты посылаешь запрос, авторизационные какие-нибудь куки или токены. И получаешь ответ, опять эти заголовки. То есть у тебя помимо полезной нагрузки передается избыточные данные, которые скорее всего одинаковые. Ну и ладно, подумаешь, избыточные данные. Что здесь такого? Вполне. Но я же не говорю, что это плохое решение. Но есть у него определенные недостатки. Такое неэффективное получается. Не такое эффективное, как может быть другие решения. Как другие решения. Второе. У тебя все-таки есть задержка. То есть чаще, чем твой период обращения к бэкэнду, ты события не получишь. То есть у тебя секунда, а ты хочешь чаще. У тебя какие-то котировки акций. Или у тебя игра, например. Представьте, браузерная игра, а ты как бы раз в секунду получаешь события. Ну, возможно, это скажется негативно на твой experience игровой. Согласен, согласен. То есть, как я это вижу, бывает такая ситуация, когда у сервера нет вообще никаких событий. Особенность HTTP протокола в том, что это request-response протокол. То есть сервер по HTTP протоколу, он не может без того, чтобы клиент прислал реквест, он не может передать эти ивенты. То есть HTTP у него, у любых версий HTTP протокола, у них такая фундаментальная особенность. И в определении HTTP протокола говорится, что это request-response. То есть получается, что действительно ты должен прислать какой-то реквест на сервер. Для того, чтобы узнать, что на самом деле произошло. И представь, что на сервере события появляются редко. И ты будешь долбить сервер. Ты его долбишь, долбишь, и ничего не получаешь. То есть вся эта нагрузка на CPU, нагрузка на сеть, все это впустую. Поэтому да, он может быть неэффективный. И плюс действительно задержка. Между прочим, по задержке это интересный момент. Какая здесь задержка будет максимальная? То есть когда мы выбираем разные решения, мы смотрим на задержку. То есть мы действительно, как ты сказал, хотим выбрать такое решение, у которого задержка будет в пределах допустимого. Поэтому мы должны смотреть не на среднюю задержку, не на среднюю задержку. А максимальную? Да, максимальная задержка. То есть выйдя за предел, который это становится уже супер неэффективным. Максимальная задержка при таком решении. То есть мы решили взять short polling. Мы решили, что мы будем спать каждую секунду и через каждую секунду делать запрос. Мы должны посмотреть худший сценарий. А в самом худшем сценарии, когда у клиента появится ивент. Вот ивент в какой-то момент зародился, появилось какое-то событие. И через сколько, максимально через сколько клиент при таком решении узнает о том, что случилось, какое-то событие. То есть какая максимальная будет задержка? Вот этот вопрос многие немножко не точно рассчитывают. И многие тебе действительно скажут, что это секунда. На самом деле самый худший сценарий состоит в следующем. Представь, что ты сервер. У тебя появился ивент какой-то, событие. Ты его записываешь в connection, в соединение. Ты записываешь в connection. Но перед тем, как первый байт по этому connection отправится в сторону клиента, у тебя появляется новое событие. Вот это самый худший сценарий. То есть ты записываешь какое-то событие, и тут появляется новое, которое ты проигнорируешь. И вот у тебя получается не просто будет задержка, максимальная равна времени твоего ожидания. Ну, клиент не просто тому количеству секунд, сколько клиент спит, а еще три latency. То есть первая latency, когда ты старый ивент, тот, который уже немножко устарел, передаешь. Потом клиент спит. Потом клиент делает реквест. Потом сервер получает реквест, и он смотрит, у меня в очереди вот тот самый ивент, который я должен был тебе давно передать. И плюс еще время передачи данных от сервера клиенту. И только после этого клиент получает наконец-то этот ивент долгожданный. То есть на самом деле будет не секунда. А если у тебя latency, например, состоит 100 миллисекунд, то будет 1,3 секунды. Ну, не знаю, насколько это интересно. Нет-нет, тут все правильно. То есть нужно учитывать. То есть если у тебя высокочастотные события возникают, то там, скорее всего, тебе придется задумываться о latency. То есть round trip time нужно тоже учитывать. И в этих полинг-механизмах, именно реквест-респонс по HTTP протоколу, это всегда 3 умножить на latency. Всегда 3 умножить. Но в short-полинге надо еще добавить время, сколько клиент спит. Надеюсь, я понятно это объяснил. Short-полингом тут более-менее понятно. А вот другая стратегия, когда мы делаем long-полинг. Что это такое? Long-полинг – это мы делаем запрос к серверу и висим. То есть мы ждем, событие там появилось или не появилось. То есть мы ждем, что-нибудь появится на той стороне трубы, на той стороне коннекшена. Как только появляется, мы это считываем себе в клиент, обрываем коннекшен и переподключаемся моментально после обработки этого события. Я бы так это еще тоже объяснил, может быть, более простыми словами. То есть вы, как клиент, делаете запрос на сервер. Сервер получает запрос, но предположим, что у сервера нет никаких ивентов, нет событий. Нечего вернуть клиенту. В случае short-полинга мы бы вернули какой-то респонс, сказали бы, ничего нет. Но в случае long-полинга мы не хотим это делать. Мы не хотим, чтобы нас клиент постоянно задалбливал своими запросами. Поэтому мы как бы такие, типа, давай ждать. То есть сервер ждет и клиент ждет. И они вместе сидят как бы ждут, когда что-нибудь появится. И вот представь, что что-то появляется, и тогда сервер возвращает. И клиент получает это событие и тут же делает заново еще один запрос. Его запросы могут просто долго длиться. Поэтому это называется long-полинг. Он долго может ждать, клиент может долго ждать. Долго висит. Да. Ну хорошо. С long-полингом тоже более-менее понятно. Я признаюсь, что никогда еще не делал long-полинг. То есть я чаще всего делал стриминг. Между прочим, long-полинг, ты, может быть, его явно не использовал, но он вообще является дефолтным таким, дефолтом у веб-сокетов. То есть если веб-сокеты, обычно ты используешь библиотеку для того, чтобы использовать веб-сокеты. И если у тебя вдруг не получается установить connection, использовать эти веб-сокеты, то тогда эта библиотека попробует использовать long-полинг. Интересная, знаешь, какая штуковина. Кто инициирует fallback в long-полинг? То есть ты делаешь запрос с апгрейдом на удаленную сторону, и она, наверное, как-то тебе отвечает, что она, вот этот промежуточный ответ, промежуточный респонс. Ну, например. Он тебе что-то отвечает, наверное, такое, что он не готов. Да, ты отправляешь какой-нибудь, например, get-запрос, какой-то путь, слэш, http 1.1, потому что только у 1.1 есть upgrade-хеддер. В connection-хеддере ты пишешь слово upgrade. В upgrade-хеддере ты пишешь веб-сокеты. И, предположим, тебе отвечает, что не могу переключиться. То есть ты ждешь 101 статус-код switching protocols, а тебе, допустим, сервер возвращает что-то другое. Блин, ну что делать? Давайте тогда, ну можно тогда long-полинг использовать. Почему бы и нет? Это же намного лучше, чем short-полинг. Там, скорее, интересно, как эта механика происходит. Я вот, честно говоря, в глаза так не смотрел. Ну, я тоже не знаю. Не дебажил, не смотрел. Очень интересно было бы посмотреть, как этот fallback в реальности происходит. То есть, видимо, и сервер должен поддерживать long-полинг. Очевидно, да. Быть имплементацией. То, что там ивент какой-нибудь, да, то есть у него какой-нибудь channel, например, если мы говорим о Go, и висит подключенный connection, и если как только что-нибудь по ченнелу нам пришло, мы туда делаем write в response. Я не знаю, почему. То есть тут надо смотреть, почему не получается создавать. Вот это интересный вопрос. Может, скорее всего, наверное, самая частая причина — это какой-нибудь proxy, за которым ты сидишь, и у тебя что-то из-за этого, знаешь, не получается. Либо какое-то у тебя в браузере там расширение установлено, которое не позволяет через веб-сокеты, и твое приложение в браузере, может быть, засекает такое, знаешь, что сокет уже долго не создается, что за дела. Давай я лучше по-другому попробую. Наверное, скорее всего, это так происходит. Это, кстати, интересно, то, что происходит fallback в long-polling. И чуть позже же мы про это поговорим. Тут насколько вообще возможно использовать long-polling для такой, знаешь, двунаправленной коммуникации, максимально эффективной. То есть все равно же ты должен реквест отправить, и что-то висеть, ожидать ответа. А в веб-сокетах есть возможность у тебя одновременной коммуникации как на чтение, так и на запись с обеих сторон. И вот насколько это сопоставимо, почему они fallback. Тут это интересно. Это сопоставимо, очень сопоставимо. Многие, когда сравнивают веб-сокеты и HTTP, они говорят, что это можно на любом конференции услышать. Если в начале конференции выступающий хочет задаться целью кратко рассказать, что такое веб-сокеты, например, для... Подожди, Боря, подожди. Мы сейчас прыгнем в веб-сокеты, мы пропустим... Извини, что я тебя прерываю вот здесь. Мы просто пропустим очень важную штуку, которую вот сейчас прямо с long-polling вместе здорово обсудить. Это стриминг. То есть ты можешь чуть-чуть улучшить свой long-polling и сделать, предоставить клиенту стриминг. Окей. Ты когда-нибудь делал стриминг через HTTP? Я не делал, скорее всего. Я не могу вспомнить. То есть я знаю, как это можно сделать, но я, кажется, этого не делал. Кажется, не делал. Я не помню сейчас. Вот ты мне задал вопросы, я, честно говоря, не могу вспомнить. Но я знаю, что стриминг, смысл его состоит в том, что клиент делает запрос на сервер, и сервер отвечает ему бесконечным респонсом. Бесконечным. То есть он постоянно что-то пишет, пишет, пишет, а клиент постоянно что-то читает, читает, читает. Но это не обязательно... Я вот дополню твое пояснение. У тебя могут возникать события произвольным образом, и когда у тебя возникают события, тогда ты и пишешь их. Да, естественно. То есть если, например, это Go, то имплементацию написать вот этого HTTP стриминга прям вообще невероятно просто. То есть у тебя, у хендлера, есть просто какой-то ченнел, с которого ты читаешь, как только там появляются какие-то данные, ты считываешь из этого ченнела, ты их пишешь и обязательно делаешь флаш. Ну, чтобы она максимально быстрее поставилась, потому что все будет... Флаш нужно делать обязательно. Это очень важно. В голэнге, если мы говорим про имплементацию в голэнге, то когда у тебя какие-то данные появились, ты должен сделать флаш, то есть минуя буфер, чтобы эти данные, ну или через буфер, ну в общем, чтобы они не накапливались в буфере. Чтобы эти данные попали именно в connection, чтобы тут же эти данные были записаны в connection. Это вообще отдельная интересная тема. Я маленькую просто звездочку здесь поставлю, ссылку на другую тему. То, что понимание того, что когда ты в Go, в частности, делаешь write в response, это не то же самое, что клиент получил твой ответ. Вот это понимание, оно настолько важное, что в принципе должно влиять на проектирование API. То есть если ты помнишь про то, что я, например, написал в response... Ну, сервер. А клиент не сервер. Да, ты пишешь сервер, да. И клиент твоего сервера, это не значит, что он прочитал этот response. Соответственно, тебе надо API вообще проектировать таким образом, чтобы клиент мог повторить запрос или получить данные. И я сталкивался с такими имплементациями, такими серверами, которые не учитывали тот факт, что несмотря на то, что они что-то записали и залогировали, это не то же самое, что я, как клиент, получил какие-то полезные данные с этого сервера. И как тебе понравилось с такими API работать? Нет. Причем чаще всего... Ну, у меня таких два примера есть. Два вот в продакшене, прям два примера API очень сложными. Каждый раз, когда я сталкиваюсь с проблемой, мне приходится вручную делать запрос. Там, ребята, а вот у меня таких-то данных нет. Они тебе говорят, да мы же тебе вот response отправили. Да, и залогировано все. Ты к нам сделал запрос, мы тебе ответили. Что ты от нас еще хочешь? Вот все, мы тебе ответили. Зачем ты к нам обращаешься? Да, а самое печальное, что нет возможности, например, GET сделать. То есть там, скажем, POST есть. Я прошу... Потому что эти ребята считают, что когда они записали тебе response, скорее всего, ты его получил. Возможно. Они правда... Я точно не знаю, что они считают, но это очень плохая стратегия вообще написания. Считать, что если я записал response, то его клиент получил. Ну ладно, здесь звездочку я оставлю, закрываю это небольшим. А между прочим, давай я по этой теме еще твою звездочку немножко отодвину и скажу еще такое тоже свое интересное наблюдение. Когда я использовал вебхуки, то провайдеры этих вебхуков, они говорят, что иногда мы будем вам события отправлять возможно несколько раз. Потому что когда мы вам отправляем события, вы, конечно же, пишите, типа 200-ый, например, статус-код. Какой-то response нам отправляете. Но этот response мы не можем получить. Иногда он до нас не доходит. Поэтому будьте готовы к тому, что это событие мы вам несколько раз отправим. И понятно почему. То есть это действительно такая особенность HTTP. Нет никаких гарантий у того, кто пишет response, что этот response на самом деле дошел. Могло по ходу много что случиться и пакеты просто могли не дойти до получателя. Можно написать имплементацию. Все-таки мы работаем через TCP. Но, скажем так, это нужно совершить extra mile. То есть тебе нужно совершить серьезные усилия, чтобы гарантировать доставку, что ты убежден, что клиент, ему доставлено было, что ты получил response. И тогда ты как-то отмечаешь у себя, что все хорошо. Ну то есть чаще всего, особенно при больших нагрузках, как-то пушнули куда-то там, оно как-то долетело, не долетело. Это уже проблема удаленной стороны. Я часто с таким, например, в том же Shopify. Там как раз такой у них подход. В Telegram что-то вроде такого. А нет, в Telegram, по-моему, они делают повторы. То есть если они не могут отправить тебе нотификацию запушить тебе, тогда они делают ее повторы, насколько я помню. Причем у них там такая интересная система повторов, что они достаточно долго их делают с увеличивающимся интервалом. И только через какое-то количество времени, через два дня или что-то вроде этого, они дропают. Ну вот я по webhook'ам, кажется, смотрел у Shopify, как раз ты мне напомнил. И кажется, у Shopify было такое написано, что они тебе отправляют какое-то событие, ты им отправляешь окей, но они иногда не получают твой окей, твой респонс. И они не могут пометить, что это событие успешно было доставлено. И поэтому они могут повторять. И об этом в API как раз обычно и пишут провайдеры таких webhook'ов. Хорошо, а вот по стримингу у меня главное, почему мне стриминг в принципе симпатичен, это то, что тебе ничего не нужно. Имплементация в принципе очень простая. Стриминг сделать очень просто. Имеется в виду сервер написать невероятно просто. Мне кажется, что лонгполлинг лучше. Мне кажется, что лонгполлинг лучше. Угу, поясни. У шортполлинга понятно, мы уже сказали, что там использование ресурсов не очень... Плюс тебе нужно накапливать какую-то очередь. То есть тебе нужно хранить состояние, что доставлено, что не доставлено в случае шортполлинга. Например, если мы говорим какой-то очередь событий. Ну да, а в случае лонгполлинга какие недостатки главные? Если ты долго держишь connection, то обычно существуют такие таймауты разные. И кто-то среди разнообразных прокси, обычно на стороне клиента, может увидеть, что connection очень долго висит. Например, он очень слабо используется. И просто могут перекрыть этот connection. И в RFC, например, есть рекомендация, кажется, около 30 секунд. То есть они прям рекомендуют больше 30 секунд не держать. То есть максимум через 30 секунд, если не появилось никакого ивента, то лучше отвечать, серверу лучше отвечать. И провоцировать клиент, например, на новый запрос. При этом вообще такая интересная штука. Дело в том, что в HTTP 1.1 клиент может переиспользовать этот connection. Ну, собственно, там и главная особенность этого протокола. Да, там persistent connections. Это главная особенность протокола HTTP 1.1. То есть на самом деле он может переиспользовать connection. Но при этом все равно на этом connection не будет какая-то повышенная активность, потому что пойдет туда реквест, хедеры, какой-то запрос. Хорошо. А следующая? Вот следующая стратегия, следующий вариант реализации получения событий с сервера. То есть мы прошли short polling, long polling. У стриминга преимущество самое большое то, что там нету практически, ну там нету latency. То есть ты постоянно стримишь, как только появилось событие, ты тут же его скидываешь. Тебе не надо ждать реквеста. Но у него самый серьезный недостаток это то, как он имплементирован в HTTP. А он имплементирован с использованием transfer encoding хедера. Ну, я, по крайней мере, про такое читал. И transfer encoding хедер – это hop-by-hop хедер. То есть он не предназначен для получателя твоего HTTP сообщения. Его используют все возможные прокси. То есть представь, что между сервером и клиентом по ходу движения респонса сидит какой-нибудь прокси. Например, он близко к клиенту сидит. И этот прокси, он вне твоего контроля, как разработчика. И этот прокси получает какое-то HTTP сообщение. Он видит, ага, это HTTP прокси. Поэтому он понимает HTTP протокол. И он видит, окей, transfer encoding хедер. И он думает... Ну, то есть ты, как сервер, ты передаешь респонс, разрезанный на кусочки. Такой chunked-респонс. И он думает, окей, буду накапливать эти чанки до тех пор, пока у меня не будет весь твой респонс. И только после этого, после того, как я все чанки получу, я этот респонс передам наконец-то дальше. Например, клиенту, которому этот респонс предназначается. Ну, я сомневаюсь, что там прям будет он, наверное, все накапливать. Такая семантика у transfer encoding. То есть если есть HTTP прокси, он скорее всего будет накапливать. Потому что в этом его как бы смысл. Если посмотреть определение, transfer encoding — это hop-by-hop-header. Он предназначен ближайшему HTTP прокси. Скорее всего, он будет накапливать совсем чуть-чуть. То есть у него там какой-то буфер. Если он тупой, то он может накапливать вообще до бесконечности, пока ты не устанешь этот стрим передавать. Это такой сомнительный прокси. Ну, может такое быть. Теоретически, наверное, может, да. Это действительно так. То есть у тебя получается, ты лишаешься определенной доли контроля. То есть если ты понимаешь, что между тобой и клиентом есть инфраструктура, которую ты не контролируешь, то нужно дополнительный какой-то ресерч провести, насколько там все может быть печально. Ну, хорошо, мы с тобой поговорили про эти варианты. А есть очень классный вариант, который очень редко упоминается. Но вместе с тем он отлично реализован. Мы вот совсем недавно его запилили. Да-да-да. И вот продакшн заедет в понедельник, на следующей неделе. Вот я хочу, например, не использовать веб-сокеты, и мне нужно какое-то простое решение. Как же мне... Причем стандартное. Практическое, что есть во всех браузерах. Такое суперрешение, но не веб-сокеты. Есть что-то такое во вселенной существует. И еще я не хочу, а еще у меня такое условие. Я в твоем Go-проекте не хочу тянуть никаких дополнительных зависимостей. Ну, ты минималист, поэтому, да. Идеальное решение, короче, оно есть. Для того, чтобы быстро получать события с сервера, это практически тот же самый HTTP-стримминг. Чаще всего его можно узнать по названию SSE аббревиатуре. Это server-sent-event. Однонаправленная коммуникация для того, чтобы максимально быстро получать события с сервера. Да, это работает следующим образом. То есть на стороне клиента в браузере, мы сейчас обсуждаем именно взаимодействие сервера с браузером. Ты на стороне браузера в JavaScript, в любом современном браузере есть EventSource объект. Ты создаешь EventSource, ты скидываешь в качестве параметра, передаешь этому EventSource URL. И в момент создания браузер делает запрос на этот URL. И Go-сервер, написанный на Go-сервер, все, что должен сделать, все, что ожидает этот EventSource объект, он ожидает от тебя респонс, у которого обязательно должен быть заголовок content-type, у которого рекомендуется использовать cache-control-no-cache, и респонс должен быть определенного формата. Ну, это ты уже в детали такие закапываешься, которые достаточно просто гугляться. Тут главное, что понимать, что это стандарт, что есть стандарт, есть документ, который определяет, что это за протокол, как это все работает. Server-sent-event. Что в браузерах, в которых есть поддержка HTML5, а HTML5 это 2008 год, все-то очень давно, все браузеры, которые поддерживают HTML5, они поддерживают вот этот EventSource. Да, в общем, это очень хорошая альтернатива. И на стороне сервера пишется имплементация на раз-два вообще. Ну вот я как раз описал, буквально мне потребовалось несколько секунд, чтобы сказать самые главные вещи. Ты опять же будешь флаш использовать и очень простой текстовый формат. То есть от тебя ничего не требуется, ты можешь обойтись без библиотеки, просто как обычный хендлер написать. И простота, конечно, поражает. И удивляет, насколько малоизвестен этот способ передачи ивентов. И если, например, полазить по всяким разным сайтам, сервисам, которые получают события с сервера, то очень редко ты можешь увидеть вот этот вариант, эту имплементацию, при том, что она невероятно простая. То есть всякие там провайдеры, хостеры, чтобы просто какую-нибудь лампочку подсвечивать или какие-то графики рисовать. Знаешь, заходишь, там у тебя сервера и дэшборд у какой-нибудь там... Ну не будем говорить у какого. Да, я заходил недавно, тоже удивился. То есть там для подсвечивания какой-то небольшой... Пимпочки. Пимпочки. Статус какой-нибудь. Там устанавливается WebSocket и постоянно там обмен происходит, сообщения. Причем там хардбиты какие-нибудь. Там были хардбиты. Я смотрю, там практически всегда хардбиты. То есть... Какой-нибудь котировки чего-нибудь. Ты смотришь, как делают эти котировки. Я часто видел WebSockets. Часто. То есть почему-то используют и такое решение. Хотя можно намного проще облегчить себе жизнь, не использовать сторонние зависимости. С сервера получать события через SSE. Server Sent Event. Это HTTP протокол, да. Это не какой-то отдельный протокол. Это именно HTTP протокол. И никаких не нужно библиотек везде. Наверное, во всех браузерах. И есть этот EventSource объект, поэтому... Ну да, это HTML5. Он входит в вот это API этого EventSource. В общем, это хорошая альтернатива, так на подумать. Мне кажется, очень хороший способ. В отличие, конечно, от WebSockets, вы в тот же самый connection не сможете передавать сообщения от клиента к серверу. То есть вы постоянно получаете от сервера к клиенту. Но если вам нужно что-то передать серверу, вы всегда можете сделать отдельный какой-то запрос. Поэтому это, мне не кажется, какой-то серьезной проблемой. Хорошо. Те варианты, которые мы сейчас обсудили, это рабочие варианты, но они идеально работают, если у тебя однонаправленная коммуникация. Если у тебя возникает потребность именно в двунаправленной коммуникации, то есть одновременно ты получаешь данные с сервера, в этот же момент ты хочешь ему что-то отправлять. То есть это всякие игровые приложения, какие-нибудь акции, еще что-нибудь ты хочешь покупать, продавать. Ты одновременно получаешь данные о котировках, одновременно ты должен сообщать события, передавать события на удаленный сервер по действиям пользователя. И вот, например, если мы делаем через short polling, то есть что это? Мы можем полить события, то есть получать котировки вот таким образом обновлять, а отдельными запросами, дополнительным connection, отдельным потоком, отдельным подключением мы сообщаем события пользователя. То есть вместо того, чтобы использовать TCP connection, мы как бы создаем дополнительное соединение. Одно соединение используется для подтягивания событий, а другое соединение для отправки событий. Ну и, естественно, это неэффективно. То есть если у тебя, например, там большая нагрузка, большое количество пользователей, то просто pool портов на сервере у тебя ограничен. Сколько там? 65 535 портов. Ну ты немножко оговорился. Я на всякий случай скажу, что ты имел в виду long polling, а не short polling. То есть при long polling, да, ты ждешь по TCP connection, ты ждешь, когда к тебе приходят ивенты с сервера. Но если вдруг клиенту что-то тоже нужно отправить, то он отправляет. И обычно в HTTP 1.1 там создается новый connection. То есть да, у тебя получается вместо... Если мы смотрим по эффективности по использованию ресурсов, то в HTTP 1.1 как раз-таки будет больше создаваться connections по сравнению с WebSockets. То есть WebSocket все-таки будет использовать один connection для того, чтобы и ивенты сервера, и ивенты клиента... Да, то есть TCP, он изначально, в принципе, задумывался как способ для двунаправленной коммуникации. То есть можно одновременно и читать, и писать. Ну это называется, да, это называется half... Это full duplex, это полнодуплексная коммуникация. Да, в Википедии мне очень понравилась визуализация. Вот есть мультиплексирование, это когда мы запихиваем все в один connection. Это просто на всякий случай сделаю такой рекап. Если у нас half-duplex, это представьте рации. То есть ты с одной стороны нажал кнопку и говоришь... Только один человек занимает канал. Поочередно, поочередно. Сначала один человек говорит, потом второй. Поочередно, потом второй человек говорит, но одновременно вы не можете. А вот full-duplex, это когда вы вместе можете по рации нажать кнопку, и вот как сейчас мы записываем с тобой этот подкаст, мы не особо там очередность соблюдаем, перерываем друг друга и слышим одновременно друг друга. Это как раз full-duplex communication. Верно. А bidirectional, так это вообще несколько есть определений. Двунаправленная коммуникация. Либо это просто, когда есть возможность и в одном, и в другом направлении. Просто возможность, неважно одновременно, просто есть возможность такая. Например, вот радио, там однонаправленная передача данных. А есть каналы передачи данных двунаправленные. Но есть еще второе определение. И не знаю, насколько это будет интересно. Вот есть такое понятие как двунаправленный HTTP. И вот то, что мы рассказали, short polling, long polling, HTTP streaming, это как раз варианты двунаправленного HTTP. Так что такое двунаправленный HTTP? Понятно, что HTTP использует TCP, а TCP по задумке он и есть. И можно и в одном, и в другом направлении им пользоваться. Так вот, двунаправленный HTTP, это значит, что не только клиент может инициировать общение. Потому что в HTTP обычно клиент всегда. Сначала клиент, потом сервер отвечает. Но в двунаправленном у нас получается, что и сервер может что-то инициировать. То есть может начать сервер какой-то передачи данных, а клиент может на это отреагировать как-то. То есть вот обычно в стандартной модели HTTP такое добиться невозможно. А вот с использованием, например, long polling это возможно. Но вот в HTTP 1.1 мы можем этого добиться, используя два коннекшена. То есть по одному коннекшену мы получаем ивенты сервера, можем на какой-то ивент каким-то образом отреагировать и отправить реквест по другому коннекшену этому серверу. В HTTP 2.0 есть оптимизация. Подожди, до HTTP 2.0 мы пока еще не дошли. В WebSockets, если так подвести определенный итог, главное преимущество по сравнению со всеми предыдущими методами, то что у нас для bidirectional вот такой вот коммуникации, full-duplex, используется один коннекшен. Это самое главное преимущество. Об этом написано даже в офисе. И если мы пытаемся, например, мы можем с помощью двух подключений дополить по одному потоку и пушить события с клиента по другому потоку. Там пост-запросы, например, отправлять в сервер. Мы, получается, будем использовать минимум два коннекшена. Для высоко нагруженных сервисов это невыгодно. Это очень тяжело по ресурсам. И плюс есть еще сложность на самом деле с тем, что тебе нужно понять, что соотнести реквест и респонс. То есть так как у тебя два разных коннекшена, не один коннекшен, а два разных коннекшена, то тебе нужно будет соотносить, кому ты там что отправил и от кого ты там что получил. Когда ты работаешь с одним TCP коннекшеном, несколько проще, как мне это видится. Ну хорошо, а вот по WebSockets здесь более-менее понятно, но есть, скажем так, как многие его называют, в принципе, убийца WebSockets. То есть протокол, который появился и который в некотором смысле не обесценил, а смысл WebSockets несколько теряется на фоне этого протокола. Я сейчас говорю об HTTP2, который мы не так часто, как, например, когда ты пишешь API клиента, мы не так часто пишем HTTP2 клиенты, но на самом деле количество сайтов, которые уже WebServices, сайтов, которые уже предоставляют возможность подключаться в том числе и по HTTP2, уже практически достигает 40%. То есть это технология, которая все активнее и активнее отвоевывает еще куски рынка. Мне кажется, что HTTP1.1 выбор для разработки backend API – это очень хороший выбор, потому что большинство как раз, большинство все-таки использует HTTP1.1. Но HTTP2, он набирает обороты, он становится все более популярным. И вот для как раз для браузеров, если ты для взаимодействия с браузерами, то есть смысл использовать HTTP2, согласен. Потому что браузеры и, ну вообще, машины как бы таких, у пользователей публичных сервисов, они хорошую получают, хорошую выгоду по использованию ресурсов, потому что самое главное преимущество HTTP2 – это как раз возможность переиспользовать один connection. То есть даже если ты несколько вкладок откроешь, из каждой вкладки будешь отправлять реквесты, у тебя это все будет через один TCP connection проходить. То есть HTTP2, он очень экономный, благодаря вот там, так называется, сложное слово, мультиплексирования технологии, которая там используется. Но вот как раз, как раз между прочим, вот ты правильно сказал, по вебсокетам он довольно, ну, похож по использованию ресурсов. Потому что и вебсокеты используют один connection, и HTTP2 тоже использует один connection. Ну и HTTP2 настолько неплохой получился транспорт, что, например, вот часто можно видеть технологию, которая требуется компаниями, это gRPC. gRPC использует HTTP2 в качестве транспорта. Реклама Вы прослушали восьмой выпуск подкаста о разработке бэкэнд-приложений. Вы можете подписаться на этот подкаст в Телеграме, в YouTube, Spotify, Google подкастах и других платформах. Также вы можете задать вопросы, предложить свое участие в подкасте или предложить тему, отправив запрос мне на почту. Спасибо, что остаетесь с нами, и до встречи через неделю.

К списку выпусков