php-runtime / runtime Goto Github PK
View Code? Open in Web Editor NEWA home for runtimes.
License: MIT License
A home for runtimes.
License: MIT License
I'm using the Bref runtime to run a Symfony application in Lambda with CloudFront in front of it. Getting the source ip address with that layout is hard, esp. because the SymfonyRequestBridge is overriding the proxy configuration when starting up.
While reading the code I was wondering, why the Bridge is using the proxy configuration at all, instead of using the Lambda provided clientIp which already contains the correct value.
By replacing (SymfonyRequestBridge:40)
'REMOTE_ADDR' => '127.0.0.1',
with
'REMOTE_ADDR' => $event->getRequestContext()['http']['sourceIp'],
I was able to fix this issue for me while testing.
I just try to use runtime using swoole runner but i got 404 when serving static files
No route found for "GET http://localhost:9876/favicon.ico"
Basically i use https://github.com/k911/swoole-bundle
and have no issue about it and want to try to use symfony runtime
When reading the docs on deploying a Bref runtime, there is a custom layer for PHP runtime. Bref provides a list of layer ARNs at https://runtimes.bref.sh. Is there somewhere similar the layer ARNs for the PHP runtimes are published?
See #71
While investigating the suitability of runtime/swoole:0.3.0
, I ran into a blocker caused by streamed responses.
I was evaluating runtime/swoole
because I ran into a problem similar to #50, and as of roadrunner-server/roadrunner#923 (comment), I also couldn't use RoadRunner (which I much prefer to Swoole, at first glance).
Testing this in automation is a bit of a mess (suggestions welcome though), but the idea is that I can stream a response as follows:
final class MyGiantCsvDownloadController
{
public function someAction(): Response
{
return new StreamedResponse(function () {
foreach (range(1, 5) as $i) {
echo str_repeat((string) $i, 100000), "\n";
sleep(1);
}
}, Response::HTTP_OK, [
'Content-Type' => 'text/plain',
]);
}
}
Symfony\Component\HttpKernel\EventListener\StreamedResponseListener
When interacting with this controller, through runtime/swoole
, the respone is not sent via \Runtime\Swoole\SymfonyHttpBridge
, where it should happen:
runtime/src/swoole/src/SymfonyHttpBridge.php
Lines 48 to 57 in 420d39e
In this block, $response->write($buffer);
should receive a massive chunk of 111111...22222...<SNIP>...55555
, but instead, all output is sent to STDOUT in the worker (not to the response object), and a warning is produced:
<SNIP>55555555555
PHP Warning: Swoole\Http\Response::write(): data to send is empty in /srv/share/vendor/runtime/swoole/src/SymfonyHttpBridge.php on line 50
Effectively, this write produces nothing, because the response was already sent by symfony/http-kernel
:
After some investigation, I found that the culprit is that symfony/http-kernel
sends StreamedResponse
through a listener:
I disabled this listener by monkey-patching (while trying out stuff), but if you know a "clean" way to remove it completely from my application, lemme know.
Assuming we disabled the StreamedResponseListener
(clean solution pending), the response content now makes it to Swoole\Http\Response#write($buffer);
, but in one big chunk: the HTTP client sees the first byte when swoole finished collecting the whole response.
This is because of this ob_start()
call not having a defined buffer size specified:
runtime/src/swoole/src/SymfonyHttpBridge.php
Lines 49 to 53 in 420d39e
According to the documentation for ob_start()
:
If the optional parameter
chunk_size
is passed, the buffer will be flushed after any output call which causes the buffer's length to equal or exceedchunk_size
. The default value0
means that the output function will only be called when the output buffer is closed.
Given that PHP uses a default buffer size of 4096
, perhaps it would be a good idea to add one here too? I tried it locally, and my HTTP client starts receiving bytes much earlier than 5 seconds, this way :)
With this runtime remember_me is not set. I changed few lines of method reflectSymfonyResponse of SymfonyHttpBridge from:
foreach ($sfResponse->headers->all() as $name => $values) {
foreach ((array) $values as $value) {
$response->header((string) $name, $value);
}
}
to:
foreach ($sfResponse->headers->all() as $name => $values) {
$response->header((string) $name, $values);
}
And it works now.
Fatal error: Declaration of Runtime\Psr17\Runtime::getArgument(ReflectionParameter $parameter, ?string $type)
must be compatible with Symfony\Component\Runtime\GenericRuntime::getArgument(ReflectionParameter $parameter, ?string
$type): mixed in /srv/middleware/vendor/runtime/psr-17/src/Runtime.php on line 53
More as a reference here currently there is no way to use a StreamedResponse
inside roadrunner. This is because things output with echo
, var_dump
, flush
or any other php internal method is not supported by roadrunner and is in this case ignored.
The issue on the roadrunner side is here: https://github.com/spiral/roadrunner/issues/2.
When using Swoole, we need to maintain session (cookie)
Ref: https://github.com/k911/swoole-bundle/blob/develop/src/Bridge/Symfony/HttpFoundation/Session/SetSessionCookieEventListener.php
We need to do this in plain PHP without PSR-7 or any other abstractions.
Hi,
Would it be possible to create a new tag, at least for runtime/frankenphp-symfony
?
Currently, it's not possible to install the package with Symfony 7, and breaks projects relying on FrankenPHP such as Symfony Docker.
Thanks!
When recieving a big number of requests (via stress test, for example) while having hook_flags
set to SWOOLE_HOOK_ALL
, the Runtime triggers the following Swoole error:
app-1 | [2024-04-16 22:31:36 $7.0] WARNING Server::check_worker_exit_status(): worker(pid=19, id=3) abnormal exit, status=0, signal=11
app-1 | A bug occurred in Swoole-v5.1.1, please report it.
app-1 | The Swoole developers probably don't know about it,
app-1 | and unless you report it, chances are it won't be fixed.
app-1 | You can read How to report a bug doc before submitting any bug reports:
app-1 | >> https://github.com/swoole/swoole-src/blob/master/.github/ISSUE.md
app-1 | Please do not send bug reports in the mailing list or personal letters.
app-1 | The issue page is also suitable to submit feature requests.
app-1 |
app-1 | OS: Linux 5.15.146.1-microsoft-standard-WSL2 #1 SMP Thu Jan 11 04:09:03 UTC 2024 x86_64
app-1 | GCC_VERSION: 12.2.0
app-1 | PHP_VERSION : 8.3.4
app-1 |
If I try to use the hook_flag in a bare Swoole server, without Symfony runtime, I get no error. Example:
<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;
$server = new Server('0.0.0.0', 8000, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
$server->set([
'hook_flags' => SWOOLE_HOOK_ALL
]);
$server->on('request', function (Request $request, Response $response) {
$response->end('Ok');
});
$server->start();
This does not trigger any error on the same stress test.
Steps to reproduce:
return $this->json('Ok');
is enough) to /example
;docker run --rm -it williamyeh/wrk -c 100 -d 1 -t8 http://host.docker.internal:8000/example
;Simply changing the hook flags to something like SWOOLE_HOOK_ALL & ~SWOOLE_HOOK_FILE & ~SWOOLE_HOOK_STDIO
makes the error go away, so I assume it's a problem that happens when logs are added.
I also opened an issue here: swoole/swoole-src#5294
It would be nice were the difference between the two runtimes documented; I'm unsure which runtime to select, or whether they are production-ready (the version number doesn't indicate so).
Could you clarify the documentation a little?
Requires a few tweaks: symfony/symfony#49443
Some of these could be solved in the runtime instead of relying on the user to do them: https://github.com/box-project/box/blob/main/doc/symfony.md#symfony-support
Runtime would need to ship:
getProjectRoot()
/cc @theofidry
Hi Tobias.
We are developing with Symfony and Google Functions. It's fresh so there are changes probably. Today we are facing some bad news.
We have problem from today as Coposer 1 run by deploy script from google forces to install their own router and crashes on it.
When I do it myself before, there starts to be conflict and google in autoload and router is invoked as the first so your library is out of work.
Maybe we are not the only one or we deploy differently. Do you have any suggestions?
Is there any way to share knowledge around your project than mailing you?
-------------------- Not installing Google's router:
ERROR: (gcloud.functions.deploy) OperationError: code=3, message=Build failed: Warning from https://repo.packagist.org: Support for Composer 1 is deprecated and some packages will not be available. You should upgrade to Composer 2. See https://blog.packagist.com/deprecating-composer-1-support/
Warning from https://repo.packagist.org: Support for Composer 1 is deprecated and some packages will not be available. You should upgrade to Composer 2. See https://blog.packagist.com/deprecating-composer-1-support/
./composer.json has been updated
Loading composer repositories with package information
Warning from https://repo.packagist.org: Support for Composer 1 is deprecated and some packages will not be available. You should upgrade to Composer 2. See https://blog.packagist.com/deprecating-composer-1-support/
Updating dependencies (including require-dev)
Restricting packages listed in "symfony/symfony" to "5.3.*"
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Conclusion: don't install google/cloud-functions-framework v0.7.2
- Conclusion: don't install google/cloud-functions-framework v0.7.1
- Conclusion: remove guzzlehttp/psr7 2.0.0
- Installation request for google/cloud-functions-framework ^0.7 -> satisfiable by google/cloud-functions-framework[v0.7.0, v0.7.1, v0.7.2].
- Conclusion: don't install guzzlehttp/psr7 2.0.0
- google/cloud-functions-framework v0.7.0 requires guzzlehttp/psr7 ^1.6 -> satisfiable by guzzlehttp/psr7[1.6.0, 1.6.1, 1.7.0, 1.8.0, 1.8.1, 1.8.2].
- Can only install one of: guzzlehttp/psr7[1.7.0, 2.0.0].
- Can only install one of: guzzlehttp/psr7[1.8.0, 2.0.0].
- Can only install one of: guzzlehttp/psr7[1.8.1, 2.0.0].
- Can only install one of: guzzlehttp/psr7[1.8.2, 2.0.0].
- Can only install one of: guzzlehttp/psr7[1.6.0, 2.0.0].
- Can only install one of: guzzlehttp/psr7[1.6.1, 2.0.0].
- Installation request for guzzlehttp/psr7 (locked at 2.0.0) -> satisfiable by guzzlehttp/psr7[2.0.0].
Installation failed, reverting ./composer.json to its original content.; Error ID: caafae72
-------------- Installing Google's router
Nothing to install, update or remove
Generating optimized autoload files
Warning: Ambiguous class resolution, "Google\CloudFunctions\CloudEvent" was found 2x: in "D:/www/cg-platby-test/app/vendor/google/cloud-functions-framework/src/CloudEvent.php" and "D:/ww
w/cg-platby-test/app/vendor/runtime/google-cloud/google\CloudEvent.php", the first will be used.
Warning: Ambiguous class resolution, "Google\CloudFunctions\Context" was found 2x: in "D:/www/cg-platby-test/app/vendor/google/cloud-functions-framework/src/Context.php" and "D:/www/cg-p
latby-test/app/vendor/runtime/google-cloud/google\Context.php", the first will be used.
Warning: Ambiguous class resolution, "Google\CloudFunctions\LegacyEventMapper" was found 2x: in "D:/www/cg-platby-test/app/vendor/google/cloud-functions-framework/src/LegacyEventMapper.p
hp" and "D:/www/cg-platby-test/app/vendor/runtime/google-cloud/google\LegacyEventMapper.php", the first will be used.
37 packages you are using are looking for funding.
Use the composer fund
command to find out more!
--------------- and then deployed:
*5 FastCGI sent in stderr: "PHP message: PHP Fatal error: Uncaught InvalidArgumentException: Function target is not callable: "throwException" in /workspace/vendor/google/cloud-functions-framework/router.php:68
Stack trace:
#0 /workspace/vendor/google/cloud-functions-framework/router.php(77): {closure}()
This runtime is more a PSR-15 runtime than a PSR-17 one.
The implementation relies (optionally) on PSR-17 to know how to create the PSR-7 request. But the application logic being run is implemented using the PSR-15 interface.
I originally submitted this in php-runtime/psr-17#2 before realizing it was a subtree split (maybe you should deactivate issue trackers in the subtree repos, or add a config for the issue creation redirecting to the mono-repo)
I'm trying to migrate my Symfony app with Bref to use Runtime, and I can't get the Lambda function with Symfony Console to work correctly. I also get the same behaviour for Nyholm/bref-runtime-demo.
By "work correctly", I mean that if I execute a command with vendor/bin/bref cli
, I can't see any output in my console. The command seems to be executed correctly, its output is logged in CloudWatch, but I can't see the output on my machine.
The readme in this project implicates that console function should keep using bref:layer.console
layer:
# pluggins and other options omitted
provider:
environment:
APP_RUNTIME: Runtime\Bref\Runtime
functions:
console:
handler: bin/console
layers:
- ${bref:layer.php-80}
- ${bref:layer.console}
The demo project suggests that console functions should use bref-extra:symfony-runtime-php-80
layer:
# pluggins and other options omitted
provider:
environment:
APP_RUNTIME: Runtime\Bref\Runtime
functions:
console:
handler: bin/console
layers:
- ${bref:layer.php-80}
- ${bref-extra:symfony-runtime-php-80}
Either way, the function seems to be executed correctly, but there is no local output.
I was able to display the function's output locally with the following config by forcing it to use GenericRuntime
.
# pluggins and other options omitted
provider:
environment:
APP_RUNTIME: Runtime\Bref\Runtime
functions:
console:
environment:
APP_RUNTIME: Symfony\Component\Runtime\GenericRuntime
handler: bin/console
layers:
- ${bref:layer.php-80}
- ${bref:layer.console}
With this setup, the function is executed correctly, its output is logged to CloudWatch, and I can see the output locally in my console. In other words, it works exactly in the same way as the "old" bin/console
without Runtime did. But I'm not sure this is the correct approach.
It's probably fine for a console function to write only to stderr if it's a cron, but for functions executed remotely with vendor/bin/bref cli
(e.g. DB migrations), their output should be transferred back to the client.
Am I missing something?
The runtime/react instantiates an HttpServer for you which is where one would define middleware. Can we abstract this in a decent way? Maybe we can add an optional $middlewares
argument to getRunner()
.
Or something a bit more long-term for people that want to tweak it different ways, perhaps there's a way were we can just extend the ServerFactory to use an HttpServer we configure. Not sure how that would work though. Maybe replace new HttpServer
with $this->getHttpServer()
, return a new one by default, or whatever the user decided if we've registered that class somehow... perhaps as an option argument to getRunner()
.
Hello,
I would like to retrieve the authorization informations from api-gateway when I'm using the bref runtime.
From a bref layer (for example php-74-fpm
), I could retrieve this information with $_SERVER['LAMBDA_REQUEST_CONTEXT']
(see Bref\Event\Http\FpmHandler.php)
Currently there is no session handling implemented in the Runner.php
. Symfony itself does never write a session cookie the the Response
object so the session cookie is never set.
First I want to demonstrate the expected behaviour, my research here is based on https://sulu.rocks/admin which currently runs on a normal apache server and the symfony demo using dev php webserver.
As we see the login page, and have a look at our Cookies
. There are 0 cookies set. So if a route is behind a firewall but the route is anonymous available no session cookies are written by symfony.
State | Value |
---|---|
Request Cookie | |
Response Set-Cookie |
If we login to the application but no user is set also no session is started.
State | Value |
---|---|
Request Cookie | |
Response Set-Cookie |
Log into an application with a correct user not having a session.
State | Value |
---|---|
Request Cookie | |
Response Set-Cookie | my_session_id |
Aslong as we are now navigating the session_id should stay the same and never again a Set-Cookie
should be called aslong as the session does not expire.
State | Value |
---|---|
Request Cookie | my_session_id |
Response Set-Cookie |
If we are logout of the application the session cookie value be set to deleted and automatically be expired so its also removed from the browser cookies.
State | Value |
---|---|
Request Cookie | my_session_id |
Response Set-Cookie | deleted, expired 1970 |
If we have an expired session cookie in our browser php will again add a Set-Cookie
header that the browsers remove it.
State | Value |
---|---|
Request Cookie | my_session_id |
Response Set-Cookie | deleted, expired 1970 |
Currently the Session is not written to the Response Object.
Solution A1:
I currently did workaround this by doing this in my modified Runner.php
by adding something like this:
// ...
$sessionName = $this->sessionOptions['name'] ?? \session_name();
// ...
$sessionId = \session_id();
// ...
$sfResponse->headers->setCookie(
Cookie::create(
$sessionName,
$sessionId,
$expires,
$this->sessionOptions['cookie_path'] ?? '/',
$this->sessionOptions['cookie_domain'] ?? null,
$this->sessionOptions['cookie_secure'] ?? null,
$this->sessionOptions['cookie_httponly'] ?? true,
false,
$this->sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX,
)
);
Solution A2: Symfony adds an option that the AbstractSessionListener
will write this cookie when the session was saved here: https://github.com/symfony/symfony/blob/bbb0a694fbbd5a527b2236636271a47122b5d47c/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php#L110
As php is never shutdown we have the problem that we need cleanup all session based php states ourselve
Solution B1: I currently did this in the Runner.php
by adding a finally block to the run method
finally {
if (session_status() === PHP_SESSION_ACTIVE) {
// abort the session if nobody did write it
session_abort();
}
$_SESSION = []; // reset session array
session_id(''); // else the next request would have the same session_id which would be a security problem
}
Solution B2: Something which maybe should be tackled with in PHP itself to add a function which would reset this. And so also avoid Problem C.
The session need also be read from the request object. As session_id will return empty string and php will not read it from the $_COOKIE variable.
Solution C1: In the Runner.php
I did do this with:
$sessionName = $this->sessionOptions['name'] ?? \session_name();
$requestSessionId = $sfRequest->cookies->get($sessionName, '');
\session_id($requestSessionId);
As the runner is Symfony specific it should also use the Symfony Framework cookie configuration when it is set.
Solution D1: That the symfony configuration for the cookie is used, I booted once the kernel in the constructor to read the sessionOptions
from it and use them to set the session cookie in the run method as seen above.
/**
* @var array<string, mixed>
*/
private $sessionOptions;
public function __construct(Kernel $kernel, ?HttpFoundationFactoryInterface $httpFoundationFactory = null, ?HttpMessageFactoryInterface $httpMessageFactory = null)
{
$this->kernel = $kernel;
// ...
$kernel->boot();
$container = $kernel->getContainer();
/** @var array<string, mixed> $sessionOptions */
$this->sessionOptions = $container->getParameter('session.storage.options');
$kernel->shutdown();
}
Currently inside the Runner.php
we don't know if a session is started or not this is because symfony does when calling session->save()
inside the AbstractSessionListener, reseting the session state in its NativeSessionStorage. There would only be 1 hint that a session was started here which is the closed = true
flag, sadly this variable is not exposed and so we never now in the Runner.php
if a session was really started or not.
Solution E1: Symfony writes the session cookie at https://github.com/symfony/symfony/blob/bbb0a694fbbd5a527b2236636271a47122b5d47c/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php#L110 when a specific option is enabled.
Currently we know never when a session is getting destroyed or should be removed so Set-Cookie
with deleted
and expire 1970
is never called.
I currently have yet a solution for this, see Point 5. and 6. above when this happens
So I have php-runtime working with swoole and symfony. Works like a charm! The only thing I cant seem to work out is to create pools for lets say SQL or Redis.
Previously I had created a command that runs a HttpServer and also spun up some workers for pools etc to keep connections open.
Can't seem to find any answers here in the issue history or anywhere else for that matter.
Kind regards,
Rogier
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.