2010/02/23

Variables from PHP sessions in Nginx config

Recently I found this really cool Nginx module mod_eval: http://github.com/vkholodkov/nginx-eval-module
It allows you to store responses from Nginx upstreams (backends) into variables which can be reused inside the Nginx configuration syntax. This offers a whole lot of new possibilities. So it made me think what I could use that for... One problem that we are often facing is that we would like to move more logic from PHP into Nginx, since the Nginx simply is waaay more efficient than our PHP framework. After considering a lot of possible enhancements this module could give us, I came to the conclusion that it would be really useful if the Nginx knew details about the users, which usually only the PHP knows about.
The Nginx itself can already extract cookie values and store them into $cookie_* variables, there already is a Memcache module for the Nginx which i can use in combination with the mod-eval to retrieve Memcache values, combined I can use those modules to do following:
  • Get the users session id out of his session_id cookie using the Nginx internal header parsing
  • Retrieve the users serialized PHP session out of Memcache using the Nginx Memcache module
  • Store the serialized PHP session into an Nginx variable using the mod_eval
Now the only piece missing is a parser for the serialized PHP sessions, so thats what i wrote. Unfortunately the Nginx configuration syntax doesn't support multidimensional array structures, only simple variables. So I couldn't implement this thing in a way which really represents the whole session. I had to implement it as some kind of string scanner which takes a search path that has to be extracted from the serialized multi dimensional session array. I guess that sounds quite complicated now... Well, I can't say it isn't, but an example should help:
In PHP i stored this structure into my session
$_SESSION['symfony/user/sfUser/attributes'] => Array
  (
    [users_dynamic] => Array
      (
        [get_last_online_state] =>
        [update_counter_time] => 1266041164
      )
    [subscriber] => Array
      (
        [user_actual_culture] => de
        [lastURI] => http://dev.poppen.lab/frontend_dev.php/home
        [invisibility] => 0
        [getGender] => m
      )
  )
My goal is to extract the users gender, so I specify the search path symfony/user/sfUser/attributes|s:10:"subscriber";s:9:"getGender" The return value will then use the PHP serialize syntax like s:1:"m"which means it is of type string, has length 1, and value m.
And now the same thing in the Nginx:
location / {
  eval $session {
    # store the retrieved memcache value into the variable $session

    set $memcached_key $cookie_session_id; # extract the value of the cookie session_id
    # extract the value of the cookie "session_id" and use it as memcache key

    memcached_pass 1.2.3.4:11211;
    # get the serialized session from memcache
  }

  php_session_parse $result $session "symfony/user/sfUser/attributes|s:10:\"subscriber\";s:9:\"getGender\"";
  # extract the gender from the serialized session in $session and store the return value into $result

  if ($result = "s:1:\"w\"")
  {
    # for girls
  }

  if ($result = "s:1:\"m\"")
  {
    # for boys
  }

  # for logged out users
}
This whole thing seems to be working quite well for me and I can't find problems with it at the moment, but I have to admit that we don't have it running in any production environment yet, with emphasis on yet. Once its running in some prod env I will post again about what we used it for, and if it works or not, not necessarily in that order.
For the ones who dare to take the risk already, I'd be glad about comments, usecases and murder threats of admins who got fired because I killed their production sites: http://github.com/replay/ngx_http_php_session