Intro to WebSockets

Intro to WebSockets

@cherepawwka

Всем привет!

Сегодня мы поговорим о веб-сокетах, а также напишем простой скрипт на Python, который поможет решить таск с платформы CodeBy Games.

WebSockets

Приступим!


Что такое websockets?

WebSocket — протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером, используя постоянное соединение. Он делает возможным более тесное взаимодействие между браузером и веб-сайтом, способствуя распространению интерактивного содержимого и созданию приложений реального времени.

Веб-сокеты — это продвинутая технология, позволяющая открыть постоянное двунаправленное сетевое соединение между браузером пользователя и сервером. С помощью его API мы можем отправить сообщение на сервер и получить ответ без выполнения HTTP-запроса, причём этот процесс будет событийно-управляемым. Ключевое слово в этом определении — двунаправленное: с помощью веб-сокетов клиент и сервер могут инициировать связь друг с другом, а также могут отправлять сообщения одновременно. Почему это так важно? Чтобы в полной мере оценить возможности WebSocket, сделаем шаг назад и рассмотрим несколько самых распространенных способов, с помощью которых компьютеры могут получать данные с сервера.

Примечание. Часть материала — перевод этой статьи.

Requset-response

В традиционном HTTP, который сегодня использует большинство сайтов, веб-сервер предназначен для приема запросов от клиентов, а также для ответа на них. При этом коммуникация может быть инициирована только в одном направлении: от клиента к серверу. Код сервера определяет, какой тип запросов он должен ожидать и как реагировать на каждый из них. Сервер понятия не имеет, от кого исходит запрос. Это stateless протокол, технический способ сказать это: «HTTP не имеет состояния». Он рассматривает каждый новый запрос как полностью независимый. У нас есть способы обойти это правило — например, можно отправлять файлы cookie, которые помогают серверу идентифицировать клиента. При этом сами HTTP-сообщения все равно будут читаться и выполняться независимо друг от друга, и единственный способ для клиентов получать обновленную информацию с сервера — отправлять запросы.

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

Short polling

Одним из самых простых решений этой проблемы является метод «Short polling». При таком подходе клиент "пингует" сервер, скажем, каждые 500 мс, получая новые данные каждые 500 мс. Однако у этого подхода есть несколько очевидных недостатков:

  • Задержка данных как минимум на 500 мс;
  • Высокое потребление ресурсов сервера при большом количестве запросов;
  • Большинство запросов возвращаются пустыми, если данные обновляются не так часто.

Long polling

Еще одним обходным путем задержки получения данных является метод «Long polling». При его использовании сервер получает запрос от клиента, но не отвечает на него, пока не получит новые данные из другого запроса. Этот метод более эффективен, чем многократная проверка связи с сервером, поскольку он избавляет от хлопот, связанных с анализом заголовков запросов, запросом новых данных и отправкой пустых ответов.

Однако теперь сервер должен отслеживать несколько запросов и порядок их получения. Кроме того, время ожидания запросов может истекать, поэтому пользователю необходимо периодически отправлять новые запросы.

Server-sent Events (SSE) | EventSource

Другой метод отправки сообщений — Server-Sent Events API, который позволяет серверу отправлять обновления клиенту, используя интерфейс EventSource JavaScript. Этот интерфейс создает постоянное однонаправленное соединение с сервером через HTTP и использует специальный заголовок потока событий. В итоге все запросы обрабатываются кодом как события JavaScript, поэтому практически не вызывают задержки между запросом и ответом.

Server-Sent Events (SSE) — однонаправленные, они отлично подходят для приложений, в которых нам не нужно отправлять на сервер какие-либо данные (лента новостей, стоимость акций in real time). Однако SSE не поддерживается старыми браузерами, а большинство существующих браузеров ограничивают количество одновременных подключений SSE. Но такой технологии все ещё недостаточно для создания того же мессенджера: получать обновления в реальном времени — хорошо, но мы хотим иметь возможность отправлять сообщения, и делать это тоже в режиме реального времени.

Подробнее про веб-сокеты

Итак, нам нужен способ для моментальной отправки информации на сервер и такого же быстрого получения обновлений с сервера. Тут мы возвращаемся к двусторонней («дуплексной») связи, с которой хорошо справляются именно веб-сокеты.

