-
-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Run externals.io on ReactPHP to improve performances #48
Conversation
…marks"" This reverts commit bc0100f.
Needs to be cleaner later.
root: 'web' | ||
# The front-controller script which determines where to send | ||
# non-static requests. | ||
passthru: '/index.php' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we move from a PHP-FPM setup to a ReactPHP setup on Platform.sh (documentation: https://docs.platform.sh/languages/php.html#alternate-start-commands)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably still want the locations
block, so that static assets are served as you expect. Just replace passthru: '/index.php'
with passthru: true
(once your application is talking HTTP directly, it has no concept of script anymore).
|
||
crons: | ||
# Look for new emails every 5 minutes. | ||
drush-queue: | ||
sync: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simply renamed the job for clarity, this should not have been included in this PR, sorry for the noise.
}); | ||
|
||
$loop->run(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ./console serve
command runs the React webserver.
Since React supports PSR-7 this is extremely easy to integrate with Stratify or any other PSR-7 framework.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be aware that it's not really compatible. https://github.com/reactphp/http/blob/d1ee39ad2a1f5cf3b5f727cdd14f466816b82bba/src/HttpBodyStream.php
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As documented thoroughly: http://reactphp.org/http/#request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, from what I've read this should not be an issue here but I learned something!
|
||
$server = new Server(function (ServerRequestInterface $request) use ($http, $db) : ResponseInterface { | ||
// Reset the DB connection | ||
$db->close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should move this into a middleware.
This line resets the database connection on every HTTP request. This is necessary because database connections can break or timeout, and Doctrine will not reconnect automatically. This is a problem specific to long running applications (like ReactPHP), on a classic application the PHP process dies (with the DB connection) on every HTTP request so there is no need for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found this analysis of the issue:
http://brady.lucidgene.com/articles/pdo-lost-mysql-connection-error
There are some ideas for other approaches in the comment section...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting link thank you! I was starting to consider whether to abstract DBAL behind another abstraction that restarts connections automatically… But that's overkill I think, and I don't think DBAL has an interface for the Connection
…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why but in several projects I tried to use this interface but always ended up using Doctrine\DBAL\Connection
directly instead.
'path.public' => __DIR__ . '/../../web', | ||
|
||
// Platform.sh defines this variable | ||
'http.port' => env('PORT', 8000), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default the application will run on port 8000
(e.g. in dev environment when running make preview
).
@@ -27,8 +30,11 @@ | |||
* HTTP stack. | |||
*/ | |||
return pipe([ | |||
BlackfireMiddleware::class, | |||
NewRelicMiddleware::class, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since Blackfire and New Relic consider that 1 HTTP request == 1 PHP process, they will not work correctly out of the box.
Those 2 middlewares activate manually Blackfire and New Relic for each HTTP request.
Those 2 middlewares could be moved to their own open source projects though.
MaintenanceMiddleware::class, | ||
ErrorHandlerMiddleware::class, | ||
AssetsMiddleware::class, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This middleware is useful only in dev environment. It serves static files (aka assets) like CSS, JS, etc.
Previously we didn't need it because PHP's built-in webserver (php -S 127.0.0.1:8000
) was handling it automatically. But since we are using React as the webserver, we need to serve the static files.
Using a middleware is not really efficient performance-wise, but this middleware is only serving files in dev
environment, not in production.
In production the middleware will not do much work, still it could be optimized. However I don't really care, the application is super duper fast, that's maybe something to optimize later if we really care.
@@ -141,6 +147,7 @@ | |||
|
|||
// Keep backward compatibility with old URLs (old threads) | |||
'/thread/{id}' => route(function (int $id, EmailRepository $emailRepository, Connection $db) { | |||
newrelic_name_transaction('thread_legacy'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have been committed on master too, my mistake.
@@ -1,5 +1,5 @@ | |||
preview: | |||
ENV=dev php -S localhost:8000 -t web | |||
ENV=dev ./console serve |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make preview
is the command used in development environment to run a small webserver.
We were previously using PHP's built-in webserver, now we simply launch the React application.
I would suggest to use amphp/aerys instead which als supports websockets etc. But still this is kinda nice! |
@staabm thanks, but we do not need websockets for now? I went for the most popular implementation that could work on Platform to be honest :) |
$body = new Stream($file); | ||
|
||
$array = explode('.', $file); | ||
$extension = strtolower(array_pop($array)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use pathinfo ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thanks!
{ | ||
$path = $request->getUri()->getPath(); | ||
|
||
$file = $this->publicPath . $path; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to make sure that te resulting path is within your webroot. Otherwise one can read arbritrary files..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do, I implemented this quickly for dev environments but you are right it's better to secure properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably best not to implement your own, e.g. Aerys has that built-in. Not sure whether ReactPHP's server has something like that yet.
|
||
$array = explode('.', $file); | ||
$extension = strtolower(array_pop($array)); | ||
$mimeType = self::MIME_TYPES[$extension] ?? 'application/octet-stream'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamically determine the mimetype instead of your custom map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted a fast response time because for most assets this will work. Maybe that could be a fallback to the array solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good enough, Aerys does the same thing.
$extension = strtolower(array_pop($array)); | ||
$mimeType = self::MIME_TYPES[$extension] ?? 'application/octet-stream'; | ||
|
||
return new Response($body, 200, [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this support cache revalidation with http 304 or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really important when it's only used for development.
"sentry/sentry": "^1.7" | ||
"sentry/sentry": "^1.7", | ||
"react/http": "^0.7.2", | ||
"blackfire/php-sdk": "^1.8" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afair you only need this sdk when the extension is not loaded.
You could also do a extension_loaded check before registering the middleware and drop this dependency
@mnapoli did you intentionally close this? if it didn't work out, I'd love to hear why :-) |
closed because of #90 |
@staabm thanks, was also interested in the why 👍 |
Right sorry for not leaving a comment ^^ Testing React was fun. This PR could be reopened later. But I want to leave platform.sh builds available for other PRs. And in the future (hopefully soon) I want to try and move externals.io to serverless/aws lambda using Bref. There is no strong reason except testing and learning so don’t expect any gains. In any case that wouldn’t work with React’s execution model. |
@mnapoli as long as you're just messin' around, I recommend trying out Swoole as well - it's neat :-) |
Right I've been wanting to look at it, it's very promising! Ping @Nyholm the diff of this PR might interest you ;) |
This PR introduces ReactPHP in order to replace PHP-FPM.
The web server is handled by React as a long running PHP application. As a result response time are drastically reduced.
Also, this was fun.
Profiling
Demo
The branch is deployed thanks to Platform.sh: https://react-e5mxmca-2snxbat5vggso.eu.platform.sh/