Edited at

PHPのsessionでセットされるレスポンスヘッダー

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_limtiernocacheが設定されていたため、

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がどうなるかを見るために


test.php


<?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も警告を出さないのでどうやら違うらしい。

これも調べてみた方が良さそう。