A note on websockets in PHP
PHP is not a very smart choice to build a websockets server. Any article out there would suggest NodeJS, Python or Go. But since PHP is the choice when it comes to web-development, why shouldn’t we try our best?
Considerations
Running a WS server in PHP is quite straightforward. There is a superb package named Ratchet that is both simple and framework agnostic. The problem is that the resulted server is not actually part of your application, which means you have to manually integrate it to a database, API and frontend.
Integrating an external library is one thing, but there are some restrictions and difficulties coming from websockets architecture itself.
Authorization
First of all, websockets server is open to any connection by default. One do can limit connections by certain domain or IP list, but that is not very convenient for any non-corporate application. Usually we deal with this kind of problem with some kind of authorization: access tokens, session cookies, etc. But we can’t send an HTTP header or cookie via ws://
protocol!
Database
Another problem was briefly mentioned above. WS server does not know anything about your database. And since it’s hard to imagine a modern PHP application built without any framework, integrating ORM services and framework architecture with native script can be challenging. Minimal strategy would require supplementing every websockets event with calls to database and, probably, application’s API.
Architecture
Last but not least, one should always remember that backend application with websockets server is not ONE application, but actually — two separate applications, no matter how tight an integration between them will be. Naturally, it should involve design of clean interfaces upon which websockets server would rely.
Options
We do however have some options to deal with mentioned difficulties. Some of this options may sound a bit crude, but they are working nonetheless.
Dealing with authorization
Due to the nature of websockets protocol, there is a moment when initial HTTP request is upgraded to WS request. Ratchet package includes this request in its Connection
object. Even though we can’t pass a Bearer header to it, we can add a token (JWT for example) as a query parameter, which is quite secure over HTTPS. Connection URL therefore may look like this:
ws://localhost?token={token_value}
Query is accessible with simple parse_str
function applied to Guzzle\HttpRequest
inside Ratchet Connection
object.
After token is extracted, we can use the same authenticator mechanism as anywhere else in main application.
Integrating database service
Your application is probably built with one of popular frameworks such as Symfony or Laravel and relies upon its ORM. If so, you can inject ORM service or manager into your Ratchet script. Following some instructions on the internet you have probably created a console command for starting a websockets server. Direct injection of database service into this kind of script is not very secure, but it allows you to extend it with methods to update users, modifying incoming message with stored data on the fly and so on. In Symfony, you can inject Doctrine manager first into console command constructor and then into your websocket instance constructor.
Separating applications
Since we should treat our main application and WS server as separate instances, nothing is stopping us from using main app API inside websockets server. Common scenario is saving messages history and then getting it in frontend. Most certainly, your application already has API routes for CRUD operations with messages. Here we’re facing a rather difficult choice. After ORM service injection, we can use it to perform CRUD on websockets events. ORM or API? Naturally, both options will work and final decision will always be questionable. But using an API as an option will have two positive sides: it helps to avoid duplicate code (since operations will be absolutely identical in controller and in WS instance) and to follow general application’s logic of separate systems. Here’s another Symfony example.
Moreover, as we extracted token and injected ORM service, we can validate both authorization and data on every single message event and actually impersonate client with HTTP request.
Conclusion
PHP still may be not the first choice when working with websockets. But it is quite possible to run a simple websocket server without ignoring security issues and existing architecture. Thank you for reading!