swoole / swoole-src

🚀 Coroutine-based concurrency library for PHP
https://www.swoole.com
Apache License 2.0
18.4k stars 3.16k forks source link

New sessions ids generated for each request #4478

Closed Ahzam-Lathiya closed 2 years ago

Ahzam-Lathiya commented 2 years ago

Hello, So I've built an MVC project from scratch. I wanted to integrate this library for asynchronous requests. After integrating I'm having issues logging in users and keeping them logged in.

Please answer these questions before submitting your issue. Thanks!

  1. What did you do? If possible, provide a simple script for reproducing the error.

Below is the sessionManager class which is initiated inside my application class. So what happens is on each request a session object is initialized and that object checks given the session id if there is a user stored in the $_SESSION global. It retrieves the user object from that global given 'user' key. This is how my project worked before using swoole.

After swoole I replaced $_SESSION with php-redis and I use the redis object to set , get and delete key values in the redis-server. I've checked my php-redis, because it works fine outside of this swoole project.

class SessionManager
{
  public $redis;

  public function __construct()
  {
    if(session_status() == 1)
    {
      session_start();
    }

    echo session_id() . PHP_EOL;
    echo session_status() . PHP_EOL;

    // Create phpredis instance
    $this->redis = new \Redis ();

    // connect redis
    $this->redis->connect('127.0.0.1', 6379); 
  }

  public function get($key)
  {
    $key = "PHPREDIS_SESSION:" . session_id() . ":" . $key;
    return $this->redis->get($key);
  }

  public function set($key,$data)
  {
    $key = "PHPREDIS_SESSION:" . session_id() . ":" . $key;
    $this->redis->set($key, $data);
  }

  public function remove($key)
  {
    $key = "PHPREDIS_SESSION:" . session_id() . ":" . $key;
    $this->redis->delete($key);
  }

  public function destroy()
  {
    session_destroy();
  }

}
  1. What did you expect to see? I was expecting the session_id to stay consistent given that session_start() checks if there is a session already, it then uses that very same id to continue between pages.

  2. What did you see instead? I am seeing different session ids generated on each request. I can't login users with this functionality.

I've also tried without the session_start() function in the constructor, in that case the session is stored and a user can login and logout just ok. But, there is only one session id no other session id generated to differentiate between sessions. Only one user can be logged in at a time.

  1. What version of Swoole are you using (show your php --ri swoole)? swoole
Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 4.7.0
Built => Aug 18 2021 18:11:03
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 1.1.1  11 Sep 2018
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
zlib => 1.2.11
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
mysqlnd => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608
  1. What is your machine environment used (show your uname -a & php -v & gcc -v) ? uname:
    Linux ahzam-probook 5.4.0-89-generic #100~18.04.1-Ubuntu SMP Wed Sep 29 10:59:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

php version:

PHP 8.0.12 (cli) (built: Oct 22 2021 12:34:00) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.12, Copyright (c), by Zend Technologies

gcc -v :

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

Anyone who can tell how to properly implement sessions in this framework. Thanks in advance.

twose commented 2 years ago

Do not use php session, it's blocking and it can not work in Swoole. Maybe something like https://github.com/hyperf/session can help you. A similar issue: https://github.com/swoole/swoole-src/issues/4197

Ahzam-Lathiya commented 2 years ago

I've used the ContextManager class sample and I am trying to print context ID and the collection of context id, I'm getting nothing. Getting the same context ID on different requests.

Also this is how I am using swoole with my application in index.php

use Swoole\Http\Server as HttpServer;
use Swoole\Coroutine as Co;
use Swoole\Http\Request;
use Swoole\Http\Response;

//enable Hooks
Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);

$server = new HttpServer('127.0.0.1', 8000);

$server->on('start', function ($server) {
    echo "Server started at http://127.0.0.1:8000\n";
});

