用户
 找回密码
 入住 CI 中国社区
搜索
查看: 6663|回复: 6
收起左侧

[库 Library] CI Output类BUG? 附完美解决方案

[复制链接]
发表于 2012-4-12 18:24:22 | 显示全部楼层 |阅读模式
本帖最后由 ciogao 于 2012-4-29 17:28 编辑

一个项目中开发接口的需求为RESTful,返回资源类型为JSON。
使用Output类设置header头信息:

PHP复制代码
 
        $this->output->set_header('Content-type: application/json',true);
        $this->output->set_status_header(200);
 
复制代码


返回资源类型正常,见图:




为缓存某接口返回信息,使用output->cache()方法,首次返回资源类型正常,第二次在cache未过期时,cache方法取得缓存,并展示,资源类型为 html,返回资源类型不正确,接口服务异常。见图:



初步判断为Output类中读取cache并输出的方法有误,未正确设置资源类型。
可否认定为Output类BUG?


发表于 2012-4-12 20:26:48 | 显示全部楼层
事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头。
这个原因实际上是由于 CI 的缓存比较简单,没有考虑还要缓存 http header。
如果算 BUG 的话,我想应该给缓存文件加上 http header 的信息才行。
 楼主| 发表于 2012-4-13 00:09:00 | 显示全部楼层
Hex 发表于 2012-4-12 20:26
事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头 ...

对的,正是因为cache方法在取得缓存文件后,输出时没有输出header信息
 楼主| 发表于 2012-4-13 01:06:59 | 显示全部楼层
Hex 发表于 2012-4-12 20:26
事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头 ...

翻看了Output类与核心文件CodeIgniter.php,

Output类中初始化头信息为空:
PHP复制代码
 
class CI_Output {
 
        var $final_output;
        var $cache_expiration   = 0;
        var $headers            = array();
        var $enable_profiler    = FALSE;
 
复制代码


于是可修改$headers为
PHP复制代码
 
        var $headers            = array(array('Content-type: application/json',true));
 
复制代码


或CodeIgniter.php中修改缓存输出头:

PHP复制代码
 
$OUT =& load_class('Output');
 
/*
 * ------------------------------------------------------
 *      Is there a valid cache file?  If so, we're done...
 * ------------------------------------------------------
 */

 
if ($EXT->_call_hook('cache_override') === FALSE)
{
        $OUT->set_header('Content-type: application/json',true); //为缓存输出header,资源为json
        if ($OUT->_display_cache($CFG, $URI) == TRUE)
        {
                exit;
        }
}
 
复制代码


此接口返回缓存cache时资源类型为json的问题解决。

但此时要输出html类型缓存又成为了问题,因为所有的缓存输出都成为了json,html代码不被解析。

拟采取的方法:以上两处恢复原样初始设置不做任何修改,修改output类中_display_cache与_display方法,
将缓存生成规则修改为
PHP复制代码
时间戳 + 资源类型 + cache
复制代码

取得cache时,正则匹配出“资源类型”,同时输出头信息。

今天晚了,明天照此优化output类,完成后分享具体修改方法与优化后类文件。
发表于 2012-4-13 12:10:01 | 显示全部楼层
ciogao 发表于 2012-4-13 01:06
翻看了Output类与核心文件CodeIgniter.php,

Output类中初始化头信息为空:

你这个方法才是治本的方法。
把 HTTP HEADER 也缓存起来,这样就能缓存各种数据了。
 楼主| 发表于 2012-4-29 17:24:40 | 显示全部楼层
本帖最后由 ciogao 于 2012-5-1 22:12 编辑
Hex 发表于 2012-4-13 12:10
你这个方法才是治本的方法。
把 HTTP HEADER 也缓存起来,这样就能缓存各种数据了。 ...

这段时间一直忙于一个项目的一系列接口的开发工作,加班甚至通宵,没有时间修改Output类,今天抽了点时间看了一下。修改完毕。分享给大家,以下是修改步骤。(HEX申请加精啊!{:soso_e100:})

首先修改一下类的注释信息:

PHP复制代码
 
 * @package        CodeIgniter
 * @author        ExpressionEngine Dev Team
 * @author         [url=mailto:ciogao@gmail.com]ciogao@gmail.com[/url] 修改于 2012-04-29 缓存多种资源类型
 
复制代码


对写缓存方法_write_cache的修改如下,详情见注释:

PHP复制代码
 
    /**
     * Write a Cache File
     * @author  ciogao@gmail.com 修改于 2012-04-29
     * @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);
       
        //=====================
        //Powered by ciogao@gmail.com
        //判断有无设置头,并写入  如 jsonFORMATS--->  xmlFORMATS--->  
        if ($this->headers) {
            $formats = explode('/',$this->headers[0][0]);
            $formats = $formats[1].'FORMATS--->';
        }
        //======================
        if (flock($fp, LOCK_EX))
        {
            fwrite($fp, $expire.'TS--->'.$formats.$output);    //写入 formats
            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);
    }
 
复制代码


此时如果视图中使用了set_herder方法,如:
PHP复制代码
 
$this->output->set_header('Content-type: application/json',true);
 
复制代码


则缓存文件中会加入资源类型描述,生成缓存如:
PHP复制代码
 
1335690070TS--->jsonFORMATS--->{"code":1000,"msg":"success","data":{"17":{"custname":"\u5c01\u795e\u9879\u76ee8"," ***
复制代码


缓存成功。

接下来修改_display_cache方法,详情见注释:

PHP复制代码
 
    /**
     * Update/serve a cached file
     * @author  ciogao@gmail.com 修改于 2012-04-29
     * @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);
               
        // Strip out the embedded timestamp        
        if ( ! preg_match("/(\d+TS--->)/", $cache, $match))
        {
            return FALSE;
        }
       
        // Has the file expired? If so we'll delete it.
        if (time() >= trim(str_replace('TS--->', '', $match['1'])))
        {        
            @unlink($filepath);
            log_message('debug', "Cache file has expired. File deleted");
            return FALSE;
        }
       
        //================================================================
        /**
         * Powered by ciogao@gmail.com 2012-07-4-29
         * 获取缓存信息中的资源类型,并设置输出头信息
         */

        preg_match("/(\w+FORMATS--->)/", $cache, $formats);
        if ($formats) {
            $formats_tem = explode('FORMATS',$formats[1]);
            switch ($formats_tem[0]){
                case 'json':
                    $header = 'application/json';    //jsonRPC
                    break;
                case 'xml':
                    $header = 'application/xml';    //xmlRPC
                    break;
                case 'plain':
                    $header = 'text/plain';          //PHP数组
                    break;
                default:
                    $header = 'test/html';            //其他判断为html
            }
            $cache = str_replace($formats[1],'',$cache);
            $this->set_header('Content-type: '.$header,true);
        }
        //================================================================
       
        // Display the cache
       
        $this->_display(str_replace($match['0'], '', $cache));
        log_message('debug', "Cache file is current. Sending it to browser.");        
        return TRUE;
    }
 
复制代码


OK,大功告成。

使用不明白的地方可以联系我,以下是联系方式:
ciogao@gmail.com    msn或email均可
新浪微博   @阳光蝙蝠

评分

参与人数 2威望 +10 收起 理由
Hex + 5 很给力!
lamtin + 5 赞一个!

查看全部评分

发表于 2015-3-6 18:32:05 | 显示全部楼层
$formats = $formats[1].'FORMATS--->';

这一句 应该是

$output .= $formats[1].'FORMATS--->' . $output;

才对吧?

本版积分规则