Поддерживаемый почти всеми современными браузерами WebSocket API позволяет открывать такое двустороннее соединение с сервером. Кроме того, сервер может отслеживать каждого клиента и отправлять сообщения подмножеству клиентов. То есть с помощью веб-сокетов мы можем пригласить всех друзей в наш чат и отправлять сообщения всем или некоторым из них, а не только одному человеку, как при использовании других протоколов связи.

Различие WebSocket и polling

Веб-сокеты под капотом

Как именно работает эта технология? Ранее я не раз упомянул протокол HTTP. Он состоит из запросов и ответов, каждый из которых содержит строку запроса (например, GET /assets/icon.png), заголовки и необязательное тело сообщения, используемое, например, в POST-запросах для отправки некоторых данных на сервер.

WebSocket — это еще один протокол для отправки и получения сообщений. Как и HTTP, веб-сокеты отправляют сообщения через соединение TCP. HTTP и WebSocket используют один и тот же механизм доставки на уровне пакетов, но протоколы структурирования сообщений у них различаются:

  • Чтобы установить WebSocket-соединение с сервером, клиент сначала отправляет HTTP-запрос «handshake» со специальным заголовком (upgrade), указывая, что клиент хочет установить WebSocket-соединение;
  • Запрос отправляется на ws: или wss:: URI (аналог http или https);
  • Если сервер устанавливает WebSocket-соединение, и это соединение разрешено (если запрос исходит от клиента, прошедшего проверку подлинности или внесенного в белый список), то сервер отправляет ответ об успешном рукопожатии. На это указывает HTTP-код 101 Switching Protocols.
Переход на вебсокет
Заголовки запроса и ответа (внимание на Upgrade: websocket)

После обновления соединения протокол переключается с HTTP на WebSocket. И хотя все пакеты по-прежнему отправляются через TCP, связь теперь соответствует формату сообщений WebSocket. Это происходит, потому что TCP является дуплексным протоколом, где клиент и сервер могут отправлять сообщения одновременно. Все данные могут быть фрагментированы, поэтому через этот формат можно отправить даже очень большое сообщение — например, изображение. В этом случае веб-сокеты разбивают его на фреймы. Каждый фрейм содержит небольшой заголовок, который указывает длину и тип полезной нагрузки, а также информацию, является ли этот кадр последним.

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

Наконец, когда соединение можно закрыть, либо клиент, либо сервер могут отправить сообщение «close».

Как устанавливается WebSocket-соединение

Процесс установления соединения начинается с WebSocket handshake — он включает в себя использование ws или wss, о котором я писал выше. Чтобы стало чуть понятнее, можно считать ws или wss эквивалентными HTTP и HTTPS. Установка WebSocket-соединения начинается с обновления HTTP-запроса, который содержит пару заголовков, таких как Connection: Upgrade, Upgrade: WebSocket, Sec-WebSocket-Key.

Запрос

Заголовок Upgrade в коде запроса ниже означает WebSocket handshake, в то время как Sec-WebSocket-Key содержит случайное значение с использованием кодировки Base64. Это значение произвольно генерируется во время каждого WebSocket-рукопожатия. Кроме того, заголовок ключа также является частью этого запроса.

Заголовок Upgrade
Заголовок Sec-WebSocket-Key

Перечисленные выше заголовки образуют GET-запрос, например:

GET ws://websocketexample.com:8181/ HTTP/1.1
Host: localhost:8181
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: b6gjhT32u488lpuRwKaOWs==
Запрос на переключение на WebSocket
Запрос в сыром виде

Ответ

При получении ответа заголовок Sec-WebSocket-Accept содержит часть значения, представленного в заголовке запроса Sec-WebSocket-Key. Это связано со спецификацией протокола — такой подход используется для подтверждения, что сервер поддерживает веб-сокет и позволяет обойти ошибки, в случае, когда веб-сокеты не поддерживаются. Пример ответа можно видеть ниже:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: rG8wsswmHTJ85lJgAE3M5RTmcCE=
Ответ при переключении на WebSocket
Ответ в сыром виде

Где используют веб-сокеты

