Slim4 + Micro Services + ReactPhp Html Server

Non blocking, async and +/-30% faster solution than apache, plus much thinner docker containers. These are the main, most important pros (at least for me). For an isolated network (containers ports not published to the host machine) looks like a very interesting solution:

With a small hack:

src/App.php

<?php

namespace App;

use Psr\Http\Message\ServerRequestInterface;
use Slim\Factory\ServerRequestCreatorFactory;
use Psr\Http\Message\ResponseInterface as Response;

class App extends \Slim\App
{
    /**
     * @var Response;
     */
    public static $response; // <= (1)

    /**
     * Run application
     *
     * This method traverses the application middleware stack and then sends the
     * resultant Response object to the HTTP client.
     *
     * @param ServerRequestInterface|null $request
     * @return void
     */
    public function run(?ServerRequestInterface $request = null): void
    {
        if (!$request) {
            $serverRequestCreator = ServerRequestCreatorFactory::create();
            $request = $serverRequestCreator->createServerRequestFromGlobals();
        }

        self::$response = $this->handle($request); // <= (2)
    }
}

src/AppFactory.php

<?php

namespace App;

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\Interfaces\CallableResolverInterface;
use Slim\Interfaces\MiddlewareDispatcherInterface;
use Slim\Interfaces\RouteCollectorInterface;
use Slim\Interfaces\RouteResolverInterface;

class AppFactory extends \Slim\Factory\AppFactory // <= (3)
{
    /**
     * @param ResponseFactoryInterface|null         $responseFactory
     * @param ContainerInterface|null               $container
     * @param CallableResolverInterface|null        $callableResolver
     * @param RouteCollectorInterface|null          $routeCollector
     * @param RouteResolverInterface|null           $routeResolver
     * @param MiddlewareDispatcherInterface|null    $middlewareDispatcher
     * @return App
     */
    public static function create(
        ?ResponseFactoryInterface $responseFactory = null,
        ?ContainerInterface $container = null,
        ?CallableResolverInterface $callableResolver = null,
        ?RouteCollectorInterface $routeCollector = null,
        ?RouteResolverInterface $routeResolver = null,
        ?MiddlewareDispatcherInterface $middlewareDispatcher = null
    ): App {
        static::$responseFactory = $responseFactory ?? static::$responseFactory;
        return new App(
            self::determineResponseFactory(),
            $container ?? static::$container,
            $callableResolver ?? static::$callableResolver,
            $routeCollector ?? static::$routeCollector,
            $routeResolver ?? static::$routeResolver,
            $middlewareDispatcher ?? static::$middlewareDispatcher
        );
    }
}

And simple server:

$ php server.php

server.php

<?php

require 'vendor/autoload.php';

use App\AppFactory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use React\EventLoop\Loop;

use React\Http\Server as HttpServer;
use React\Socket\Server as SocketServer;

$loop = Loop::get();

$server = new HttpServer(function (Request $request) {
    echo 'Request to ' . $request->getUri(). PHP_EOL;

    $app = AppFactory::create();

    $app->get('/dudu', function (Request $request, Response $response, $args) {
        $response->getBody()->write("Hello Baba!");
        return $response;
    });

    $app->run($request);

    return $app::$response;
});

$socket = new SocketServer('127.0.0.1:8080');

$server->listen($socket);

$loop->run();

Hi @tj_gumis Thanks for this inspiring idea. May I ask why you need a static variable for the response instead of just return the response from the Slim app handle method?

For example:

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use React\EventLoop\Loop;
use Slim\Factory\AppFactory;

use React\Http\Server as HttpServer;
use React\Socket\Server as SocketServer;

$loop = Loop::get();

$server = new HttpServer(function (Request $request) {
    $app = AppFactory::create();

    $app->get('/dudu', function (Request $request, Response $response, $args) {
        $response->getBody()->write("Hello Baba!");
        return $response;
    });

    return $app->handle($request); // <--- why not just return the response directly?
});

$socket = new SocketServer('127.0.0.1:8080');

$server->listen($socket);

$loop->run();
1 Like

The answer is painfully simple - there is only one Odan in this world :smiley: . Thank you for your remark.

1 Like

Ok cool :slight_smile:
I’ve made some performance tests with Apache ab,
and I have to say that the response time is awesome.

Apache

Server Software:        Apache/2.4.39

Concurrency Level:      100
Time taken for tests:   4.183 seconds
Complete requests:      1000
Failed requests:        0
Keep-Alive requests:    1000
Total transferred:      283100 bytes
HTML transferred:       31000 bytes
Requests per second:    239.07 [#/sec] (mean)
Time per request:       418.280 [ms] (mean)
Time per request:       4.183 [ms] (mean, across all concurrent requests)
Transfer rate:          66.10 [Kbytes/sec] received

ReactPHP

Concurrency Level:      100
Time taken for tests:   0.396 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      127000 bytes
HTML transferred:       31000 bytes
Requests per second:    2525.12 [#/sec] (mean)
Time per request:       39.602 [ms] (mean)
Time per request:       0.396 [ms] (mean, across all concurrent requests)
Transfer rate:          313.17 [Kbytes/sec] received

Update: The result has been fixed after disabling the XDebug extension.

2 Likes

WOOOOOOOOOOWWWWWWWWWW - and I (lazy me, I must admit) was just relying on some random benchmark publication - it is certainly much more than 30% :smiley:

I tried react+slim example from @odan blog but routing is not working.
Only index page is working so how can I rewrite other requests to serve from the react server?

@raziul It works like any other route in Slim.

Example with a callback function:

$app->get('/users', function (Request $request, Response $response) {
        $response->getBody()->write('Hello users!');
        
        return $response;
    }
);
http://127.0.0.1:8080/users
Hello users!