Many PHP developers are green with envy when thinking about their colleagues who use Node. Asynchronous Node systems are sharing the codebase between protocols and serving them right out of the code. This really makes one wonder about switching to Node. Well, actually something resembling Node in PHP has been added as an extension. And it’s called Swoole.
Node in PHP? What is that Swoole thing?
Well, let me copy-paste the definition straight from the documentation:
“Production-Grade Async programming Framework for PHP
Enable PHP developers to write high-performance, scalable, concurrent TCP, UDP, Unix socket, HTTP, Websocket services in PHP programming language without too much knowledge about non-blocking I/O programming and low-level Linux kernel”.
Swoole works as an extension to PHP and it’s written in C. Sounds promising, right? Hosting HTTP server with PHP? Websockets in PHP itself? Many other possibilities, some looking exotic? And all this with keeping high performance. Let’s check it out!
How to get it running?
Installation methods on native platforms vary.
For Linux it boils down to one pecl command:
pecl install swoole
For MacOS, brew can be used:
brew install swoole
brew install homebrew/php/php72-swoole
And on Windows, native installation is not possible, but we should use Docker anyway.
Of course, the best idea to run the PHP and Swoole is the Docker container. Let’s see how to create a container which will allow us to use Swoole. First, we need a Dockerfile.
RUN pecl install swoole
ADD php.ini /usr/local/etc/php
RUN usermod -u 1000 www-data
That was pretty straightforward. After extending the official PHP Docker image, installing Swoole with pecl, and copying php.ini — we got it. The last line is a standard Docker fix for MacOS.
When it comes to php.ini config file which is copied, it’s even simpler — requires only one line:
What does it offer?
Swoole has variety of functionalities. Most of them are asynchronous. Here are some of the most interesting ones (other can be found at Swoole documentation):
- TCP/UDP server and client,
- HTTP server and client,
- Websocket server and client,
- Redis protocol server and client,
- MySQL client,
Let’s see how to use three of them: HTTP server, Websocket server and Filesystem. These are the most important ones in my opinion.
HTTP Server using Swoole
Swoole allows us to run a simple HTTP server which works asynchronously. Here’s the simple code that will also use asynchronous filesystem access to respond with index.html file to every request it handles.
As we can see, it looks a bit like Node.
First, we create the swoole_http_server object that resembles the HTTP server. Then, we bind two anonymous functions for two events: one for start, which will be executed when the server is started, and another for request, which will be executed for every request. It takes a request and responses objects as parameters.
Request object contains all the data related to the request itself: requested path, headers, and so on. On the other hand, it’s used to provide output, set headers etc. It’s worth mentioning that these two objects are not PSR conformant, but rather they are custom Swoole objects.
In the request event, asynchronous file system is used to load the data from a file. Once the data is available, a callback is fired with the content loaded. Then, this content is used to fill the response object and close the response. This sends the data back to the browser effectively.
That looks neat and most of all — works. But what about the performance?
HTTP Server benchmark
To test the performance of HTTP server using Swoole, I’ve created an app in Node — which can do exactly the same thing as the one in Swoole — and also a NGINX server which serves index.html as a static file. All dockerized in 3 separate containers.
Then, I used wrk tool to hit these containers hard. Results were astounding.
Swoole results were way better than expected!
That came as a surprise. I didn’t expect Swoole to overtake NGINX, but it actually did! Node was outdistanced. Raw power of this extension is really impressive, but it fades away with more work being done in the request. Unfortunately, Swoole has two little drawbacks that make these raw benchmarks irrelevant. We will get to them later.
Websocket Server using Swoole
As mentioned before, Swoole offers a method to create websocket server. It works asynchronously, follows the same methodology as HTTP and other Swoole functionalities. In my opinion, it’s one of the most important Swoole parts. Come on, websockets in PHP? I’m in. Let’s see how it looks.
Looks similar to HTTP server example.
First, we create the swoole_websocket_server object which resembles the websocket server. Then, we bind 4 anonymous functions to 4 events. One to start event, which will work just like start event from HTTP server. Another to open event which will be executed once a new websocket connects. Yet another to message event which will be executed when a websocket sends a message to the server. And finally — to close event which runs when a websocket disconnects.
Websockets connected to the server are identified by the unique ID that increments with each of the new websockets.
Problems encountered whilst using Swoole
It all worked well so far, but there were two problems I’ve encountered during the tests of some solutions using Swoole. These were:
- no real HTTPS support in HTTP server,
- no support for global variables in scripts.
First one is easy to mitigate. We just need to set up a reverse proxy using NGINX or any load balancer and it’s done. But by doing that, we lose extreme performance boost that Swoole offers.
Second one is more tricky. Swoole spawns worker processes for handling requests which means that if we create a global variable, its value is independent between threads and it can’t work. Here’s an example showing this behaviour.
One would expect the response would return 0, then 1, 2, 3 and so on, but instead it always returns 0.
I reached to the Swoole authors to check if it’s a bug, but it isn’t. In order to get the behaviour we expect, we can set worker_num = 1 in configuration, but this decreases the performance.
Swoole, as everything, has its bright sides and dark corners. I think it’s still a good idea to bring asynchronous programming to PHP. It can be used in various cases, ranging from fast prototyping, by concise single responsibility microservices, low-latency game servers and ending with being a backend server for big frameworks. Promising indeed.