diff --git README.txt README.txt
index c7229ff..31ca4c6 100644
--- README.txt
+++ README.txt
@@ -56,6 +56,17 @@ To learn more about parallelizing downloads by using subdomains (or CDNs!),
see http://drupal.org/project/parallel.
+Module compatibility
+--------------------
+This module has been verified to be compatible with the following modules:
+- CSS Gzip
+ (This module is obsolete when using Origin Pull mode with the Far Future
+ expiration setting enabled.)
+- ImageCache
+ (This module requires one of the patches that ships with the CDN module.)
+- Javascript Aggregator
+
+
Installation
------------
1) Note: skip this step if you want to rely on the fallback mechanism!
diff --git cdn.admin.inc cdn.admin.inc
index c424457..77f07c6 100644
--- cdn.admin.inc
+++ cdn.admin.inc
@@ -152,7 +152,7 @@ function cdn_admin_details_form(&$form_state) {
t("@format-url is the CDN URL that should be used. E.g.:
'http://cdn-a.com
'.", $format_variables),
t("@format-extensions is an optional
- setting to limit which files should be served from a CDN. E.g.:
+ setting to limit which file types should be served from a CDN. E.g.:
'.css .jpg .jpeg .png
'.", $format_variables),
);
$example_list_items = array(
@@ -181,6 +181,114 @@ function cdn_admin_details_form(&$form_state) {
'#dependency' => array('radio:' . CDN_MODE_VARIABLE => array(CDN_MODE_BASIC)),
);
+ $farfuture_extensions = variable_get(CDN_BASIC_FARFUTURE_EXTENSIONS_VARIABLE, CDN_BASIC_FARFUTURE_EXTENSIONS_DEFAULT);
+ $farfuture_variables = array(
+ '!extensions' => "." . implode("
, .", explode("\n", $farfuture_extensions)) . "
",
+ '!extensions-compressed' => "." . implode("
, .", explode("\n", CDN_BASIC_FARFUTURE_GZIP_EXTENSIONS)) . "
",
+ );
+ $form['settings'][CDN_BASIC_FARFUTURE_VARIABLE] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Far Future expiration'),
+ '#description' => t('Recommended for maximum performance boost!
+
+ Mark all files served from the CDN to expire in the
+ far future (decades from now). This is the same as
+ telling browsers to always use the cached
+ files. This can significantly speed up page loads.
+ Files are also automatically compressed (when the
+ browser supports it), to speed up page loads even
+ further.
+
+ Of course, you still want visitors to immediately get
+ new versions of files when they change. That is why
+ unique filenames are generated automatically.
+
+ For the experts: the following HTTP
+ headers are set: Expires
,
+ Cache-Control
,
+ Last-Modified
, Vary
, for
+ files with one of the following extensions: !extensions
+ and of these extensions, some will also be
+ automatically compressed: !extensions-compressed.', $farfuture_variables
+ ),
+ '#default_value' => variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT),
+ '#process' => array('ctools_dependent_process'),
+ '#dependency' => array('radio:' . CDN_MODE_VARIABLE => array(CDN_MODE_BASIC)),
+ );
+
+ $format_variables = array(
+ '@format-directory' => '<' . t('directory') . '>',
+ '@format-extensions' => '<' . t('extensions') . '>',
+ '@format-unique-identifier-method' => '<' . t('unique identifier method') . '>',
+ );
+
+ $methods = array();
+ $ufi_info = module_invoke_all('cdn_unique_file_identifier_info');
+ foreach ($ufi_info as $ufi) {
+ $methods[] = $ufi['label']
+ . ' (' . $ufi['machine_name'] . '
): '
+ . $ufi['description'];
+ }
+ $format_variables['!format-available-unique-identifier-methods'] = theme('item_list', $methods);
+ // '@format-available-unique-identifier-methods'
+
+
+ $format_explanation_list_items = array(
+ t("@format-directory is the directory (may include
+ wildcards) to which a unique identifier method will be applied. Multiple
+ directories may be listed, separated with semi-colons
+ (:). E.g.:
+ 'sites/*/modules/*:sites/*/themes/*
'.", $format_variables),
+ t("@format-extensions is an optional
+ setting to limit which file types should use this unique identifier
+ method. E.g.:
+ '.css .jpg .jpeg .png
'.", $format_variables),
+ t("@format-unique-identifier-method sets the unique
+ identifier method that should be applied to the aforementioned
+ directories, and only to (optionally) the listed file types. Available
+ methods are: !format-available-unique-identifier-methods.",
+ $format_variables),
+ );
+ $example_list_items = array(
+ t("This would generate a unique identifier for Drupal core files based on
+ the Drupal core version, files in the site directory would get unique
+ identifiers based on the last time they were modified and movie files
+ would not receive a unique identifier (they're so large browser can't
+ cache them anyway):
+
misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual
"),
+ t("In this second example, we're dealing with a more high-traffic website,
+ where it is too costly to access the filesystem for every served file.
+ Therefor, this site defines a CDN_DEPLOYMENT_ID
constant
+ somewhere in its codebase. This constant changes whenever a module or
+ theme changes. This is therefor far more efficient. See the last line:
+ misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual\nsites/*/modules/*:sites/*/themes/*|deployment_id
"),
+ );
+ $form['settings'][CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE] = array(
+ '#type' => 'textarea',
+ '#title' => t('Unique file identifier generation'),
+ '#description' => t("Define how unique file identifiers are generated.+ Note that if no unique file identifier generation + method is specified for a file because none of the + above rules apply to it, the CDN module will fall + back to the !default-ufi-method method. +
+ Sample mappings: + !example-list", $format_variables + array( + '!format-explanation-list' => theme('item_list', $format_explanation_list_items), + '!example-list' => theme('item_list', $example_list_items), + '!default-ufi-method' => '' . CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_DEFAULT . '
',
+ )),
+ '#size' => 35,
+ '#default_value' => variable_get(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT),
+ '#process' => array('ctools_dependent_process'),
+ '#dependency' => array('edit-cdn-farfuture-status' => array('1')),
+ );
+
+
//
// Advanced mode settings.
//
@@ -243,7 +351,6 @@ function cdn_admin_other_settings_form(&$form_state) {
'#type' => 'checkbox',
'#title' => t('CDN supports HTTPS'),
'#default_value' => variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE),
- '#process' => array('ctools_dependent_process'),
);
$path_explanation = t(
diff --git cdn.module cdn.module
index e5a018d..f2b205f 100644
--- cdn.module
+++ cdn.module
@@ -5,7 +5,6 @@
* Implementation of the core hooks, defines, public and private functions.
*/
-
define('CDN_DISABLED', 0);
define('CDN_TESTING', 1);
define('CDN_ENABLED', 2);
@@ -38,6 +37,16 @@ define('CDN_EXCEPTION_AUTH_USERS_BLACKLIST_DEFAULT', '');
// Variables for basic mode.
define('CDN_BASIC_MAPPING_VARIABLE', 'cdn_basic_mapping');
+define('CDN_BASIC_FARFUTURE_VARIABLE', 'cdn_farfuture_status');
+define('CDN_BASIC_FARFUTURE_DEFAULT', TRUE);
+define('CDN_BASIC_FARFUTURE_EXTENSIONS_VARIABLE', "cdn_farfuture_extensions");
+define('CDN_BASIC_FARFUTURE_EXTENSIONS_DEFAULT', "css\njs\nsvg\nico\ngif\njpg\njpeg\npng\notf\nttf\neot\nwoff\nflv\nswf");
+define('CDN_BASIC_FARFUTURE_GZIP_EXTENSIONS', "css\njs\nico\nsvg\neot\notf\nttf");
+define('CDN_BASIC_FARFUTURE_GZIP_DIRECTORY', "cdn/farfuture/gzip");
+define('CDN_BASIC_FARFUTURE_REMOVE_HEADERS', "Set-Cookie");
+define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE', 'cdn_farfuture_unique_identifier_mapping');
+define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT', "misc/*:modules/*:themes/*|drupal_version\nsites/*|mtime\nsites/*|.avi .m4v .mov .mp4 .wmv .flv|perpetual");
+define('CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_DEFAULT', 'mtime');
// Variables for advanced mode.
define('CDN_ADVANCED_SYNCED_FILES_DB_VARIABLE', 'cdn_advanced_synced_files_db');
@@ -58,6 +67,7 @@ define('CDN_DAEMON_PID_FILE', 'daemon.pid');
function cdn_file_url_alter(&$path) {
$status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
$mode = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
+ $farfuture = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
$stats = variable_get(CDN_STATS_VARIABLE, FALSE) && user_access(CDN_PERM_ACCESS_STATS);
$file_path_blacklist = variable_get(CDN_EXCEPTION_FILE_PATH_BLACKLIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_BLACKLIST_DEFAULT);
$file_path_whitelist = variable_get(CDN_EXCEPTION_FILE_PATH_WHITELIST_VARIABLE, CDN_EXCEPTION_FILE_PATH_WHITELIST_DEFAULT);
@@ -89,7 +99,7 @@ function cdn_file_url_alter(&$path) {
return;
}
}
-
+
// If the current Drupal path matches one of the blacklisted Drupal paths,
// return immediately.
if (drupal_match_path($_GET['q'], $drupal_path_blacklist)) {
@@ -106,6 +116,24 @@ function cdn_file_url_alter(&$path) {
$start = microtime();
}
+ // Alter the file path when using Origin Pull mode and using that mode's
+ // Far Future setting.
+ if ($mode == CDN_MODE_BASIC && $farfuture) {
+ $path = urldecode($path);
+ // If the file does not yet exist, perform a normal HTTP request to this
+ // file, to generate it. (E.g. when ImageCache is used, this will
+ // generate the derivative file.)
+ if (!file_exists($path)) {
+ drupal_http_request(url($path, array('absolute' => TRUE)));
+ }
+ // Generate a unique file identifier (UFI).
+ cdn_load_include('basic.farfuture');
+ $ufi = cdn_basic_farfuture_get_identifier($path);
+ // Generate the new path.
+ $path_before_farfuture = $path;
+ $path = "cdn/farfuture/$ufi/$path";
+ }
+
// Load the include file that contains the logic for the mode that's
// currently enabled.
cdn_load_include(($mode == CDN_MODE_BASIC) ? 'basic' : 'advanced');
@@ -151,7 +179,8 @@ function cdn_file_url_alter(&$path) {
// If the user can access it, add this to the per-page statistics.
if ($stats) {
$end = microtime();
- _cdn_devel_page_stats($path, $cdn_url, $server, $end - $start);
+ $source_path = ($mode == CDN_MODE_BASIC && $farfuture) ? $path_before_farfuture : $path;
+ _cdn_devel_page_stats($source_path, $cdn_url, $server, $end - $start);
}
// Override the path with the corresponding CDN URL, *if* the file is
@@ -159,10 +188,75 @@ function cdn_file_url_alter(&$path) {
if ($cdn_url !== FALSE) {
$path = $cdn_url;
}
+ else {
+ // If the file is not available on the CDN and Origin Pull mode's Far
+ // Future setting is enabled, then don't serve it using CDN module's
+ // non-scalable Far Future functionality, but using Drupal's default.
+ if ($mode == CDN_MODE_BASIC && $farfuture) {
+ $path = $path_before_farfuture;
+ }
+ }
}
}
/**
+ * Implementation of hook_cdn_unique_file_identifier_info().
+ */
+function cdn_cdn_unique_file_identifier_info() {
+ return array(
+ 'md5_hash' => array(
+ 'label' => t('MD5 hash'),
+ 'machine_name' => 'md5_hash',
+ 'prefix' => 'md5',
+ 'description' => t('MD5 hash of the file.'),
+ 'filesystem' => TRUE,
+ 'callback' => 'md5_file',
+ ),
+ 'mtime' => array(
+ 'label' => t('Last modification time'),
+ 'machine_name' => 'mtime',
+ 'prefix' => 'mtime',
+ 'description' => t('Last modification time of the file.'),
+ 'filesystem' => TRUE,
+ 'callback' => 'filemtime',
+ ),
+ 'perpetual' => array(
+ 'label' => t('Perpetual'),
+ 'machine_name' => 'perpetual',
+ 'prefix' => 'perpetual',
+ 'description' => t('Perpetual files never change (or are never cached
+ by the browser, e.g. video files).'),
+ 'filesystem' => FALSE,
+ 'value' => 'forever',
+ ),
+ 'drupal_version' => array(
+ 'label' => t('Drupal version'),
+ 'machine_name' => 'drupal_version',
+ 'prefix' => 'drupal',
+ 'description' => t('Drupal core version — this should only be applied
+ to files that ship with Drupal core.'),
+ 'filesystem' => FALSE,
+ 'value' => VERSION,
+ ),
+ 'deployment_id' => array(
+ 'label' => t('Deployment ID'),
+ 'machine_name' => 'deployment_id',
+ 'prefix' => 'deployment',
+ 'description' => t('A developer-defined deployment ID. Can be an
+ arbitrary string or number, as long as it uniquely
+ identifies deployments and therefore the affected
+ files.settings.php
as the
+ CDN_DEPLOYMENT_ID
+ constant, and it will be picked up instantaneously.'),
+ 'filesystem' => FALSE,
+ 'callback' => '_cdn_ufi_deployment_id',
+ ),
+ );
+}
+
+/**
* Implementation of hook_menu().
*/
function cdn_menu() {
@@ -214,6 +308,14 @@ function cdn_menu() {
'file' => 'cdn.stats.inc',
);
+ $items['cdn/farfuture'] = array(
+ 'title' => 'Download a far futured file',
+ 'access callback' => TRUE,
+ 'page callback' => 'cdn_basic_farfuture_download',
+ 'type' => MENU_CALLBACK,
+ 'file' => 'cdn.basic.farfuture.inc',
+ );
+
return $items;
}
@@ -251,7 +353,6 @@ function cdn_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
}
}
-
/**
* Implementation of hook_theme().
*/
@@ -422,6 +523,16 @@ function cdn_load_include($basename) {
// Private functions.
/**
+ * Callback for generating a unique file identifier.
+ *
+ * @param $path
+ * The file path to the file for which to generate a unique identifier.
+ */
+function _cdn_ufi_deployment_id($path) {
+ return CDN_DEPLOYMENT_ID;
+}
+
+/**
* Helper function to evaluate CDN_PICK_SERVER_PHP_CODE_VARIABLE, when that is
* being used instead of an actual cdn_pick_server() function.
*