Приглашаем посетить
Чулков (chulkov.lit-info.ru)

Writing Custom Session Handlers

Previous
Table of Contents
Next

Writing Custom Session Handlers

We have already discussed the session API from a userspace level, in Chapter 14, "Session Handling." In addition to being able to register userspace handlers for session management, you can also write them in C and register them directly by using the session extension.

This section provides a quick walkthrough of how to implement a C-based session handler, using a standard DBM file as a backing store.

The session API is extremely simple. At the most basic level, you simply need to create a session module struct (similar in concept to the zend_module_entry structure). You first create a standard extension skeleton. The extension will be named session_dbm. The session API hooks can be separately namespaced; you can call them all dbm for simplicity.

The session API hook structure is declared as follows:

#include "ext/session/php_session.h"
ps_module ps_mod_dbm = {
  PS_MOD(dbm)
};

The PS_MOD() macro automatically registers six functions that you need to implement:

  • [PS_OPEN_FUNC(dbm)] Opens the session back end.

  • [PS_CLOSE_FUNC(dbm)] Closes the session back end.

  • [PS_READ_FUNC(dbm)] Reads data from the session back end.

  • [PS_WRITE_FUNC(dbm)] Writes data to the session back end.

  • [PS_DESTROY_FUNC(dbm)] Destroys a current session.

  • [PS_GC_FUNC(dbm)] Handles garbage collection.

For further details on the tasks these functions perform and when they perform them, refer to the discussion of their userspace equivalents in Chapter 14.

PS_OPEN_FUNC passes in three arguments:

  • void **mod_data A generic data pointer used to hold return information.

  • char *save_path A buffer to hold the filesystem path where session data will be saved. If you are not using file-based sessions, this should be thought of as a generic location pointer.

  • char *session_name The name of the session.

mod_data is passed and propagated along with a session and is an ideal place to hold connection information. For this extension, you should carry the location of the DBM file and a connection pointer to it, using this data structure:

typedef struct {
  DBM *conn;
  char *path;
} ps_dbm;

Here is PS_OPEN_FUNC, which does not do much other than initialize a ps_dbm struct and pass it back up to the session extension in mod_data:

PS_OPEN_FUNC(dbm)
{
  ps_dbm *data;

  data = emalloc(sizeof(ps_dbm));
  memset(data, 0, sizeof(ps_dbm));
  data->path = estrndup(save_path, strlen(save_path));
  *mod_data = data;
  return SUCCESS;
}

PS_CLOSE_FUNC() receives a single argument:

void **mod_data;

This is the same mod_data that has existed through the request, so it contains all the relevant session information. Here is PS_CLOSE(), which closes any open DBM connections and frees the memory you allocated in PS_OPEN():

PS_CLOSE_FUNC(dbm)
{
  ps_dbm *data = PS_GET_MOD_DATA();

  if(data->conn) {
    dbm_close(data->conn);
    data->conn = NULL;
  }
  if(data->path) {
    efree(data->path);
    data->path = NULL;
  }
  return SUCCESS;
}

PS_READ_FUNC() takes four arguments:

  • void **mod_data The data structure passed through all the handlers.

  • const char *key The session ID.

  • char **val An out-variable passed by reference. The session data gets passed back up in this string.

  • int *vallen The length of val.

In the following code, PS_READ_FUNC() opens the DBM if it has not already been opened and fetches the entry keyed with key:

PS_READ_FUNC(dbm)
{
  datum dbm_key, dbm_value;

  ps_dbm *data = PS_GET_MOD_DATA();
  if(!data->conn) {
    if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) {
      return FAILURE;
    }
  }
  dbm_key.dptr = (char *) key;
  dbm_key.dsize = strlen(key);
  dbm_value = dbm_fetch(data->conn, dbm_key);
  if(!dbm_value.dptr) {
    return FAILURE;
  }
  *val = estrndup(dbm_value.dptr, dbm_value.dsize);
  *vallen = dbm_value.dsize;
  return SUCCESS;
}

datum is a GDBM/NDBM type used to store key/value pairs. Note that the read mechanism does not have to know anything at all about the type of data being passed through it; the session extension itself handles all the serialization efforts.

PS_WRITE_FUNC() is passed arguments similar to those passed to PS_READ_FUNC():

  • void **mod_data The data structure passed through all the handlers.

  • const char *key The session ID.

  • const char *val A string version of the data to be stored (the output of the serialization method used by the session extension).

  • int vallen The length of val.

PS_WRITE_FUNC() is almost identical to PS_READ_FUNC(), except that it inserts data instead of reading it:

PS_WRITE_FUNC(dbm)
{
  datum dbm_key, dbm_value;

  ps_dbm *data = PS_GET_MOD_DATA();
  if(!data->conn) {
    if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) {
      return FAILURE;

    }
  }
  dbm_key.dptr = (char *)key;
  dbm_key.dsize = strlen(key);
  dbm_value.dptr = (char *)val;
  dbm_value.dsize = vallen;

  if(dbm_store(data->conn, dbm_key, dbm_value, DBM_REPLACE) != 0) {
    return FAILURE;
  }
  return SUCCESS;
}

PS_DESTROY_FUNC() takes two arguments:

  • void **mod_data The data structure passed through all the handlers.

  • const char *key The session ID to be destroyed.

The following function simply calls dbm_delete to delete the key in question:

PS_DESTROY_FUNC(dbm)
{
  datum dbm_key;
  ps_dbm *data = PS_GET_MOD_DATA();

  if(!data->conn) {
    if((data->conn = dbm_open(data->path, O_CREAT|O_RDWR, 0640)) == NULL) {
      return FAILURE;
    }
  }
  dbm_key.dptr = (char *)key;
  dbm_key.dsize = strlen(key);
  if(dbm_delete(data->conn, dbm_key)) {
    return FAILURE;
  }
  return SUCCESS;
}

Finally, PS_GC_FUNC() takes three arguments:

  • void **mod_data The data structure passed through all the handlers.

  • int maxlifetime The configured maximum lifetime for a session.

  • int *nrdels An out-variable that holds the number of expired sessions.

As described in Chapter 10, "Data Component Caching," data expiration in a DBM file is complex. You could encode the modification time in the records you insert in PS_READ_FUNC() and PS_WRITE_FUNC(). The implementation of that is left to you as an exercise. This particular garbage collection function simply returns success:

PS_GC_FUNC(dbm)
{
  return SUCCESS;
}

To actually make this extension available to use, you need to register it not only with PHP but with the session extension itself. To do this, you call php_session_register_module() from within your MINIT function, like so:

PHP_MINIT_FUNCTION(session_dbm)
{
  php_session_register_module(&ps_mod_dbm);
  return SUCCESS;
}

Now you can now set the new handler in the php.ini file, like this:

session.save_handler=dbm

Because many sites are session heavy (meaning that sessions are used on most, if not all, pages), the session-backing implementation is a common source of overhead, especially when userpsace session handlers are used. That, combined with the simplicity of the API, makes using custom C session handlers an easy way to extract a nice performance gain.


Previous
Table of Contents
Next