Suffix cache

Derek Jones edited this page Jul 5, 2012 · 6 revisions
Clone this wiki locally

[size=2]Updated to work with the new Output library from CI 1.7svn[/size]

Josh Surber's Suffix forking and header caching via his forum post.

[size=5]$this->uri->suffix(string)[/size]

This function grabs the suffix from the URI, allowing you to (for example) serve differant page formats when a page is requested with differant suffixes. For example, /blog/entries.html would format your page as HTML but /blog/entries.rss would be your RSS feed.

You can also manually set your URI suffix, which can then be used (for example) to send different output formats based on the requested URL.

$suffix = $this->uri->suffix();

You can pass in a default value to be returned if there is no URI suffix:

$suffix = $this->uri->suffix('html');

[size=5]Header caching[/size]

When a page is loaded for the first time, the cache file will be written to your system/cache folder. On subsequent page loads the cache file will be retrieved and sent to the requesting user's browser, along with any headers set via $this->output->set_header(). If it has expired, it will be deleted and refreshed before being sent to the browser.

Note: Headers sent via a direct header() call are not cached.

[size=4]MY_URI[/size]


<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * MY_URI Class
 *
 * Adds a suffix getter and setter.  Intended to be used in conjunction
 * with MY_Output to support headers for cached files.  Woot!
 *
 * @package        CodeIgniter
 * @subpackage    Libraries
 * @category    URI
 * @author        Josh Surber
 * @transcriber Bradford Mar
 * @link        http://codeigniter.com/forums/viewthread/76260/
 */
class MY_URI extends CI_URI {
    
    var $suffix;

    // --------------------------------------------------------------------
    
    /**
     * Remove the suffix from the URL if needed
     *
     * @access    private
     * @return    void
     */    
    function _remove_url_suffix()
    {
        /*
         * Pulls between 1 and 5 characters from the end of the URI string,
         * if they follow a period. The uri_string var is stripped of these 
         * characters (including the period), and the suffix is placed in the
         * suffix var, without the period. Maybe should be conditional? <josh@surber.us>
         */
        $regexp = "|\.\w{1,5}$|";
        $old = $this->uri_string;
        $new = preg_replace($regexp, "", $old);
        $suf = substr($old, strlen($new)+1);
        $this->uri_string = $new;
        $this->suffix = $suf;
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Fetch the URI suffix
     *
     * @access    public
     * @param    string
     * @return    string
     */
    function suffix($nosuf = '') {
        /*
         * Return the URI suffix. If a variable is passed thru, and there
         * is no suffix, return said variable. <josh@surber.us>
         */
        $suf = $this->suffix;
        return (!$suf=='') ? $suf : $nosuf;
    }

}
// END MY_URI Class

/* End of file MY_URI.php */
/* Location: ./system/application/libraries/MY_URI.php */

[size=4]MY_Output[/size]


&lt;?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
 * MY_Output
 *
 * Use with MY_URI to add headers to CI's cache files.
 *
 * @package        CodeIgniter
 * @subpackage    Libraries
 * @category    Output
 * @author        Josh Surber
 * @transcriber Bradford Mar
 * @link        http://codeigniter.com/forums/viewthread/76260/
 */
class MY_Output extends CI_Output {

    /**
     * Display Output
     *
     * All "view" data is automatically put into this variable by the controller class:
     *
     * $this->final_output
     *
     * This function sends the finalized output data to the browser along
     * with any server headers and profile data.  It also stops the
     * benchmark timer so the page rendering speed and memory usage can be shown.
     *
     * @access    public
     * @return    mixed
     */        
    function _display($output = '')
    {    
        // Note:  We use globals because we can't use $CI =& get_instance()
        // since this function is sometimes called by the caching mechanism,
        // which happens before the CI super object is available.
        global $BM, $CFG;

        // --------------------------------------------------------------------

        // Set the output data
        if ($output == '')
        {
            $output =& $this->final_output;
        }

        // --------------------------------------------------------------------

        // Do we need to write a cache file?
        if ($this->cache_expiration > 0)
        {
            $this->_write_cache($output);
        }

        // --------------------------------------------------------------------

        // Parse out the elapsed time and memory usage,
        // then swap the pseudo-variables with the data

        $elapsed = $BM->elapsed_time('total_execution_time_start', 'total_execution_time_end');        
        $output = str_replace('{elapsed_time}', $elapsed, $output);

        $memory     = ( ! function_exists('memory_get_usage')) ? '0' : round(memory_get_usage()/1024/1024, 2).'MB';
        $output = str_replace('{memory_usage}', $memory, $output);
        
        // --------------------------------------------------------------------

        // Is compression requested?
        if ($CFG->item('compress_output') === TRUE)
        {
            if (extension_loaded('zlib'))
            {
                if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) AND strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
                {
                    ob_start('ob_gzhandler');
                }
            }
        }

        // --------------------------------------------------------------------

        // Are there any server headers to send?
    if (count($this->headers) > 0)
    {
            foreach ($this->headers as $header)
        {
                @header($header[0], $header[1]);
        }
    }        

        // --------------------------------------------------------------------

        // Does the get_instance() function exist?
        // If not we know we are dealing with a cache file so we'll
        // simply echo out the data and exit.
        if ( ! function_exists('get_instance'))
        {
            echo $output;
            log_message('debug', "Final output sent to browser");
            log_message('debug', "Total execution time: ".$elapsed);
            return TRUE;
        }

