Skip to content
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

WIP: Roadrunner example #1213

Conversation

alexander-schranz
Copy link

@alexander-schranz alexander-schranz commented May 11, 2021

Currently try to debug session problems with symfony application and roadrunner. See also: chirimoya/sulu-skeleton-runtime#1

  1. clone branch
  2. install composer dependency
  3. run redis-server
  4. adopt database in .env file (.env.local does not work)
  5. adopt following vendor classes (not yet sure which changes we need to contribute to symfony or roadrunner to get this running correctly)
diff --git a/EventListener/AbstractSessionListener.php b/EventListener/AbstractSessionListener.php
index 4244f460..52920dae 100644
--- a/EventListener/AbstractSessionListener.php
+++ b/EventListener/AbstractSessionListener.php
@@ -108,6 +108,8 @@ abstract class AbstractSessionListener implements EventSubscriberInterface
              * Symfonys session implementation starts the session on demand. So writing to it after
              * it is saved will just restart it.
              */
+            $response->headers->set('X-SESSION-ID', $session->getId());
+
             $session->save();
         }
diff --git a/src/Runner.php b/src/Runner.php
index 3ae36ca..76bfe82 100644
--- a/src/Runner.php
+++ b/src/Runner.php
@@ -8,7 +8,9 @@ use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
 use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
 use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
 use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Kernel;
 use Symfony\Component\HttpKernel\TerminableInterface;
 use Symfony\Component\Runtime\RunnerInterface;
 
@@ -22,7 +24,7 @@ class Runner implements RunnerInterface
     private $httpMessageFactory;
     private $psrFactory;
 
-    public function __construct(HttpKernelInterface $kernel, ?HttpFoundationFactoryInterface $httpFoundationFactory = null, ?HttpMessageFactoryInterface $httpMessageFactory = null)
+    public function __construct(Kernel $kernel, ?HttpFoundationFactoryInterface $httpFoundationFactory = null, ?HttpMessageFactoryInterface $httpMessageFactory = null)
     {
         $this->kernel = $kernel;
         $this->psrFactory = new Psr7\Factory\Psr17Factory();
@@ -38,7 +40,59 @@ class Runner implements RunnerInterface
         while ($request = $worker->waitRequest()) {
             try {
                 $sfRequest = $this->httpFoundationFactory->createRequest($request);
+
+                $hasRequestSession = $sfRequest->hasSession();
+
+                /** @var Response $sfResponse */
                 $sfResponse = $this->kernel->handle($sfRequest);
+
+                $sessionId = null;
+                $requestSessionId = null;
+                if ($sfRequest->hasSession()) {
+                    $container = $this->kernel->getContainer();
+                    /** @var array<string, mixed> $sessionOptions */
+                    $sessionOptions = $container->getParameter('session.storage.options');
+
+                    // $session = $sfRequest->getSession();
+                    // $sessionId = $session->getId();
+                    // read session from custom AbstractSessionListener
+                    $sessionId = $sfResponse->headers->get('X-SESSION-ID');
+
+                    // read session name from php or container config
+                    $sessionName = $sessionOptions['name'] ?? session_name();
+
+                    $requestSessionId = $sfRequest->cookies->get($sessionName);
+
+                    // if the session id did change we are setting it to the response
+                    // so the next request will have the correct cookie
+                    if ($sessionId !== $requestSessionId) {
+                        $expires = 0;
+                        $lifetime = $sessionOptions['cookie_lifetime'] ?? null;
+                        if ($lifetime) {
+                            $expires = time() + $lifetime;
+                        }
+
+                        $sfResponse->headers->setCookie(
+                            Cookie::create(
+                                $sessionName,
+                                $sessionId,
+                                $expires,
+                                $sessionOptions['cookie_path'] ?? '/',
+                                $sessionOptions['cookie_domain'] ?? null,
+                                $sessionOptions['cookie_secure'] ?? null,
+                                $sessionOptions['cookie_httponly'] ?? true,
+                                false,
+                                $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX,
+                            )
+                        );
+                    }
+                }
+
+                $sfResponse->headers->set('X-SESSION-ID', $sessionId ?? '');
+                $sfResponse->headers->set('X-REQUEST-SESSION-ID', $requestSessionId ?? '');
+                $sfResponse->headers->set('X-Has-Session', $sfRequest->hasSession() ? 'true' : 'false');
+                $sfResponse->headers->set('X-Has-Session-on-Request', $hasRequestSession ? 'true' : 'false');
+
                 $worker->respond($this->httpMessageFactory->createResponse($sfResponse));
 
                 if ($this->kernel instanceof TerminableInterface) {
  1. run roadrunner:
bin/rr serve -c .rr.admin.yaml

@Nyholm
Copy link
Member

Nyholm commented May 17, 2021

Thank you.

Could you elaborate why the changed to AbstractSessionListener.php is needed?

@alexander-schranz
Copy link
Author

alexander-schranz commented May 17, 2021

@Nyholm the cookie should only be written when the Session::save is called, else you would get an invalid session id as cookie. Described in Problem E in php-runtime/runtime#46 so best would be that the whole $response->headers->setCookie( would be moved to the AbstractSessionListener.php because the X-session-id looks a little bit strange.

Another solution would be that we have something like wasSaved on the session object:

$request->getSession()->wasSaved(); // write session cookie for browser

And to solve the destroy problem (Problem F in php-runtime/runtime#46) an additional:

$request->getSession()->wasDestroyed(); // remove session cookie from browser

I hope the Session itself is always an new instance for every master request else it could also make problems.

@javiereguiluz
Copy link
Member

@alexander-schranz thanks for this proposal.

After thinking about this, I've decided to close this without merging. The reason is that nowadays there are too many PHP runners (and new ones are being added, such as FrankenPHP) and we cannot support all of them.

These runners can use the Symfony Demo app for their own demos in their own repositories. We'd love that. Thanks for understanding.

@alexander-schranz
Copy link
Author

Sure make sense :)

Since we implemented symfony/symfony#41390 in Symfony Core alternative runtimes like roadrunner running in CLI context also works out of boxes without custom handling of sessions :).

@alexander-schranz alexander-schranz deleted the feature/roadrunner-example branch December 1, 2022 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants