Writing Custom Session HandlersWe 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:
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:
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:
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():
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:
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:
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. |