        // --------------------------------------------------------------------

        // Grab the super object.  We'll need it in a moment...
        $CI =& get_instance();

        // Do we need to generate profile data?
        // If so, load the Profile class and run it.
        if ($this->enable_profiler == TRUE)
        {
            $CI->load->library('profiler');                

            // If the output data contains closing &lt;/body&gt; and &lt;/html&gt; tags
            // we will remove them and add them back after we insert the profile data
            if (preg_match("|&lt;/body&gt;.*?&lt;/html&gt;|is", $output))
            {
                $output  = preg_replace("|&lt;/body&gt;.*?&lt;/html&gt;|is", '', $output);
                $output .= $CI->profiler->run();
                $output .= '&lt;/body&gt;&lt;/html&gt;';
            }
            else
            {
                $output .= $CI->profiler->run();
            }
        }

        // --------------------------------------------------------------------

        // Does the controller contain a function named _output()?
        // If so send the output there.  Otherwise, echo it.
        if (method_exists($CI, '_output'))
        {
            $CI->_output($output);
        }
        else
        {
            echo $output;  // Send it to the browser!
        }

        log_message('debug', "Final output sent to browser");
        log_message('debug', "Total execution time: ".$elapsed);        
    }
    
    // --------------------------------------------------------------------
    
    /**
     * Write a Cache File
     *
     * @access    public
     * @return    void
     */    
    function _write_cache($output)
    {
        $CI =& get_instance();    
        $path = $CI->config->item('cache_path');
    
        $cache_path = ($path == '') ? BASEPATH.'cache/' : $path;
        
        if ( ! is_dir($cache_path) OR ! is_writable($cache_path))
        {
             return;
         }
        
        /*
         * Should including the suffix be optional? <josh@surber.us>
         */
        $uri =    $CI->config->item('base_url').
                $CI->config->item('index_page').
                $CI->uri->uri_string() . '.' .
                $CI->uri->suffix;
        
        $cache_path .= md5($uri);

        if ( ! $fp = @fopen&#40;$cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE&#41;)
        {
            log_message('error', "Unable to write cache file: ".$cache_path);
             return;
         }
         
        /*
         * Save the timestamp as a header, and get all
         * the headers together in one string so we can
         * cache them. <josh@surber.us>
         */
        $this->set_header('X-CI-timestamp: '.(time() + ($this->cache_expiration * 60)));
    $headers = '';
    foreach ( $this->headers as $header )
    {
        $headers .= $header[0]."\n";
    }

    if (flock($fp, LOCK_EX))
    {
        fwrite($fp, $headers."\n".$output);
        flock($fp, LOCK_UN);
    }
    else
    {
        log_message('error', "Unable to secure a file lock for file at: ".$cache_path);
        return;
    }
        fclose($fp);
//        @chmod($cache_path, 0777);
        @chmod($cache_path, DIR_WRITE_MODE);

        log_message('debug', "Cache file written: ".$cache_path);
    }

    // --------------------------------------------------------------------
    
    /**
     * Update/serve a cached file
     *
     * @access    public
     * @return    void
     */    
    function _display_cache(&$CFG, &$RTR)
    {

        $URI =& load_class('URI');
    
        $cache_path = ($CFG->item('cache_path') == '') ? BASEPATH.'cache/' : $CFG->item('cache_path');
        
        if ( ! is_dir($cache_path) OR ! is_writable($cache_path))
        {
             return FALSE;
         }
        
        /*
         * Build the file path.  The file name is an MD5 hash of the full URI
         * If we make the suffix optional in _write_cache() be sure to do
         * so here too. <josh@surber.us>
         */
        $uri =    $CFG->item('base_url').
                $CFG->item('index_page').
                $URI->uri_string . '.' .
                $URI->suffix;
        
        $filepath = $cache_path.md5($uri);
        
        if ( ! @file_exists($filepath))
        {
            return FALSE;
        }
    
        if ( ! $fp = @fopen&#40;$filepath, FOPEN_READ&#41;)
        {
            return FALSE;
        }
            
        flock($fp, LOCK_SH);
        
        $cache = '';
        if (filesize($filepath) > 0)
        {
            $cache = fread($fp, filesize($filepath));
        }
    
        flock($fp, LOCK_UN);
        fclose($fp);

        /*
         * Seperate headers from content.
         * This is used to output cached headers. <josh@surber.us>
         */
        list($headers, $cache) = explode("\n\n", $cache, 2);

        /*
         * Seperate each header, looking for timestamp
         * The others are output as-is. <josh@surber.us>
         */
        $headers = explode("\n", $headers);
        foreach ($headers as $header) {
            if (FALSE === strpos($header, 'X-CI-timestamp')) {
                $this->set_header($header);
            } else {
                $timestamp = substr($header, 16);
        // Has the file expired? If so we'll delete it.
                if (time() >= trim($timestamp))
                {         
                    @unlink&#40;$filepath&#41;;
                    log_message('debug', "Cache file has expired. File deleted");
                    return FALSE;
                }
            }
        }

        // Display the cache
        $this->_display($cache);
        log_message('debug', "Cache file is current. Sending it to browser.");        
        return TRUE;
    }

}
// END MY_Output Class

/* End of file MY_Output.php */
/* Location: ./system/application/libraries/MY_Output.php */