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.
+
+ Enter one rule per line, in the format @format-directory[|@format-extensions]|@format-unique-identifier-method:
+ !format-explanation-list +

+ 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.
+ Define this deployment ID in any enabled module or + in 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. *