Flaskで画像やscriptやCSSが更新前のキャッシュで読み込まれるのを回避する(パラメータの設定)

テーマはPythonのFlaskでファイルのパラメータを設定する pythonの話

ブラウザは、画像ファイル、scriptファイル、CSSファイルについてキャッシュがあればそちらを表示してしまいます。
これらを更新しても、キャッシュが読み込まれるとせっかくの更新が反映されません。

Flaskを使えば簡単に実装可能

PCであればスーパーリロードすれば更新後のファイルを読み込みますが、サイト訪問者はページが更新されているかどうかも分からないので更新前のキャッシュが読み込まれると古いページのままと認識されてスーパーリロードしてくれることはほぼありません。

また、Iphoneでは簡単にスーパーリロードはできません。

更新前のファイルがキャッシュで読み込まれるのを防止するために、画像ファイルや、scriptファイル、CSSファイルは、ファイル名の後ろに?でパラメータをつけることでファイルの更新情報をブラウザに送ることができます。

ブラウザで保存しているキャッシュファイルとパラメータも含めて一致すればキャッシュが、不一致であればサーバのファイルを読み込んでくれます。

更新の都度、手作業でhtmlのCSSファイルなどにパラメータを追加、変更するのは面倒なのでFlaskで実装することにしました。

方法としては以下を考えました。考え方はapp.pyのなかでファイルの更新日時を取得してそれをパラメータとして設定します。

  • htmlには、「url_for(・・)」で画像のパスを生成を生成しつつ、app.pyからパラメータを追加する
  • app.pyからhtmlに画像パス+パラメータの文字列を送り込む
  • 面倒なのでパラメータは一括で同じものを使う

以下は、画像ファイルの例ですが、CSSファイル、JavaScriptファイルでも同様です。

htmlでは「for(・・)」で画像のパスを生成し別途パラメータを追加する

htmlでは、url_forを使って画像パスを生成し、その後ろにparaという変数でパラメータの文字列を追加します。

<img src={{ url_for('static', filename='sample.png')+para }}>

Flaskのapp.pyではファイルから最終更新日時を取得してパラメータ文字列(”?更新日時”)にしてparaとして送り込みます。

これでhtmlで生成したファイルパスと合わせて「ファイルパス?更新日時」になります。

import os
from datetime import datetime
from flask import Flask, render_template, url_for,
@app.route('/')
def index():
    # ファイルのパス
    file = "static/sample.png"
    # ファイルの更新時間をタイムスタンプで取得する
    time_stamp = os.path.getmtime(file)
    # ファイルの最終更新日時を分までにフォーマットする
    modified_time = datetime.fromtimestamp(time_stamp).strftime("%Y%m%d%H%M")
    # パラメータの文字列として?最終更新日にする
    para = "?" + modified_time 
    return render_template('index.html', para=para )

この方法だとhtmlでファイル名を指定して、app.pyでもファイル名を指定しているのが気になります。

htmlに画像パス+パラメータの文字列を送り込む

どうせ、app.pyでファイル名を指定するので、app.pyから「画像パス?更新日時」という文字列を送ってしまったほうがスッキリするので、そちらでもやってみました。

<img src={{ sample_path }}>

Flaskのapp.pyではファイル+最終更新日時の文字列をsample_pathとして送り込みます。

import os
from datetime import datetime
from flask import Flask, render_template, url_for,
@app.route('/')
def index():
    # ファイルのパス
    file = "static/sample.png"
    # ファイルの更新時間をタイムスタンプで取得する
    time_stamp = os.path.getmtime(file)
    # ファイルの最終更新日時を分までにフォーマットする
    modified_time = datetime.fromtimestamp(time_stamp).strftime("%Y%m%d%H%M")
    # 送り込む文字列として画像パス?最終更新日にする
    sample_path = file + "?" + modified_time 
    return render_template('index.html', sample_path = sample_path )

この方法だと、ファイルパスを1回しか書かなくていいのでスッキリするように思います。

ただ、デメリットとしてhtml側ではファイルパスが分からないので何のファイルかをapp.pyを見にいかないと分からないです。

こちらも、同様にファイルが多ければrender_templateの引数がやたら多くなりそうです。

こちらの日経平均のポイント&フィギュアの記事での画像だけのリンク先はこの方法を使っています。
開発者ツールで見てもらうと「”image/pf.png?202306142132″」という風に画像リンク+ファイル作成日時となっています。(ただ、どの方法でも出来上がりのhtmlでは同じです。)

url_forで画像パスを生成して共通のパラメータを送り込む

個別のファイルについて変更日時をとってパラメータとして送り込もうとするとファイル数が多くなると引数が多くなってややこしくなりそうです。
また、既存のhtmlのテンプレートに追加していこうと思うと面倒です。

そこで、staticフォルダで最も新しい日時を抽出して、それを共通のパラメータとして設定するのがいいかなと思っています。そこまでファイル数も多くないのであればファイル一つ更新したら他も更新されたことになるデメリットはそれほどないだろうと思います。

htmlでは、url_forで画像パスを生成して、paraとして共通のパラメータを追加します。

<img src={{ url_for('static', filename='sample.png')+para}}>
<img src={{ url_for('static', filename='sample2.png')+para}}>

Flaskのapp.pyでは、staticフォルダ内をforで回して最も新しい更新日時を取得して、それをパラメータに加工して、render_templateで送り込みます。

全ファイル共通のパラメータとして送り込むので引数が多くなるということはありません。

import os
from datetime import datetime
from flask import Flask, render_template, url_for,
@app.route('/')
def index():
    # staticフォルダのファイルの更新時間で最も新しいものを取得する。
    folder_path = "static"  # フォルダのパス
    latest_stamp = 0  # 最新の日時を保持する変数
    for file in os.listdir(folder_path):
        file_path = os.path.join(folder_path, file )
        if os.path.isfile(file_path):
            file_stamp = os.path.getmtime(file_path)
            if file_stamp > latest_stamp:
                latest_stamp = file_stamp
    # フォルダ内ファイルの最終更新日時を分までにフォーマットする
    modified_time = datetime.fromtimestamp(latest_stamp ).strftime("%Y%m%d%H%M")
    # パラメータの文字列として?最終更新日にする
    para = "?" + modified_time 
    return render_template('index.html', para  = para )

この方法だと、既存のテンプレートを修正する場合も「+para」を全部足していくだけで済みます。
また、htmlを見ればどのファイルを参照しているかも認識できるので、この方法がよさげかなと思っています。

コメント

タイトルとURLをコピーしました