导航

Drupal启动阶段之一:配置

Posted on 2013-10-08 11:19  eastson  阅读(585)  评论(0编辑  收藏  举报

配置是Drupal启动过程中的第一个阶段,通过函数_drupal_bootstrap_configuration()实现:

function _drupal_bootstrap_configuration() {
  set_error_handler('_drupal_error_handler');
  set_exception_handler('_drupal_exception_handler');

  drupal_environment_initialize();
  timer_start('page');
  drupal_settings_initialize();
}

 

错误和异常处理

Drupal实现了自己的错误处理器_drupal_error_handler()和异常处理器_drupal_exception_handler(),抽时间可以研究下具体如何实现的。

set_error_handler('_drupal_error_handler');
set_exception_handler('_drupal_exception_handler');

 

初始化环境

初始化环境通过函数drupal_environment_initialize()实现。首先,对$_SERVER中的一些变量检查和修改,保证后续需要用到时都是处理过的:

if (!isset($_SERVER['HTTP_REFERER'])) {
  $_SERVER['HTTP_REFERER'] = '';
}
if (!isset($_SERVER['SERVER_PROTOCOL']) || ($_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.0' && $_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.1')) {
  $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0';
}

if (isset($_SERVER['HTTP_HOST'])) {
  // As HTTP_HOST is user input, ensure it only contains characters allowed
  // in hostnames. See RFC 952 (and RFC 2181).
  // $_SERVER['HTTP_HOST'] is lowercased here per specifications.
  $_SERVER['HTTP_HOST'] = strtolower($_SERVER['HTTP_HOST']);
  if (!drupal_valid_http_host($_SERVER['HTTP_HOST'])) {
    // HTTP_HOST is invalid, e.g. if containing slashes it may be an attack.
    header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
    exit;
  }
}
else {
  // Some pre-HTTP/1.1 clients will not send a Host header. Ensure the key is
  // defined for E_ALL compliance.
  $_SERVER['HTTP_HOST'] = '';
}

 

再次,检查用户请求的路径,放到$_GET['q']里面:

$_GET['q'] = request_path();

函数request_path()需要判断请求URL中是否有包含?q=XXX这样的查询参数。有时候为了让搜索引擎友好,会重写URL,也会出现http://example.com/node/306这样的情况。

function request_path() {
  static $path;

  if (isset($path)) {
    return $path;
  }

  if (isset($_GET['q']) && is_string($_GET['q'])) {
    // This is a request with a ?q=foo/bar query string. $_GET['q'] is
    // overwritten in drupal_path_initialize(), but request_path() is called
    // very early in the bootstrap process, so the original value is saved in
    // $path and returned in later calls.
    $path = $_GET['q'];
  }
  elseif (isset($_SERVER['REQUEST_URI'])) {
    // This request is either a clean URL, or 'index.php', or nonsense.
    // Extract the path from REQUEST_URI.
    // $_SERVER['REQUEST_URI'] = /drupal/index.php?debug_host=10.0.2.15&debug_port=10137&debug_session_id=1000这样
    // 进过strtok()去掉问号的内容后 $request_path = /drupal/index.php
    $request_path = strtok($_SERVER['REQUEST_URI'], '?');
    
    // $_SERVER['SCRIPT_NAME'] = /drupal/index.php
    // $base_path_len = strlen(/drupal)
    $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/'));
    
    // Unescape and strip $base_path prefix, leaving q without a leading slash.
    // $path = index.php
    $path = substr(urldecode($request_path), $base_path_len + 1);
    
    // If the path equals the script filename, either because 'index.php' was
    // explicitly provided in the URL, or because the server added it to
    // $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some
    // versions of Microsoft IIS do this), the front page should be served.
    // $_SERVER['PHP_SELF'] = /drupal/index.php
    if ($path == basename($_SERVER['PHP_SELF'])) {
      $path = '';
    }
    
    // $_SERVER['REQUEST_URI'] = /drupal/node/306
    //    $request_path = /drupal/node/306
    //    $base_path_len = 7, 不变
    //    $path = node/306
  }
  else {
    // This is the front page.
    $path = '';
  }

  // Under certain conditions Apache's RewriteRule directive prepends the value
  // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing
  // slash in place, hence we need to normalize $_GET['q'].
  $path = trim($path, '/');

  return $path;
}

 

然后,修改了一些PHP设置。需要注意一下Drupal使用了cookie来传递会话ID,避免了将会话ID放在查询参数里面,导致请求URI太长:

// Don't escape quotes when reading files from the database, disk, etc.
ini_set('magic_quotes_runtime', '0');
// Use session cookies, not transparent sessions that puts the session id in
// the query string.
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');

 

初始化设置

初始化设置是通过函数drupal_settings_initialize()完成的。这里的设置是指Drupal的settings.php文件。需要注意的是,这里许多变量都做了global声明,也就是说这些变量在Drupal都是全局的。

global $base_url, $base_path, $base_root;

// Export these settings.php variables to the global namespace.
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url;


首先,直接导入settings.php文件:

if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
    include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php';
}

