Friday, April 12, 2013

CodeIgniter Caching Problem with HTTP Headers (Content-Type)


Problem Description

I use 

$this->output->cache(n); 
to cache the file for at least n minutes. This works perfect for the first request the users’ browser makes. The second time, the  files seem to have the Content-type text/html, so no custom header type is applied at all!

How can I use caching and make sure the Content-Type header is also cached, so codeigniter does not fall back to text/html

Solution:


When you set a header via $this->output->set_header(), but also use caching via $this->output->cache(), those headers you sent will not be cached along with your content.
This can be very problematic: if you're caching CSS, for example, you want the cache to return that CSS along with the Content-Type header "text/css" - otherwise most browsers will interpret your CSS as just plain text, and hence not use it for rendering. With this simple extension to the Output class, headers will also be cached and returned.

Advantages over built-in cache

This extension is more memory efficient as the current (1.7.0) caching system, so even if you don't need the header caching, this extension is worth installing. 

Instructions

Create a file in application/libraries named MY_Output.php ( as described in the user guide ) and copy/paste the following code:
   <?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
 * MY_Output
 * 
 * Modified _write_cache and _display_cache to allow for header caching
 * works with CodeIgniter 1.7.0
 *
 * @package        CodeIgniter
 * @subpackage    Libraries
 * @category    Output
 * @author        Arjen van Bochoven
 * @link        http://codeigniter.com/wiki/Cache_headers
 */

class MY_Output extends CI_Output {


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

    /**
     * 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_really_writable($cache_path))
        {
            return;
        }
    
        $uri =    $CI->config->item('base_url').
                $CI->config->item('index_page').
                $CI->uri->uri_string();
    
        $cache_path .= md5($uri);

        if ( ! $fp = @fopen($cache_path, FOPEN_WRITE_CREATE_DESTRUCTIVE))
        {
            log_message('error', "Unable to write cache file: ".$cache_path);
            return;
        }
    
        $expire = time() + ($this->cache_expiration * 60);
            
        if (flock($fp, LOCK_EX))
        {
            fwrite($fp, serialize(array('exp' => $expire, 'headers' => $this->headers, 'output' => $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, DIR_WRITE_MODE);

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

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

    /**
     * Update/serve a cached file
     *
     * @access    public
     * @return    void
     */    
    function _display_cache(&$CFG, &$URI)
    {
        $cache_path = ($CFG->item('cache_path') == '') ? BASEPATH.'cache/' : $CFG->item('cache_path');
        
        if ( ! is_dir($cache_path) OR ! is_really_writable($cache_path))
        {
            return FALSE;
        }
    
        // Build the file path.  The file name is an MD5 hash of the full URI
        $uri =    $CFG->item('base_url').
                $CFG->item('index_page').
                $URI->uri_string;
            
        $filepath = $cache_path.md5($uri);
    
        if ( ! @file_exists($filepath))
        {
            return FALSE;
        }

        if ( ! $fp = @fopen($filepath, FOPEN_READ))
        {
            return FALSE;
        }
        
        flock($fp, LOCK_SH);
        
        $cache = '';
        if (filesize($filepath) > 0)
        {
            $cache = fread($fp, filesize($filepath));
        }
    
        flock($fp, LOCK_UN);
        fclose($fp);
        
        // Restore the cache array
        $cache = unserialize($cache);
        
        // Validate cache file
        if ( ! isset($cache['exp'] ) OR ! isset($cache['headers']) OR ! isset($cache['output']))
        {
            return FALSE;
        }
        
        // Has the file expired? If so we'll delete it.
        if (time() >= trim($cache['exp']))
        {         
            @unlink($filepath);
            log_message('debug', "Cache file has expired. File deleted");
            return FALSE;
        }
        
        // Restore headers
        $this->headers = $cache['headers'];
                
        // Display the cache
        $this->_display($cache['output']);
        log_message('debug', "Cache file is current. Sending it to browser.");        
        return TRUE;
        
    }
}

Ref: https://github.com/EllisLab/CodeIgniter/wiki/Cache-headers

1 comment:

  1. You can Eliminate cache by Passing Current Time stamp also.

    ReplyDelete