Помимо Socket.io, существует множество других реализаций веб-сокетов в разных языках программирования:

  • ActionCable в Ruby on Rails;
  • Channels в Django для Python;
  • Gorilla на Go;
  • Meteor — полноценный JavaScript-фреймворк, основанный на WebSocket вместо HTTP;
  • Apollo — сервер GraphQL, который помогает получать данные в режиме реального времени с помощью веб-сокетов.

Следующие типы проектов чаще всего используют веб-сокеты:

  • Мессенджеры: концептуально самая простая реализация веб-сокетов. Пользователи отправляют сообщения на сервер, который мгновенно отправляет эти сообщения получателю;
  • Многопользовательские игры: общий шаблон для многопользовательских игр заключается в том, что сервер хранит игровое состояние пользователей. Игроки выполняют какие-то действия, которые сразу отправляются на сервер. Он обновляет состояние игры и быстро отправляет его обратно пользователям;
  • Приложения для совместной работы: веб-сокеты позволяют сразу нескольким пользователям работать в одном документе и мгновенно обновлять его для всех;
  • Карты: обновляются моментально при даже небольшом изменении GPS-координат пользователя.

Краткий итог

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


Практика

Выше я уже вкратце рассмотрел, как выглядит переход на сокеты в задании, о котором пойдёт речь в этой статье. Для примера я рассмотрю таск "Я не робот!" с платформы CodeBy Games.

Описание задания

При посещении ресурса нас встречает капча и поле ввода:

Рассматриваемый ресурс

Капча представляет из себя закодированную в base64 строку, содержащую простой пример:

Закодированный в base64 пример

Для примера я обновлю страницу и покажу, что происходит под капотом:

Запросы при обновлении страницы

Как мы видим на скриншоте, приложение меняет протокол на WebSocket. Ему присущи все те этапы, которые я упомянул выше.

При посещении вкладки Response мы увидим данные, которые поместило в сокет приложение:

Данные, направленные от сервера в сокете

Если мы решим пример и отправим ответ, то увидим, как с нашей стороны идёт отправка данных:

Отправка данных от нас

Сразу же мы получаем вторую строку, закодированную в base64. Кто-нибудь мог подумать, что можно использовать Burp Suite, но тут нас ждёт следующее:

Взаимодействие с приложением из браузера через прокси
Весь проксированный трафик
История взаимодействия с сокетом

Здесь нам тоже нужно последовательно декодить и решать каждый пример (я не знаю функционала, который поможет автоматизировать математические операции). И так будет продолжаться 50 раз, пока мы правильно не ответим на все вопросы. Звучит долго и нудно. Что же в таком случае делать?

Правильно, автоматизировать. И тут нам на помощь приходит Python!

Python WebSockets

Для решения этого таска я написал простой Python скрипт, использующий 3 библиотеки: asyncio и websockets для автоматизации работы с веб-сокетом нашего приложения и base64 для декодинга информации, полученной в сокете:

Листинг скрипта

Код скрипта в тектовом виде также приведен ниже:

import asyncio
import websockets
import base64

async def codeby():
 async with websockets.connect('ws://62.173.140.174:16011/ws') as websocket: #creating socket
  for i in range (0,50): #cycle for task
   response = await websocket.recv() #wait for data from server
   print(response)
   if i==0:
    data = response[response.find(":")+2:] #select substing in base64 in first iteration
   else:
    data = response[response.find(":")+2:response.find("(")-1] #select substing in base64 in other iterations
   decoded_data = base64.b64decode(data).decode('utf-8') #decode data from base64
   print(f"Decoded from base64: "+decoded_data)
   expression = decoded_data[4:] #math preparation
   result = eval(expression) #counting
   print(result)
   await websocket.send(str(result)) #send data to socket

  response = await websocket.recv()
  print(response)

asyncio.get_event_loop().run_until_complete(codeby())

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

Так, если его запустить, мы увидим следующее:

Запуск скрипта

В результате за считанные секунды получим заветный флаг!

Okay, gimme yor password

Так, ознакомившись с технологией WebSocket, мы простым путём решили несложное задание с CTF платформы и заработали 200 баллов.

До новых встреч!

До новых встреч!


Report Page