conf_path()用来处理多站点的情形,具体可以参看《Drupal配置文件settings.php搜索规则》。
settings.php文件必须申明数据库连接信息$databases。另外的,也可以设置Drupal配置变量。在settings.php文件中设置的配置变量具有最高优先级,会覆盖掉后台数据库variable表中的变量,这些变量可以通过variable_get()和variable_set()访问,可以参考《Drupal如何处理系统变量》。例如,可以在settings.php设置代理信息:

$conf['proxy_server'] = '';
$conf['proxy_port'] = 8080;
$conf['proxy_username'] = '';
$conf['proxy_password'] = '';
$conf['proxy_user_agent'] = '';
$conf['proxy_exceptions'] = array('127.0.0.1', 'localhost');


然后,处理各种与URL有关的信息:

$is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on';

if (isset($base_url)) {
  // Parse fixed base URL from settings.php.
  $parts = parse_url($base_url);
  if (!isset($parts['path'])) {
    $parts['path'] = '';
  }
  $base_path = $parts['path'] . '/';
  // Build $base_root (everything until first slash after "scheme://").
  $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path']));
}
else {
  // Create base URL.
  $http_protocol = $is_https ? 'https' : 'http';
  
  // http://localhost
  $base_root = $http_protocol . '://' . $_SERVER['HTTP_HOST'];

  $base_url = $base_root;

  // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
  // be modified by a visitor.
  if ($dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')) {
    $base_path = $dir;
    $base_url .= $base_path;
    $base_path .= '/';
  }
  else {
    $base_path = '/';
  }
  
  // $base_path = /drupal/
  // $base_url = http://localhost/drupal/
}

$base_secure_url = str_replace('http://', 'https://', $base_url);
$base_insecure_url = str_replace('https://', 'http://', $base_url);


最后,处理Cookie和会话有关的信息:

if ($cookie_domain) {
  // If the user specifies the cookie domain, also use it for session name.
  $session_name = $cookie_domain;
}
else {
  // Otherwise use $base_url as session name, without the protocol
  // to use the same session identifiers across HTTP and HTTPS.
  list( , $session_name) = explode('://', $base_url, 2);
  // 最后的会话Cookie名称是 session_name('SESS' . substr(hash('sha256', 'localhost/drupal'), 0, 32))
  
  // HTTP_HOST can be modified by a visitor, but we already sanitized it
  // in drupal_settings_initialize().
  if (!empty($_SERVER['HTTP_HOST'])) {
    $cookie_domain = $_SERVER['HTTP_HOST'];
    // Strip leading periods, www., and port numbers from cookie domain.
    $cookie_domain = ltrim($cookie_domain, '.');
    if (strpos($cookie_domain, 'www.') === 0) {
      $cookie_domain = substr($cookie_domain, 4);
    }
    $cookie_domain = explode(':', $cookie_domain);
    $cookie_domain = '.' . $cookie_domain[0];
  }
}
// Per RFC 2109, cookie domains must contain at least one dot other than the
// first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
  ini_set('session.cookie_domain', $cookie_domain);
}
// To prevent session cookies from being hijacked, a user can configure the
// SSL version of their website to only transfer session cookies via SSL by
// using PHP's session.cookie_secure setting. The browser will then use two
// separate session cookies for the HTTPS and HTTP versions of the site. So we
// must use different session identifiers for HTTPS and HTTP to prevent a
// cookie collision.
if ($is_https) {
  ini_set('session.cookie_secure', TRUE);
}
$prefix = ini_get('session.cookie_secure') ? 'SSESS' : 'SESS';
session_name($prefix . substr(hash('sha256', $session_name), 0, 32));

这里说明一下,Drupal的会话Cookie名称使用了SESS前缀再加上URL路径的形式:

session_name('SESS' . substr(hash('sha256', 'localhost/drupal'), 0, 32))

透过Chrome浏览器可以很直观地看到这一点: