Привет! Продолжаем цикл статей про тестирование не-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 все же надо самим добавить еще и Origin — https://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)
fomka12
13.07.2023 21:13в очередной раз хорошая статья - коротко и по делу. Спасибо, жду материал про удаленный вызов процедур)
Mapaxa864
Пример с дверью не очень удачный, кмк. Потому как просто поменялась дверь в которую будут стучать, а суть осталась прежней (по крайней мере такое складывается впечатление по примеру).
Hroft356 Автор
Либо дверь открыта всегда, либо открывается и закрывается много раз
Mapaxa864
Одна открыта, а другая закрыта. И все равно ждать звонка и идти к двери нужно.