PHPのプロジェクトの挙動を追ってたら予想外の挙動に遭遇したのでメモがてら。
TL;DR;
session_start
を呼ぶとhttp response headerの中身に書き換わるものがある
詳しく
途中でセットしてあるはずのヘッダーが実際にはサーバーから送信されず、どこからも指定していないヘッダーが送信される、という挙動に遭遇した。
具体的には
header('Cache-Control: pre-check=0');
のようにカスタム?値をセットしているはずがレスポンスには現れず、代わりに以下の3行が追加されていた。ちなみにpre-checkというのは仕様には無く、IE5の独自ヘッダーらしい?
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
この値をセットしている記述はどこにもないが、ローカル環境でもプロダクション環境でも同様の値がセットされているため、何かしらの処理が入っているはず。全く当たりがつかないので、ステップ実行しながら怪しい箇所で headers_list
を実行して、何に起因しているかを追ってみた。すると、どうやらsession_start
をコールした前後で書き換わっているようだった。
なぜこんなことが起きるか分からないのでsession拡張のコードを見てみると
session_start
関数からphp_session_cache_limiter
が呼ばれていた。
気になったらこの辺から追ってみると良いでしょう。
https://github.com/php/php-src/blob/f009d6e7c3ce124d1a5a602e7532f93ed55ebae0/ext/session/session.c#L2431
その中ではiniの設定値の session.cache_limiter
の中身に従ってヘッダーを設定する処理が記述されている。
https://github.com/php/php-src/blob/f009d6e7c3ce124d1a5a602e7532f93ed55ebae0/ext/session/session.c#L1180-L1208
関連していそうなsession_cache_limiter
関数のドキュメントはこちら
https://www.php.net/manual/ja/function.session-cache-limiter.php
私の手元ではsession.cache_limtier
に nocache
が設定されていたため、
CACHE_LIMITER_FUNC(nocache) /* {{{ */
{
ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
/* For HTTP/1.1 conforming clients */
ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate");
/* For HTTP/1.0 conforming clients */
ADD_HEADER("Pragma: no-cache");
}
この処理が実行されて上で記述したヘッダーがセットされているようだった。
このExpiresの日付はなんなんだと思ったけど、この変更が入ったのはどうやら16年も前らしい・・・
試しにExpiresやContent-Typeがどうなるかを見るために
<?php
header('Content-Type: application/json');
header('Expires: Wed, 18 Nov 1981 00:00:00 GMT');
session_start();
echo json_encode([
'foo' => 'bar',
]);
という適当なPHPコードを書いてBuilt inサーバーで実行したところ
Content-Type: application/json
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
というヘッダーが返ってきた。Expiresは上書きされてConetnt-Typeはそのままという想定した挙動になった。
session_startをコールする前にheaderをセットするのはお行儀の悪さが感じられてあまり見ることは無いんじゃないかと思うが、久しぶりにPHPの想定しない挙動に巡り会った。
header
関数が呼ばれたらすぐに出力に乗るのかと思っていたけど、headers_sent
を実行してもfalseが返ってくるしsession_startも警告を出さないのでどうやら違うらしい。
これも調べてみた方が良さそう。