Привет! Продолжаем цикл статей про тестирование не-REST-бэкенда, в прошлый раз мы говорили о GraphQL, теперь пришло время WebSocket.

Итак, что такое WebSocket?

Википедия сообщает, что это «протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером, использующий постоянное соединение».

Что тут важно — что это протокол (со всеми вытекающими последствиями для протокола), который использует постоянное соединение.

Работу по WebSocket в обычной жизни можно представить примерно так.

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

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

ОК, что вам делать в такой ситуации? Как минимум, открыть свою крутую звуконепроницаемую дверь, чтобы услышать звонок курьера в дверь квартиры, и продолжать работать. Когда он придет и позвонит, вы услышите и среагируете.

Собственно, вот так работает WebSocket.

А если бы мы взаимодействовали через REST-HTTP, всё выглядело бы немного иначе.

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

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

Я в этот момент начинаю выглядеть как-то так.

Но потом вспоминаю, что я в первую очередь инженер, и мне интересно понять «А почему так?». Сижу и думаю — неужели они реально используют HTTP? Подключаю мобилку к компу, смотрю трафик — и правда, оказывается, мобильный клиент раз в 25 секунд шлет HTTP-запрос на сервер и уточняет, есть ли в чате новый ответ от оператора. Если нет, то он зачем-то перерисовывает фронт. Заодно дропая все введенные мною данные. Видимо, ребята не тестировали это. И зря.

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

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

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

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade

Первый – это connection upgrade. Тут мы говорим серверу, что готовы перенести наши отношения на новый уровень, более постоянный и вообще хороший. Второй — мы указываем, что мы хотим сделать апгрейд на WebSocket.

Если сервер может, он отвечает — хорошо, это будет код ответа 101, и служебные заголовки смены протокола. 

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMl YUkAGmm50PpG2HaGWk=
Sec-WebSocket-Protocol: chat'

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

После этого между клиентом и сервером устанавливается тип соединения connection, они начинают общаться.

HTTP Connect is upgrading to WS
TYPE: Connection

Если вдруг кто-то там отвалился или закрыл соединение, тип коннекта меняется на дисконнект.

WS connection close
TYPE: Disconnect

И здесь сразу возникает вопрос — а как сервер поймет, что клиент все еще там?

Представьте себе пневмопочту.

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

Вот на этот случай у WebSocket-протокола есть специальный механизм пинг-понгов — он всегда может уточнить, “слышно” ли его и стоит ли продолжать работу. Если вдруг кто-то не ответил, происходит несколько попыток (с увеличивающимся таймаутом), и после этого соединение считается закрытым. У WebSocket для этого есть свои коды ответов, которые все так или иначе завязаны именно на закрытие соединения.

Status Code

Meaning

1000

Normal Closure

1001

Going Away

1002

Protocol error

1003

Unsupported Data

1004

Reserved

1005

No Status Rcvd

1006

Abnormal Closure

1007

Invalid frame payload data

1008

Policy Violation

1009

Message Too Big

1010

Mandatory Ext.

1011

Internal Error

1012

Service Restart

1013

Try Again Later

1014

The server was acting as a gateway or proxy and received an invalid response from the upstream server. This is similar to 502 HTTP Status Code.

1015

TLS handshake

1016-2999

Unassigned

3000

Unauthorized

3001-3002

Unassigned

3003

Forbidden

3004-3999

Unassigned

4000-4999

Reserved for Private Use

Как с ним работать в Postman

На одном из своих прошлых проектов я как раз тестировал поиск на WebSocket, так что на этом примере и разберемся.

Запустим Google Chrome, откроем консоль разработчика (F12), перейдем на сайт https://www.onetwotrip.com/ru/bus/ и поищем билеты Москва-Тверь на дату в будущем. Дожидаемся окончания загрузки и переходим во вкладку Network

Увидим следующую картину. Как видите, везде коды 200, то есть обычные HTTP-запросы.

Где ловить WebSocket запрос? Он находится на вкладке WS, WebSocket

Тут видно, что протокол SSL-ный, защищенный и есть вся информация.

Есть и вкладка payload — показывает, что мы отправили какие-то данные.

Есть вкладка Messages, где мы видим все сообщения, которыми обменивались клиент и сервер.

Здесь мы сначала сделали connection, потом мы отослали на сервер ОК, и он вернул нам данные в четыре захода

Как это все делать и протестировать в Postman?

Нажимаете в Postman кнопку New, видите WebSocket.

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

Давайте потестируем.

В адрес вводим wss://www.onetwotrip.com/_bus/ws/run/search?startGeoId=16&endGeoId=100&date=2023-08-10&adults=1&children=0

Если дата в параметре date устарела, меняем на будущую. Postman сам проставит параметры и заголовки, но в Headers все же надо самим добавить еще и Originhttps://www.onetwotrip.com

Нажимаем, как всегда, кнопочку connect, и видим, что все остановилось. Почему?

Смотрим вниз окна и форму Responses, где нас уже ждет ответ.

Раскрываем его

Видим, что сперва был послан сначала обычный HTTPS-запрос на установку Websocket-соединения со всеми служебными заголовками: connection upgrade, WebSocket, все как надо.

Сервер прислал мне в ответ 101, мол, я согласен, давай проапгрейдим наши отношения на WebSocket. Он выдал мне сокет и все остальное, включая куки.

При этом статус нашего соединения connected.

Теперь с ним можно работать, сервер ждет от нас сообщение

Пошлем ему ОК. Для этого во вкладке Messages напишем “ОК” и нажмем кнопку Send.

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

Это сигнал для клиента к отключению соединения. Если раскрыть это сообщение, увидим, что мы завершились с кодом 1000 — это нормальное завершение.

Собственно, в этом и заключается вся магия вебсокета. Во-первых, нам нужно установить вебсокет-соединение: посылаем обычный http-запрос (Postman это, кстати, делает автоматически). Далее посылаем данные в том формате, который ждет сервер, и получаем от него информацию в ответ. В конце еще получим какие-то определенные коды, связанные с закрытием коннекта.

Что можно тестировать?

Прежде всего — сам факт установки соединения. Потому что, может быть, сервер откажется соединяться, или он отключен, или умеет только в http.

Обязательно не забудьте проверить security, SSL никто не отменял и установку по WSS тоже нужно проверить.

Далее, как в классическом тестировании backend api, — проверяем формат ответа и сами данные, которые внутри.

Как написано выше, у вебсокета есть свои коды ответов. Тестировать или нет их — зависит от обстоятельств. Если вам это действительно нужно, ведь у вас есть такой функционал завязанный на это, — то вперед! В своей практике я с тестированием кодов ответа почти не сталкивался.

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


С вебсокетом на этом всё. Для тех кто еще с нами — в третьей части поговорим про самое сложное из цикла. Про gRPC.

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


  1. Mapaxa864
    13.07.2023 21:13
    +1

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


    1. Hroft356 Автор
      13.07.2023 21:13

      Либо дверь открыта всегда, либо открывается и закрывается много раз


      1. Mapaxa864
        13.07.2023 21:13

        Одна открыта, а другая закрыта. И все равно ждать звонка и идти к двери нужно.


  1. fomka12
    13.07.2023 21:13

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