ciogao 发表于 2012-4-12 18:24:22

CI Output类BUG? 附完美解决方案

本帖最后由 ciogao 于 2012-4-29 17:28 编辑

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


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


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

data/attachment/album/201204/12/182449u76g472gugp62255.png


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

data/attachment/album/201204/12/182449qf8g8j8c8uri8gjn.png

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


Hex 发表于 2012-4-12 20:26:48

事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头。
这个原因实际上是由于 CI 的缓存比较简单,没有考虑还要缓存 http header。
如果算 BUG 的话,我想应该给缓存文件加上 http header 的信息才行。

ciogao 发表于 2012-4-13 00:09:00

Hex 发表于 2012-4-12 20:26 static/image/common/back.gif
事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头 ...

对的,正是因为cache方法在取得缓存文件后,输出时没有输出header信息

ciogao 发表于 2012-4-13 01:06:59

Hex 发表于 2012-4-12 20:26 static/image/common/back.gif
事实上,CI 如果发现有缓存不会经过控制器,所以你的 set_header 方法不会被执行,当然就无法发送正确的头 ...

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

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

class CI_Output {

        var $final_output;
        var $cache_expiration        = 0;
        var $headers                 = array();
        var $enable_profiler         = FALSE;


于是可修改$headers为

        var $headers                 = array(array('Content-type: application/json',true));


或CodeIgniter.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方法,
将缓存生成规则修改为
时间戳 + 资源类型 + cache
取得cache时,正则匹配出“资源类型”,同时输出头信息。

今天晚了,明天照此优化output类,完成后分享具体修改方法与优化后类文件。

Hex 发表于 2012-4-13 12:10:01

ciogao 发表于 2012-4-13 01:06 static/image/common/back.gif
翻看了Output类与核心文件CodeIgniter.php,

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


你这个方法才是治本的方法。
把 HTTP HEADER 也缓存起来,这样就能缓存各种数据了。

ciogao 发表于 2012-4-29 17:24:40

本帖最后由 ciogao 于 2012-5-1 22:12 编辑

Hex 发表于 2012-4-13 12:10 static/image/common/back.gif
你这个方法才是治本的方法。
把 HTTP HEADER 也缓存起来,这样就能缓存各种数据了。 ...
这段时间一直忙于一个项目的一系列接口的开发工作,加班甚至通宵,没有时间修改Output类,今天抽了点时间看了一下。修改完毕。分享给大家,以下是修改步骤。(HEX申请加精啊!{:soso_e100:})

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


* @package      CodeIgniter
* @author      ExpressionEngine Dev Team
* @author         ciogao@gmail.com 修改于 2012-04-29 缓存多种资源类型


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


    /**
   * Write a Cache File
   * @authorciogao@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);
            $formats = $formats.'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方法,如:

$this->output->set_header('Content-type: application/json',true);


则缓存文件中会加入资源类型描述,生成缓存如:

1335690070TS--->jsonFORMATS--->{"code":1000,"msg":"success","data":{"17":{"custname":"\u5c01\u795e\u9879\u76ee8"," ***


缓存成功。

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


    /**
   * Update/serve a cached file
   * @authorciogao@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);
            switch ($formats_tem){
                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,'',$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均可
新浪微博   @阳光蝙蝠

vem 发表于 2015-3-6 18:32:05

$formats = $formats.'FORMATS--->';

这一句 应该是

$output .= $formats.'FORMATS--->' . $output;

才对吧?
页: [1]
查看完整版本: CI Output类BUG? 附完美解决方案