Drupal 7 bootstrap explained: DRUPAL_BOOTSTRAP_PAGE_HEADER

In the previous part of this series I covered DRUPAL_BOOTSTRAP_SESSION, next up is DRUPAL_BOOTSTRAP_PAGE_HEADER. _drupal_bootstrap_page_header() is the function that gets called when we get into this phase.

<?php
  bootstrap_invoke_all
('boot');

  if (!drupal_is_cli()) {
   
ob_start();
   
drupal_page_header();
  }
}
?>

It starts by invoking hook_boot. That's all there is to do if we're in command line mode, otherwise we continue by turning on PHP's output buffering and jumping in to drupal_page_header().

<?php
function drupal_page_header() {
 
$headers_sent = &drupal_static(__FUNCTION__, FALSE);
  if (
$headers_sent) {
    return
TRUE;
  }
 
$headers_sent = TRUE;

  $default_headers = array(
   
'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT',
   
'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME),
   
'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0',
   
'ETag' => '"' . REQUEST_TIME . '"',
  );
 
drupal_send_headers($default_headers);
}
?>

This function creates the default HTTP response header and passes it to drupal_send_headers(), unless it has already sent them during this request and set the static variable $headers_sent to TRUE. The set header defaults are for authenticated users and ensure that the page won't be cached. If you have your page cache turned off they will get served for all anonymous requests as well. They include an unique ETag to work around an old Firefox bug, you can read the long explanation from Drupal documentation if you're interested in the details of that. This workaround is probably getting removed in Drupal 8.

<?php
/**
 * Sends the HTTP response headers that were previously set, adding defaults.
 *
 * Headers are set in drupal_add_http_header(). Default headers are not set
 * if they have been replaced or unset using drupal_add_http_header().
 *
 * @param $default_headers
 *   An array of headers as name/value pairs.
 * @param $single
 *   If TRUE and headers have already be sent, send only the specified header.
 */
function drupal_send_headers($default_headers = array(), $only_default = FALSE) {
 
$headers_sent = &drupal_static(__FUNCTION__, FALSE);
 
$headers = drupal_get_http_header();
  if (
$only_default && $headers_sent) {
   
$headers = array();
  }
 
$headers_sent = TRUE;
?>

Entering drupal_send_headers() we call drupal_get_http_header() which just fetches all the drupal HTTP headers from the static variable 'drupal_http_headers' and returns them when called without any parameters. The function sets only the headers passed as the first parameter instead of all in 'drupal_http_headers' if the second parameter is TRUE and it sees from a static variable that previous ones have already been set. So if you call it with $only_default as TRUE and it is the first time this function is called, all headers will always get set with header(). After the first call only the ones passed will get set when $only_default is TRUE.

'drupal_http_headers' is manipulated with drupal_add_http_header() which also calls _drupal_set_preferred_header_name() with the header name as a parameter. That function adds the name as value with lower-cased version of the name as key to a static array. This seems to ensure that several versions of the same header with different casing don't get added and unsetting works with all variations. These values are also fetched in drupal_send_headers() by calling _drupal_set_preferred_header_name() with no parameters. This code got added to let drupal_add_http_header() support unsetting headers. Unfortunately it doesn't even seem to work as intended because drupal_add_http_header() calls drupal_send_headers() for each header that gets added and this causes all the headers to be set with PHP's header() and skipping them later will not help. Now that Drupal 8 will require PHP 5.3 this functionality can be provided by header_remove() and it should be possible to simplify this function quite a bit.

<?php

  $header_names = _drupal_set_preferred_header_name();
  foreach (
$default_headers as $name => $value) {
   
$name_lower = strtolower($name);
    if (!isset(
$headers[$name_lower])) {
     
$headers[$name_lower] = $value;
     
$header_names[$name_lower] = $name;
    }
  }
  foreach (
$headers as $name_lower => $value) {
    if (
$name_lower == 'status') {
     
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value);
    }
   
// Skip headers that have been unset.
   
elseif ($value) {
     
header($header_names[$name_lower] . ': ' . $value);
    }
  }
}
?>

Next the array of lower-case header names that was discussed above is retrieved from _drupal_set_preferred_header_name(). The default headers about to be sent are looped over and if any of them have not been overridden in the array of set headers already (keyed by the lower case name), they get added to both the header array and the header names array. The HTTP status header requires special handling, the protocol & version in use is fetched from $_SERVER['SERVER_PROTOCOL'] when header() is called. Others are just passed to header() prefixed with the name and a colon to form a proper HTTP header if the value is not FALSE which drupal_add_http_header() uses to signify that the header is to be unset.

After the headers are set, DRUPAL_BOOTSTRAP_PAGE_HEADER is done. The bootstrap phase isn't too complicated and hopefully it will be even simpler with Drupal 8 as old workarounds get removed.

Tagit: 

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Fill in the blank.