Wednesday, June 22, 2011

PHP session parser in production

Last year I have posted about the Nginx ngx_http_php_session module that i wrote and which is able to parse php sessions and extract certain values from them. Unfortunately this took quite long time, but now i can finally announce that i am actually using this on a production system and so far it seems stable.
In this current case where I'm using the module in production now, it is used to optimize the way how Nginx is using the Memcaches. Since quite long time I already had a Memcache in production which is fed plain HTML by PHP and then read out and delivered by the Nginx. This works well as long as the content is more or less static and independent on the user which accesses it, and exactly this second requirement is now not necessary anymore. Using the "ngx_http_php_session" module the Nginx can check if a requesting user has a valid session, and if he does, it can check for things like payment status, gender, his preferences or whatever your site content is depending on. After this is known to the Nginx, the correct cache can then be retrieved accordingly. As a result there are many more pages that are cachable in a very efficient way.
Here i show an example how this module is used to decide if the Nginx should use the profile cache key for paying users or the one for non-paying users.
location /

    if ($cookie_session_id = "") {
        rewrite . /login redirect;
    }

    eval $session {
        set $memc_key $cookie_session_id;
        memc_pass session_memcache;
    }

    php_session_parse $authenticated $session "symfony/user/sfUser/authenticated";

    if ($authenticated != "b:1") {
        rewrite . /login redirect; #the user is logged out
    }

    php_session_parse $is_paying $session "symfony/user/sfUser/attributes|s:10:\"subscriber\";s:9:\"is_paying\"";
    php_session_parse $user $session "symfony/user/sfUser/attributes|s:10:\"subscriber\";s:11:\"getNickname\"";

    php_session_strip_formatting $stripped_user $user;

    if ($is_paying = "i:1") {
        rewrite . profile_cache$uri:paying last; # the user is paying
    }

    rewrite ^/(.+)$ $1;
    set $tmp_compare "$stripped_user:$uri";

    if ($tmp_compare ~* "^(.*):\1$") {
        rewrite . profile_cache$uri:paying last; # the user is viewing his own profile
    }

    rewrite . profile_cache$uri:non-paying last; # the user is non-paying
}

location profile_cache {
    default_type    text/html;
    add_header      "Content" "text/html; charset=utf8";
    lower           $uri_low $uri;
    set $memc_key $uri_low;
    memcached_next_upstream error timeout not_found;
    error_page      500 404 405 = /fallback_to_php;
    memc_pass  profilememcache;
}
In the previous example you can also see the directive "php_session_strip_formatting". This is used because a string which is extracted from a PHP session is formatted, like for example "s:7:astring". By using the "php_session_strip_formatting" directive the actual string "astring" will be extracted and the formatting will be stripped away.
Another directive from this example is "lower". This directive is coming from another module that i wrote with the name "lower_upper_case". It simply uppercases or lowercases a string in the Nginx config, and stores the result in another variable. In the above example I'm using it in to make the memcache keys case insensitive. To achieve this i just lowercase all keys on the Nginx, and also on the PHP side when it is filling the Memcache.
Both modules can be retrieved from github where I'm hosting the repositories:
https://github.com/replay/ngx_http_lower_upper_case
https://github.com/replay/ngx_http_php_session

3 comments:

  1. Really nice post.
    It's exacly what I'm in need for. there a 3 type of users here: guests, logged in and logged in vips.. so it seems there could be 3 different caches for each page.

    But I don't think this gonna be a problem for memcache. I'll see later, I'f I can handle these configurations.

    ReplyDelete
  2. This is a great idea to check the pages on the web server level. If the user is not logged in, then nginx can reject the request or forward it to the login page.

    ReplyDelete
  3. This is an useful module. Thanks. Do you plan to support this project ? If it works on the last nginx version ?

    ReplyDelete