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)


  1. FanatPHP
    08.01.2023 07:58
    +10

    В целом неплохо, но хотелось бы больше конкретики. Я был на 100% уверен, что в конце будет реализация. И был довольно сильно разочарован. В частности, становится непонятно, к чему здесь вообще притянуты хабы Симфони и Доктрина. Ну хоть кусочек кода и оттуда и оттуда можно было привести, хотя бы для приличия?


  1. BigDflz
    08.01.2023 09:40

    --- Поскольку сервер веб-сокетов - это отдельное от основного бэкенда приложение, он ничего не знает о существующей базе данных.

    одно это говорито том, что предстоит пройти велосипедостроение. php - обработчик запросом от клиента, но не ws . оно конечно похвально, что php стремится расширить своё применение, но увы .... это не серверные приложения, и всю прелесть ws просто теряют....

    ---PHP все еще может быть не первым вариантом для работы с веб-сокетами

    это хорошо, что автор признался...


    1. FanatPHP
      08.01.2023 09:47
      +3

      одно это говорит о том, что предстоит пройти велосипедостроение. php — обработчик запросом от клиента, но не ws

      Если честно, то не вижу здесь логики. Да — отдельное, да — не знает. Но при чем тут велосипедостроение-то? Что имеется в виду под велосипедом? Использование РНР в качестве демона? Это давно уже не велосипед, а сложившаяся практика. О чем говорит, в частности, наличие описываемой в данной заметке библиотеки.


      1. Fafhrd
        08.01.2023 16:12

        Немного дополню.

        Устойчивая практика использования php в качестве демона в некоторых проектах возникла уже в 2003 году, когда завезли pcntl и select. А сколько радости было, когда на свет появился libevent.


  1. aceofspades88
    08.01.2023 10:28

    PHP - едва ли первое, что придет в голову, когда стоит задача поднять сервер веб-сокетов.

    Но поскольку PHP - это однозначно первое, что приходит в голову, когда речь идет о веб-приложениях

    Откуда столько пафоса? Почему так однозначно? У меня например уже года 3 как пхп в голову не приходит, судя по вакансиям компаний чуть больших чем "рога и копыта" тоже. В статье это никак не объясняется.


    1. mxr
      08.01.2023 10:45
      +9

      php рано списывать, на нем буквально пол интернета работает. Если 7я версия освежила язык, то 8я с добавлением нормального JIT гарантировала ему место в ТОПе ещё много лет.

      Никогда не понимал хейта php, если дело в говнокоде - посмотрите на Кресты. Там его больше и само по себе понятие зарождалось благодаря гибкости именно этого языка. Но это не делает язык плохим.


    1. Ctrl-V
      10.01.2023 13:06

      Согласен. Я изучаю JS и у меня всегда в голову на тему бэкенда приходит только JS, ну или Python, Go, Java или C#. А про PHP я уже давно забыл, потому что ассоциируется он у меня с чем-то медленным и непрофессиональным.


  1. colesnic89
    09.01.2023 16:39

    PHP - едва ли первое, что придет в голову, когда стоит задача поднять сервер веб-сокетов.

    Я тоже так думал пару лет назад. Но когда количество подключении возросло до 4-5к пожалел. Успешно перешли на NodeJS с использованием пакета WS. Бэкэнд (Rest API) остался на PHP, и через RabbitMQ шлются мессаджи на websoket. Пока полет нормальный. А главное сообщения аккумулируются в очередях, и после перезагрузки или падения websoket сервиса не исчезают.


  1. evgeniy_p
    09.01.2023 17:18
    +1

    Не проще реализовать все через centrifugo. Она написан на go и умеет держать много ws подключений.
    А из пхп отправлять события/сообщения через gRPC в centrifugo.
    Не думали над таким решением?


  1. MihaOo
    10.01.2023 13:04

    На проекте смотрели и на ратчет и на свуле, свуле по-лучше как мне кажется. Но в итоге нам не подошла ни одна из них. Сейчас используем ASP.NET + SignalR