A session is a mechanism for storing information across page visits for a specific user on a web site. Sessions are stored in a cookie and are assigned a unique ID, allowing the web site to access information in the cookie and associate that
information with a specific user. Once a session has been created, Drupal core and contributed modules may use that
session to store and retrieve information from the cookie as the user meanders around your site, without having to go backto the database to retrieve values.
Drupal utilizes sessions to store information about authenticated users and occasionally for anonymous users, in cases where
a module utilizes sessions to store context across page loads; other wise sessions are not generated for anonymous users.
Usage
Drupal uses sessions for several important functions internally to store transient information regarding an individual user’s
state or preferences. For example, drupal_set_message() needs to carry over a status message or an error message for the user from thepage on which the error occurred to the next page. This is done by storing the messages in an array named messages inside the user’s session:
/**
* Set a message that reflects the status of the performed operation.
*
* If the function is called with no arguments, this function returns all set
* messages without clearing them.
*
* @param $message
* The message should begin with a capital letter and always ends with a
* period '.'.
* @param $type
* The type of the message. One of the following values is possible:
* - 'status'
* - 'warning'
* - 'error'
* @param $repeat
* If this is FALSE and the message is already set, then the message won't
* be repeated.
*/
function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { if ($message) {
if (!isset($_SESSION['messages'][$type])) {
$_SESSION['messages'][$type] = array();}
if ($repeat || !in_array($message, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $message;}
// Mark this page as being not cacheable.
drupal_page_is_cacheable(FALSE);}
// Messages not set when DB connection fails.
return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL;}
Another example is from poll.module, where the session is used to prevent the user from hitting the page cache:
if (!$user->uid) {
// The vote is recorded so the user gets the result view instead of the
// voting form when viewing the poll. Saving a value in $_SESSION has the
// convenient side effect of preventing the user from hitting the page
// cache. When anonymous voting is allowed, the page cache should
// contain only the voting form, not the results.
$_SESSION['poll_vote'][$node->nid] = $choice;}
Drupal also uses sessions to keep a handle on file uploads when a node is being previewed, to remember viewing
preferences when filtering the list of site content or the list of recent log entries at Reports -> Recent log entries, and for
the installation and update systems (install.php and update.php).
Drupal creates sessions for users that are logged into a site (authenticated users). In the row of the sessions table
representing an anonymous user, the uid column is set to 0. Because sessions are browser-specific (they’re tied to the
browser’s cookie),having multiple browsers open on a single computer results in multiple sessions.
The actual data stored in a session is stored as serialized data in the session column of the sessions table. Two rows of a
typical sessions table are shown in Table 17-1. The table shows records for the superuser (uid 1), an authenticated user
(uid 3), and ananonymous user (uid 0). The superuser has watchdog filtering settings (used by the dblog module) stored inthe session.
Table 17-1. Example Rows from
a typical Sessions
table
|
||||||
uid
|
sid
|
ssid
|
hostname
|
timestamp
|
cache
|
session
|
1
|
11diqzKTOnxI_zlUs9jDlLIzlrA xgGmM3l0oo-ux7Ws
|
NULL
|
1.2.3.4
|
120846400
|
106
|
dblog_overview
_filter|a:0:{}
|
3
|
1WgbUe5UGP26vIY8wkxbkF A8odnqoy4x6bC7uV8lss
|
NULL
|
5.6.7.8
|
1208460845
|
0
|
--
|
The sessions table is cleaned when PHP’s session garbage collection routine runs. The length of time a row remains in the table is
determined by the session.gc_maxlifetime setting in settings.php. If a user logs out, the row for that session is removed from the database immediately. Note that if a user is logged in via multiple browsers (not browser windows) or multiple IP addresses at the
same time, each browser has a session; therefore logging out from one browser doesn’t log the user out from the other browsers.
Session-Related Settings
There are three places where Drupal modifies session-handling settings: in the .htaccess file, in the settings.php file, and in the
bootstrap code in the includes/bootstrap.inc file.
In .htaccess
Drupal ensures that it has full control over when sessions start by turning off PHP’s session.auto_start functionality in the Drupal
installation’s default .htaccess file with the following line:
php_flag session.auto_start false
session.auto_start is a configuration option that PHP cannot change at runtime, which is why it lives in the .htaccess file instead of
settings.php.
In settings.php
You’ll set most session settings within the settings.php file, located at sites/default/settings.php or sites/example.com/settings.php.
ini_set('session.gc_probability', 1); ini_set('session.gc_divisor', 100); ini_set('session.gc_maxlifetime', 200000); ini_set
('session.cookie_lifetime', 2000000);
Having these settings in settings.php instead of .htaccess allows subsites to have different settings and allows Drupal to modify the
session settings on hosts running PHP as a CGI (PHP directives in .htaccess don’t work in such a configuration).
Drupal uses the ini_set('session.save_handler', 'user'); function to override the default session handling provided by PHP and
implement its own session management; user-defined in this context means “defined by Drupal”(see www.php.net/manual
In bootstrap.inc
Session settings included in bootstrap.inc are:
ini_set('session.use_cookies', '1'); ini_set('session.use_only_cookies', '1'); ini_set('session.use_trans_sid', '0');
// Don't send HTTP headers using PHP's session handler.
ini_set('session.cache_limiter', 'none');
// Use httponly session cookies.
ini_set('session.cookie_httponly', '1');
PHP provides built-in session-handling functions but allows you to override those functions if you want to implement your own
handlers, such as storing sessions in memcache or MongoDB instead of MySQL. PHP continues to handle the cookiemanagement,
while Drupal’s implementation does the back-end handling of session storage.
The call to drupal_session_initialize() during the DRUPAL_BOOTSTRAP_SESSION phase of bootstrapping sets the handlers to functions in includes/sessions.inc and starts session handling:
case DRUPAL_BOOTSTRAP_SESSION:
require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc');
drupal_session_initialize(); break;
The drupal_session_initialize() function within session.inc sets the handlers to the following values:
session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
Notice that the file being included in bootstrap.inc is defined by a Drupal variable named session_inc. This means that you can
cleanly implement your own session handling and plug in that instead of using Drupal’s default session handling. For example,the
memcache module (drupal.org/project/memcache) implements the _drupal_session_open(), _drupal_session_close(),
_drupal_session_read(), _drupal_session_write(), _drupal_session_destroy(), and _drupal_session_garbage_collection() session-related functions. Setting the session_inc Drupal variable causes Drupal to use this code for sessions instead of using default session handling:'session_inc' => './sites/all/modules/memcache/memcache-session.inc',
You could also override the variable by setting it in your settings.php file:
$conf = array('session_inc' => './sites/all/modules/memcache/memcache-session.inc,...);
Requiring Cookies
If the browser doesn’t accept cookies, a session cannot be established because the PHP directive sessions_use_only_cookies
has been set to 1 and the alternative (passing the PHPSESSID in the query string of the URL) has been disabled by setting
sessions.use_trans_sid to 0. This is a best practice, as recommended by Zend (see http://php.net/session.configuration):
URL-based sessi on management has additional s ecurity risks c ompared t o cookie- based session management.
Users m ay send a URL that contains an active session ID to their fri ends by e-mail or users may save aURL
that contains a session ID t o their bookmarks and access your site with the same session ID always, for example.
When PHPSESSID appears in the query string of a site, it’s typically a sign that the hosting provider has locked down PHP and
doesn’t allow the ini_set() function to set PHP directives at runtime. Alternatives are to move the settings into the.htaccess file (if
the host is running PHP as an Apache module) or into a local php.ini file (if the host is running PHP as a CGI executable).
To discourage session hijacking (where someone grabs a session ID out of an old cookie and attempts to reuse that session ID—see http://en.wikipedia.org/wiki/Session_hijacking), the session ID is regenerated when a user logs in (see theuser_login_
finalize() function in modules/user/user.module). The session is also regenerated when a user changes his or her password.
Storage
Session information is stored in the sessions table, which associates session IDs with Drupal user IDs during the DRUPAL_BOOTSTRAP_SESSION phase of bootstrapping (see Chapter 16 to learn more about Drupal’s bootstrapping process). In fact, the $user
object, which is used extensively throughout Drupal, is first built during this phase by _drupal_session_read() in includes/sessions.
inc (see Chapter 6 to see how the $user object is built). Table 17-2 shows the table structure in which sessions are stored.
Table 17-2. The Structure of the sessions Table
|
|||
Field
|
Type
|
Length
|
Description
|
uid
|
int, unsigned
|
10
|
User ID of authenticated user (0 for anonymous user)
|
sid
|
varchar
|
128S
|
Session ID generated by PHP
|
ssid
|
varchar
|
128
|
Secure session ID generated by PHP
|
hostname
|
varchar
|
128
|
IP address that last used this session ID
|
timestamp
|
int
|
11
|
Unix timestamp of last page request
|
cache
|
int
|
11
|
Time of user’s last
post, which is used to enforce minimum cache lifetime
|
session
|
long
|
blob
|
Serialized contents
of data stored in $_SESSION
|
When Drupal serves a page, the last task completed is to write the session to the sessions table (see_drupal_session_write() in
includes/session.inc). This is done only if the browser has presented a valid cookie to avoid bloating the sessions table with
sessions for web crawlers or if a module has stored data in $_SESSION.
Session Life Cycle
The session life cycle begins (see http://api.drupal.org/api/function/drupal_session_initialize/7) with a check to see if a session
cookie exists, and if so, to initialize the session, otherwise the session is started on demand only when something needs to be stored
in a session. This approach allows anonymous users to browse a site without the need of a session cookie unless they perform an
operation, like submitting a form, that requires a session. This allows Drupal to serve up cached pages from a reverse proxy server,
like Varnish, for anonymous users. Drupal does, however, create a unique session identifier even if a session cookie isn’t required, in preparation for cases where a session will be needed. An example would be the case where a module called drupal_get_token()—this function needs to know the session ID in advance of the session being generated.
If a session is required, Drupal checks the sessions table for the existence of a row with the session ID as the key. If found, the
_drupal_session_read() function in includes/sessions.inc retrieves the session data and performs an SQL JOIN on the row from
the sessions table and on the corresponding row from the users table. The result of this join is anobject containing all fields and
values from both rows. This is the global $user object that’s used throughout the rest of Drupal (see Chapter 6). Thus, session datais also available by looking in the $user object, specifically in $user->session, $user->sid, $user->hostname, $user->timestamp,
and -$user->cache. Roles for the current user are looked up and assigned to $user->roles in _drupal_session_read() as well.
But what happens if there’s no user in the users table with a user ID that matches the user ID in the session? This is a trick
question. Because Drupal’s installer creates a row in the users table with the user ID of 0, and because unauthenticated
(anonymous) users are assigned the uid of 0 in the sessions table, the join always works.
If you want to find out the last time the user accessed a page, you could look at either $user->timestamp, which is based on
the time stamp recorded in the sessions table or $user->access, which is kept in the users table. Of the two, $user->timestamp
will give you more accurate results if it is present, because updating $user->access in the userstable is subject to throttling so that
writes do not happen more often than every 180 seconds by default. This value can be changed by setting the Drupal variable
session_write_interval, that can be found in the _drupal_session_write() function inincludes/session.inc:
// Last access time is updated no more frequently than once every 180 seconds.
// This reduces contention in the users table.
if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) {
db_update('users')->fields(array('access' => REQUEST_TIME))->condition('uid', $user->uid)->execute();}
Of course, neither $user->login nor $user->access will be present for users visiting for the first time or for anonymous users without a session, as no time stamp has been saved yet.
When the web page has been delivered to the browser, the last step is to close the session. PHP invokes the _drupal_session_
write() function in includes/session.inc, which writes anything that was stashed in $_SESSION (during the request) to the sessions
table. It is a good idea to store data in $_SESSION only if you absolutely need to.
Session Conversations
Here are some examples of what happens when you visit Drupal in your browser, from a sessions perspective.
First Visit
Browser: Hi, I’d like a page, please.
Drupal: May I see your cookie?
Browser: Sorry, I don’t have a cookie; this is my first time here.
Drupal: Here’s the page you requested.
Browser: My user did something that generated a message.
Drupal: Ok, I’ll create a session and store the message in the session (cookie).
Here it is.
Browser: Thanks for the cookie.
Second Visit
Browser: May I have another page, please?
Drupal: May I see your cookie?
Browser: Right here. It says session number 6tc47s8jd6rls9cugkdrrjm8h5.
Drupal: Hmm, I can’t find you in my records. But here’s your page anyway. I’ll make a note of you in case you visit again.
User with an Account
[The user has created an account and clicked the Log In button.] Browser: Hi, I’d like a page, please.
Drupal: May I see your cookie?
Browser: Right here. It says session number 31bfa29408ebb23239042ca8f0f77652.
Drupal: Hi, Joe! [Mumbling] You’re user ID 384, and you like your comments nested and your coffee black. Here’s a new
cookie so your session doesn’t get hijacked. I’ll make a note that you visited. Have a nice day.
Common Tasks
Here are some common ways in which you might want to use sessions or tweak session settings.
Changing the Length of Time Before a Cookie Expires
The length of time before the cookie containing the session ID expires is controlled by session.cookie_lifetime in settings.php and set by default to 2,000,000 seconds (about 23 days). Modifying this value to 0 causes the cookie to be destroyed when theuser
closes the browser.
Changing the Name of the Session
A common problem with sessions arises when deploying web sites on multiple subdomains. Because each site uses the same default value for session.cookie_domain and the same session.name of PHPSESSID by default, users find themselves able to log intoonlyone site at any given time. Drupal solves this problem by creating a unique session name for each site.
The session name is based on a sha-256 hash, with some modifications, of the base URL for the site.
The automatic generation of the session name can be bypassed by uncommenting a line in settings.php and specifying the value of
the $cookie_domain variable. The value should contain alphanumeric characters only. Here is the relevant section of settings.php:
/**
* Drupal automatically generates a unique session cookie name for each site
* based on its full domain name. If you have multiple domains pointing at
* the same Drupal site, you can either redirect them all to a single domain
* (see comment in .htaccess), or uncomment the line below and specify their
* shared base domain. Doing so assures that users remain logged in as they
* cross between your various domains.
*/
# $cookie_domain = 'example.com';
Storing Data in the Session
Storing data in a user’s session is convenient, because the data is automatically stored by the sessions system. Whenever you want
to store data that you want to associate with a user during a visit (or multiple visits up to session.cookie_lifetime), use the
$_SESSION superglobal: $_SESSION['favorite_color'] = $favorite_color;
Later, on a subsequent request, do the following to retrieve the value:
$favorite_color = $_SESSION['favorite_color'];
Summary
After reading this chapter, you should be able to
• Understand how Drupal modifies PHP’s session handling.
• Understand which files contain session configuration settings.
• Understand the session life cycle and how Drupal’s $user object is created during a request.
• Store data in and retrieve data from a user’s session.
Không có nhận xét nào:
Đăng nhận xét