PHP - едва ли первое, что придет в голову, когда стоит задача поднять сервер веб-сокетов. Практически каждая статья в интернете будет пестрить предложениями использовать для этого NodeJS, Python или Go. Но поскольку PHP - это однозначно первое, что приходит в голову, когда речь идет о веб-приложениях, почему бы не попробовать?
На самом деле, запуск сервера веб-сокетов на PHP довольно прост. Существует превосходная библиотека Ratchet, позволяющая работать на любом фреймворке (или вовсе без него) полноценно и легко.
Казалось бы, на этом разговор можно заканчивать, но мы неизбежно столкнемся с некоторыми ограничениями и проблемами, связанными с архитектурой конечного приложения и природой самого протокола веб-сокетов.
Авторизация
По умолчанию, сервер веб-сокетов открыт для любого подключения. Конечно, можно поставить сетевые ограничения по доменам или IP адресам, но для веб-приложения - это, мягко говоря, не эффективный подход. В обычной ситуации мы используем для таких ограничений тот или иной вариант сервиса авторизации - токены, сессии и т.д. Здесь же проблема в том, что мы не сможем отправить через протокол ws:// ни HTTP заголовок, ни cookies. Значительная часть привычных методов, таким образом, не сработает.
Архитектура
Основное приложение != сервер веб-сокетов. Для работы с ними всегда необходимо держать в голове, что мы имеем дело с двумя отдельными приложениями, вне зависимости от того, насколько тесно они взаимодействуют между собой. На первый взгляд это может показаться незначительным нюансом, однако такое положение вещей требует особого внимания к подготовке интерфейсов для интеграции основного приложения и сервера веб-сокетов. Ко всему прочему, это порождает еще одну проблему.
База данных
Поскольку сервер веб-сокетов - это отдельное от основного бэкенда приложение, он ничего не знает о существующей базе данных. Сложно представить себе современное приложение на PHP, написанное без использование какого-либо фреймворка и ORM, так что перед разработчиком встанет дополнительная задача интегрировать службы, сервисы и библиотеки для работы с БД в сторонний скрипт.
Решения
Для каждой из названных проблем вполне возможно отыскать соответствующее решение. Некоторые из потенциальных решений могут показаться шероховатыми, но главное, что они рабочие.
Авторизуем пользователей
В процессе подключения к серверу веб-сокетов существует этап, на котором исходный HTTP запрос преобразуется в WS запрос. Используемая нами библиотека Ratchet сохраняет этот начальный запрос в объекте Connection. Хотя возможности подцепить Bearer заголовок к запросу нет (для клиентского приложения запрос строится сразу как ws://websocket-server), мы можем передать токен (например, JWT) в параметрах запроса. При использовании HTTPS - это вполне безопасный способ передачи.
В итоге, запрос на подключение может выглядеть примерно так:
ws://server?token={token_value}
Строку параметров затем можно извлечь из упомянутого ранее объекта Connection.
<?php
$request = $connection->httpRequest;
$queryString = $request->getUri()->getQuery();
parse_str($queryString, $query);
$token = $query[‘token’]
После извлечения токен может использоваться в любом уже применяющемся механизме авторизации, реализованном в основном приложении.
Интегрируем базу данных
В 9 из 10 случаев основное приложение будет написано на одном из популярных фреймворков вроде Laravel или Symfony. Все, что нам необходимо реализовать в такой ситуации - внедрение службы, отвечающей за ORM, в конструктор сервера веб-сокетов. При условии, что для запуска сервера используется консольная команда, использующая компонент Symfony Console, мы можем сделать это в два этапа: первоначальной инъекцией в конструктор консольной команды, а оттуда передачей в конструктор основного класса веб-сокетов.
Разделяем приложения
Раз уж мы вынуждены расценивать основное приложение и сервер веб-сокетов как два отдельных компонента, ничто не мешает нам использовать API основного приложения внутри сервера веб-сокетов. Пожалуй, самый распространенный сценарий - сохранение сообщений в БД и последующая отдача их фронтенд-приложению.
В целом, после внедрения ORM в обработчик веб-сокетов, мы могли бы выполнять все это с помощью обычных CRUD-операций. Но гораздо более эффективным решением было бы использовать уже готовый API. Почему? Во-первых, это позволит избежать дублирования кода (ровно такие же CRUDы используются в контроллерах, отвечающих за API). Во-вторых, таким способом мы укладываемся в общую архитектуру разделенных компонентов, даже внутри монолитного решения. Более того, имея одновременно токен из исходного запроса и внедренный ORM, мы получаем возможность авторизовывать действия и валидировать данные при абсолютно каждом событии веб-сокетов, а это уже полноценная имперсонификация пользователя.
Выводы
PHP все еще может быть не первым вариантом для работы с веб-сокетами, но на нем все еще вполне возможен запуск и эксплуатация полноценного сервера веб-сокетов со всеми необходимыми соображениями безопасностями и прозрачной архитектуры.
Комментарии (10)
BigDflz
08.01.2023 09:40--- Поскольку сервер веб-сокетов - это отдельное от основного бэкенда приложение, он ничего не знает о существующей базе данных.
одно это говорито том, что предстоит пройти велосипедостроение. php - обработчик запросом от клиента, но не ws . оно конечно похвально, что php стремится расширить своё применение, но увы .... это не серверные приложения, и всю прелесть ws просто теряют....
---PHP все еще может быть не первым вариантом для работы с веб-сокетами
это хорошо, что автор признался...
FanatPHP
08.01.2023 09:47+3одно это говорит о том, что предстоит пройти велосипедостроение. php — обработчик запросом от клиента, но не ws
Если честно, то не вижу здесь логики. Да — отдельное, да — не знает. Но при чем тут велосипедостроение-то? Что имеется в виду под велосипедом? Использование РНР в качестве демона? Это давно уже не велосипед, а сложившаяся практика. О чем говорит, в частности, наличие описываемой в данной заметке библиотеки.
Fafhrd
08.01.2023 16:12Немного дополню.
Устойчивая практика использования php в качестве демона в некоторых проектах возникла уже в 2003 году, когда завезли pcntl и select. А сколько радости было, когда на свет появился libevent.
aceofspades88
08.01.2023 10:28PHP - едва ли первое, что придет в голову, когда стоит задача поднять сервер веб-сокетов.
Но поскольку PHP - это однозначно первое, что приходит в голову, когда речь идет о веб-приложениях
Откуда столько пафоса? Почему так однозначно? У меня например уже года 3 как пхп в голову не приходит, судя по вакансиям компаний чуть больших чем "рога и копыта" тоже. В статье это никак не объясняется.
mxr
08.01.2023 10:45+9php рано списывать, на нем буквально пол интернета работает. Если 7я версия освежила язык, то 8я с добавлением нормального JIT гарантировала ему место в ТОПе ещё много лет.
Никогда не понимал хейта php, если дело в говнокоде - посмотрите на Кресты. Там его больше и само по себе понятие зарождалось благодаря гибкости именно этого языка. Но это не делает язык плохим.
Ctrl-V
10.01.2023 13:06Согласен. Я изучаю JS и у меня всегда в голову на тему бэкенда приходит только JS, ну или Python, Go, Java или C#. А про PHP я уже давно забыл, потому что ассоциируется он у меня с чем-то медленным и непрофессиональным.
colesnic89
09.01.2023 16:39PHP - едва ли первое, что придет в голову, когда стоит задача поднять сервер веб-сокетов.
Я тоже так думал пару лет назад. Но когда количество подключении возросло до 4-5к пожалел. Успешно перешли на NodeJS с использованием пакета WS. Бэкэнд (Rest API) остался на PHP, и через RabbitMQ шлются мессаджи на websoket. Пока полет нормальный. А главное сообщения аккумулируются в очередях, и после перезагрузки или падения websoket сервиса не исчезают.
evgeniy_p
09.01.2023 17:18+1Не проще реализовать все через centrifugo. Она написан на go и умеет держать много ws подключений.
А из пхп отправлять события/сообщения через gRPC в centrifugo.
Не думали над таким решением?
MihaOo
10.01.2023 13:04На проекте смотрели и на ратчет и на свуле, свуле по-лучше как мне кажется. Но в итоге нам не подошла ни одна из них. Сейчас используем ASP.NET + SignalR
FanatPHP
В целом неплохо, но хотелось бы больше конкретики. Я был на 100% уверен, что в конце будет реализация. И был довольно сильно разочарован. В частности, становится непонятно, к чему здесь вообще притянуты хабы Симфони и Доктрина. Ну хоть кусочек кода и оттуда и оттуда можно было привести, хотя бы для приличия?