納品した WEB サイトが、ie11 の時だけログインできないと言われた。(それも本番サイトだけ)

csrf エラーのようなので、ログインフォームがキャッシュされているのだろうと思い、 確認してみると、やはりログインページをキャッシュから読み出していた。 ログアウト時には、セッションが破棄されるため、csrf token が一致しない、などが考えられる。

詳しく見ていないが、本番サイトだけキャッシュまわりの設定が違うのかもしれない。

ブラウザのキャッシュに関する検証

ブラウザのキャッシュについては、MDN のHTTPキャッシュ などに詳しく解説されているが、 実際の動作を確かめたことはなかった。

http-equiv を入れれば解決するとは思うが、一度、自分で確認してみたいと思う。

ヘッダを足したりしてみたいので、単体で web サーバになれる python の responder を使う。

api.py

#
# cache test by moriya@runserver.jp
#
from pathlib import Path
import datetime
import responder

wd = Path(__file__).parent
api = responder.API(templates_dir=wd/'templates')

# ヘッダに何も指定しない
#
# ie11 ではキャッシュされない
#
# chrome(92.0.4515.131) ではキャッシュされない
#
# => 特に指定がない場合の動作
#
@api.route("/cache_nospec/")
async def normal(req, resp):
    now = datetime.datetime.now()
    resp.html = api.template('cache_nospec.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# Cache-Control ヘッダ: max-age=60
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => max-age を指定した場合の動作
#    ie11 はアドレスバーで URL を入力した場合は、リンククリックと同様
#
#    chrome はアドレスバーで URL を入力した場合は、リロードと同様
#    https://stackoverflow.com/questions/11245767/is-chrome-ignoring-cache-control-max-age
#    https://blog.chromium.org/2017/01/reload-reloaded-faster-and-leaner-page_26.html
#    などか
#
@api.route("/cache_control1/")
async def cache_control1(req, resp):
    now = datetime.datetime.now()
    resp.headers['Cache-Control'] = 'max-age=60'
    resp.html = api.template('cache_control1.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# Cache-Control ヘッダ max-age=60
# ドキュメントの http-equiv Cache-Control の値 no-store
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => Cache-Control は http-equiv では指定できない?
#
@api.route("/cache_control1a/")
async def cache_control1a(req, resp):
    now = datetime.datetime.now()
    resp.headers['Cache-Control'] = 'max-age=60'
    # <meta http-equiv="Cache-Control" content="no-store">
    resp.html = api.template('cache_control1a.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# Cache-Control ヘッダ: max-age=60
# ドキュメントの http-equiv Expires の値: Thu, 01 Dec 1994 16:00:00 GMT
#
# ie11
# リンククリック/アドレスバー: 再リクエストされる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => ie11 では、http-equiv expires は有効
#    chrome は http-equiv expires を無視?
#
@api.route("/cache_control2/")
async def cache_control2(req, resp):
    now = datetime.datetime.now()
    resp.headers['Cache-Control'] = 'max-age=60'
    # <meta http-equiv="Expires" content="Thu, 01 Dec 1994 16:00:00 GMT">
    resp.html = api.template('cache_control2.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# Cache-Control ヘッダ: max-age=60
# ドキュメントの http-equiv Cache-Control 値: max-age=0
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => ie11/chrome とも ドキュメントの http-equiv cache-control は無視?
#
@api.route("/cache_control2a/")
async def cache_control2a(req, resp):
    now = datetime.datetime.now()
    resp.headers['Cache-Control'] = 'max-age=60'
    # <meta http-equiv="Cache-Control" content="max-age=0">
    resp.html = api.template('cache_control2a.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# レスポンスヘッダなし
# ドキュメントの http-equiv Cache-Control 値: max-age=60
#
# ie11
# リンククリック/アドレスバー: 再リクエストされる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: 再リクエストされる
# アドレスバー/リロード: 再リクエストされる
#
# => ie11/chrome とも ドキュメントの http-equiv cache-control は無視?
#
@api.route("/cache_control2b/")
async def cache_control2b(req, resp):
    now = datetime.datetime.now()
    # <meta http-equiv="Cache-Control" content="max-age=60">
    resp.html = api.template('cache_control2b.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# レスポンスヘッダなし
# ドキュメントの http-equiv Expires 値: 60秒後
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: 再リクエストされる
# アドレスバー/リロード: 再リクエストされる
#
# => ie11 はドキュメントの http-equiv が使われる
#    chrome は http-equiv expires を無視?
#
@api.route("/cache_control2c/")
async def cache_control2c(req, resp):
    now = datetime.datetime.now()
    utcnow = datetime.datetime.now(datetime.timezone.utc)
    expires = utcnow + datetime.timedelta(seconds=60)
    # <meta http-equiv="Expires" content="現在時刻+60秒">
    resp.html = api.template('cache_control2c.html', time=now.strftime('%Y/%m/%d %H:%M:%S'), expires=expires.strftime('%a, %d %b %Y %H:%M:%S GMT'))

# Expires ヘッダ: Thu, 01 Dec 1994 16:00:00 GMT
# ドキュメントの http-equiv Expires 値: 60秒後
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: 再リクエストされる
# アドレスバー/リロード: 再リクエストされる
#
# => ie11 はドキュメントの http-equiv が優先
#    chrome は http-equiv expires を無視?
#
@api.route("/cache_control2d/")
async def cache_control2d(req, resp):
    now = datetime.datetime.now()
    utcnow = datetime.datetime.now(datetime.timezone.utc)
    expires = utcnow + datetime.timedelta(seconds=60)
    resp.headers['Expires'] = 'Thu, 01 Dec 1994 16:00:00 GMT'
    # <meta http-equiv="Expires" content="現在時刻+60秒">
    resp.html = api.template('cache_control2c.html', time=now.strftime('%Y/%m/%d %H:%M:%S'), expires=expires.strftime('%a, %d %b %Y %H:%M:%S GMT'))

# Expires ヘッダ: 60秒後
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => Expires ヘッダは有効(念のため確認)
# 
@api.route("/cache_control2e/")
async def cache_control2e(req, resp):
    now = datetime.datetime.now()
    utcnow = datetime.datetime.now(datetime.timezone.utc)
    expires = utcnow + datetime.timedelta(seconds=60)
    resp.headers['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
    resp.html = api.template('cache_control2e.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

# Cache-Control ヘッダ: max-age=60
# Expires ヘッダ: Thu, 01 Dec 1994 16:00:00 GMT
# MDN によると、Cache-Control に max-age 指定があると、Expires は無視される
#
# ie11
# リンククリック/アドレスバー: キャッシュが使われる
# リロード: 再リクエストされる
#
# chrome
# リンククリック: キャッシュが使われる
# アドレスバー/リロード: 再リクエストされる
#
# => MDN の記述どおり
#
@api.route("/cache_control3/")
async def cache_control3(req, resp):
    now = datetime.datetime.now()
    resp.headers['Cache-Control'] = 'max-age=60'
    resp.headers['Expires'] = 'Thu, 01 Dec 1994 16:00:00 GMT'
    resp.html = api.template('cache_control3.html', time=now.strftime('%Y/%m/%d %H:%M:%S'))

if __name__ == "__main__":
    api.run(address='0.0.0.0')

コードと動作については、コメントを参考にしていただきたいが、以下のような感じではないだろうか。

  • 基本的には、Cache-Control ヘッダの max-age、または、Expires ヘッダがキャッシュを使うかどうかを決める。
  • ただし、キャッシュの有無にかかわらず、ie11 の場合はリロード、chrome の場合はアドレスバーからの入力、または、リロードで、リクエストが送信される。
  • ie11 の場合は、http-equiv で Expires を設定すると、ヘッダで指定した(もしくは指定しなかった)キャッシュ期間設定を変更することができる。
  • chrome の場合は、http-equiv で Expires は変更されない。

検証コード

動くソースは、cachetest に置いているのでご参考まで。

docker を使う場合

docker build -t cachetest .
docker run -it -p 5042:5042 cachetest

docker を使わない場合

pip3 install -r requirements.txt
python3 api.py

続き: Cache-Control を実際に試してみる(その2)