CodeIgniterの学習 36 - HTTPレスポンスヘッダの調整をおこなってブラウザ側でキャッシュされないようにする

昨日のエントリの作業中に、HTTPヘッダを見ていると、

  • Expires
  • Last-Modified
  • Cache-Control
  • Pragma

系のヘッダを吐いていないことに気づいた。

普通のHTMLページ(静的ページ)ならば、キャッシュは別にされても良いけれど、

webシステム系だと、ブラウザ側のキャッシュが優先されて登録したはずのデータが画面に反映されないような事態は困るのだ。

つうわけで、phpのheader() をどこかに書きたいのだが、CodeIgniterではどのようにすればいいか?


header()は、$this->output->set_header()で設定する

マニュアルを見てみると

$this->output->set_header("HTTP/1.0 200 OK");
$this->output->set_header("HTTP/1.1 200 OK");
$this->output->set_header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_update).' GMT');
$this->output->set_header("Cache-Control: no-store, no-cache, must-revalidate");
$this->output->set_header("Cache-Control: post-check=0, pre-check=0", false);
$this->output->set_header("Pragma: no-cache"); 

みたいにレスポンスヘッダを指定できるらしい。

じゃあどこに書けばいいのだろう?


生成部分の探索 … 結論:post_controllerでフックすればいいみたい

ソースを覗いてみると、

system/libraries/Controller.php
でコントローラがインスタンス化されたときに、
_ci_initialize() が呼ばれて、そこで

<?php
 //上略
  function _ci_initialize()
  {
    // Assign all the class objects that were instantiated by the
    // front controller to local class variables so that CI can be
    // run as one big super object.
    $classes = array(
              'config'  => 'Config',
              'input'   => 'Input',
              'benchmark' => 'Benchmark',
              'uri'   => 'URI',
              'output'  => 'Output',
              'lang'    => 'Language'
              );
    
    foreach ($classes as $var => $class)
    {
      $this->$var =& load_class($class);
    }
 //下略
 ?>

みたいに outputがインスタンス化されるみたいなので、ほぼどこでも書けそうだ。


んで
$this->output->set_header('レスポンスヘッダ');すると

system/libraries/Output.phpの、_display()メソッド内で

<?php
 //上略
    // Are there any server headers to send?
    if (count($this->headers) > 0)
    {
      foreach ($this->headers as $header)
      {
        @header($header);
      }
    } 
 //下略
 ?>

としてheaderを吐いているようだ。


すなわち、_display()が呼ばれるより前にセットしてあげればいい。


じゃあ_display()はどこで呼ばれるかというと、
ドキュメントルート/index.phpから呼び出される
フロントコントローラsystem/codeigniter/CodeIgniter.php 内に有って、

<?php
 //上略
/*
 * ------------------------------------------------------
 *  Is there a "post_controller" hook?
 * ------------------------------------------------------
 */
$EXT->_call_hook('post_controller');

/*
 * ------------------------------------------------------
 *  Send the final rendered output to the browser
 * ------------------------------------------------------
 */

if ($EXT->_call_hook('display_override') === FALSE)
{
	$OUT->_display();
}
 //下略
 ?>

みたいになっているので、_display()の直前のpost_controller辺りにフックすればいいんだろうと思った。


フックを設定する

以前のエントリ
http://d.hatena.ne.jp/dix3/20081003/1222980188
http://d.hatena.ne.jp/dix3/20081005/1223161593
やらで既にフックは使っているので、そのソースにpost_controllerフックを追加してみた。

以下変更部分

変更箇所1:application/config/config.php

$config['enable_hooks'] = TRUE;

にする。(過去に設定済み)

これでフックは有効になる。


変更箇所2:system/application/config/hooks.php

<?php
 //上略
//HTTPレスポンスヘッダの調整
$hook['post_controller'][] = array(
'class' => 'MyClasses',
'function' => 'set_header',
'filename' => 'MyClasses.php',
'filepath' => 'hooks'
);

//for my_classes

/* End of file hooks.php */
/* Location: ./system/application/config/hooks.php */
 ?>

と、post_controllerのフックを設定。

呼び出し先は、application/hooks/MyClasses.phpの、set_header()メソッドとした。
(MyClasses.phpは以前のエントリで作成済)


変更箇所3:application/hooks/MyClasses.php

<?php 
if ( ! defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class MyClasses {
  /**
   * includes the directory application\my_classes\ in your includes directory
   */
  function index()
  {
    // includes the directory application\my_classes\
    // for windows tests change the ':' before APPPATH to ';'
    // Zend Framework、その他独自移植ライブラリ用のパスの読み込み
    ini_set( 'include_path', ini_get( 'include_path' ) . ':' . APPPATH . 'my_classes/' );
  }
  function enable_profiler()
  {
    // 開発時に自動的にプロファイラを有効にする
    if ( config_item( 'my_debugger' ) ) {
      $CI = &get_instance();
      $CI -> output -> enable_profiler( true );
    }
  }

  function set_header()
  {
    // HTTPレスポンスヘッダの調整
    $CI = &get_instance();
    // $CI -> output -> set_header( "HTTP/1.0 200 OK" );
    // $CI -> output -> set_header( "HTTP/1.1 200 OK" );
    $CI -> output -> set_header( "Content-Type: text/html; charset=UTF-8" );
    $CI -> output -> set_header( "Expires: Thu, 01 Dec 1994 23:59:59 GMT" );
    $CI -> output -> set_header( "Last-Modified: " . gmdate( "D, d M Y H:i:s", time() ) . " GMT" );
    $CI -> output -> set_header( "Cache-Control: no-store, no-cache, must-revalidate" );
    $CI -> output -> set_header( "Cache-Control: post-check=0, pre-check=0", false );
    $CI -> output -> set_header( "Pragma: no-cache" );
  }
} //endofclass

/* End of file MyClasses.php */
/* Location: ./application/hooks/MyClasses.php */
 ?>

function set_header()を追加。
(function index()と、function enable_profiler()は以前のエントリでの追加分なので、不要なら削って良い)


吐かれるヘッダ

上の設定でレスポンスヘッダを弄れるようになった。

(HTTPヘッダ)

http://xxxx/yyyy

GET /yyyy HTTP/1.1
Host: xxxx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://xxxx/yyyy
Cookie: ci_session=cc75ce21fb590e841cd801386aa7b94c
If-Modified-Since: Thu, 06 Nov 2008 08:00:27 GMT

HTTP/1.x 200 OK
Date: Thu, 06 Nov 2008 08:00:45 GMT
Server: Apache
Set-Cookie: ci_session=90f04f1f989ddb0f668254339430278b; expires=Thu, 06-Nov-2008 10:00:45 GMT; path=/
Expires: Thu, 01 Dec 1994 23:59:59 GMT
Last-Modified: Thu, 06 Nov 2008 08:00:45 GMT
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Content-Language: ja
----------------------------------------------------------


てなかんじ。