$server->on('request', function (Request $request, Response $response){

$app = new Application($request, $response);

$app->routes['GET']['/'] = [SiteController::class, 'home'];
$app->routes['GET']['/home'] = [SiteController::class, 'home'];
$app->routes['GET']['/contact'] = [SiteController::class, 'contact'];
$app->routes['POST']['/contact'] = [SiteController::class, 'contact'];
$app->routes['GET']['/about'] = [SiteController::class, 'about'];
$app->routes['GET']['/stuck'] = [SiteController::class, 'stuck'];
$app->routes['GET']['/coroutine'] = [SiteController::class, 'coroutineID'];
$app->routes['GET']['/contexts'] = [SiteController::class, 'contexts'];

  $app->run($request, $response);

});

$server->start();

In the application the request according to the 'request_uri' and 'request_method' is served with the corresponding controller and the method. In the controller I'm just printing the coroutine ID and am getting the same coroutine ID for different windows/browsers.

use Swoole\Http\Response;
use Swoole\Http\Request;
use Swoole\Coroutine as Co;

class ContextManager
{
    // Set is used to save a new value under the context
    public function set(string $key, mixed $value)
    {
        // Get the context object of the current coroutine
        $context = Co::getContext();

        // Long way of setting a new context value
        $context[$key] = $value;
        //$content->key = $value;

        // Short method of setting a new context value, same as above code...
        //Co::getContext()[$key] = $value;
    }

    // Navigate the coroutine tree and search for the requested key
    public function get(string $key, mixed $default = null): mixed
    {
        // Get the current coroutine ID
        $cid = Co::getCid();

        do
        {
            /*
             * Get the context object using the current coroutine 
             * ID and check if our key exists, looping through the
             * coroutine tree if we are deep inside sub coroutines.
             */
            if(isset(Co::getContext($cid)[$key]))
            {
                return Co::getContext($cid)[$key];
            }

            // We may be inside a child coroutine, let's check the parent ID for a context
            $cid = Co::getPcid($cid);

        } while ($cid !== -1 && $cid !== false);

        // The requested context variable and value could not be found
        return $default ?? throw new InvalidArgumentException(
            "Could not find `{$key}` in current coroutine context."
            );
    }

    public function getCoID()
    {
      return Co::getCid();
    }

    public function getAllContexts()
    {
      return Co::list();
    }
}

I'm calling the getAllContexts(), getCoID() method in the controller methods. For the first method I'm getting nothing and for the second I'm getting '1' on different windows/browsers.

How can I set unique Coroutines on different windows, so that I can start implementing some sort of session storage in the ContextManager??

Ahzam-Lathiya commented 2 years ago

@twose Perhaps I'm getting the same coroutine ID because there is only one coroutine generated instead of different coroutines generated for different windows/browsers.

twose commented 2 years ago

Try to set worker_num to 1 on the Server to debug this problem, because your requests may be dispatched to different worker processes, and cid always starts from 1.

Ahzam-Lathiya commented 2 years ago

@twose You're right about that the cid always starts from 1 and increments on each request. How can I get some sort of unique id that keeps track which window/client initiated this request. I'm trying to build a session manager, but can't store a unique key because each request increments the cid. This is what is returned when I echo Co::getAllContexts() :

{"0":1}

//next request
{"0": 2}

// next request
{"0": 3}
matyhtf commented 2 years ago

You should save session data to redis or files, so that can be used across processes

Ahzam-Lathiya commented 2 years ago

@matyhtf In order to save data in redis, I need some sort of unique key, if I set my redis session data on basis of my coroutine ID, then it would be a disaster because that would change on every request. I need some sort of unique ID, all of my sessions should be distinct. Why can't I get/regenerate the session ID without the session functions ??

twose commented 2 years ago

Generate a token (anything you like, such as a random string), bind the token to the user in DB, and send the token to the client by set cookie. Then the client needs to access the server with the cookie, now you can get its token from the cookie, then use the token to get the user from DB. That's how session libraries work, is that what you're asking? I am not sure...

Ahzam-Lathiya commented 2 years ago

@twose Yes, that is what I asked, I'll look into it. Thanks a ton.