38
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

.htaccessによるWebPの選択的レスポンスとその問題点と改善案

Last updated at Posted at 2018-10-06

nginx向けの設定をこちらに書きました。

Webサーバー上のJpegやPNGファイルについて、そのWebP版ファイルがある場合はそちらを返す(もちろんブラウザが対応している場合のみ)、という.htaccessの記述例はすでにいろいろ公開されています。

多少の差違はありますが、たいてい以下のような方法をとっています。

WebP-images-with-htaccess

※ 2018/10/31追記 Varyヘッダの送出条件に関するプルリクがマージされました。混乱するので、以前のコミットにリンク先を変更します。

コメントを日本語に置き換えて説明します。

WebP選択レスポンスでメジャーな.htaccess
<IfModule mod_rewrite.c>
  # Rewriteモジュールを有効にする
  RewriteEngine On

  # ブラウザから送信されるAcceptリクエストヘッダがimage/webpを含む場合のみ
  # 後続のRewriteRuleを適用する
  RewriteCond %{HTTP_ACCEPT} image/webp

  # WebP版のファイルがある場合のみ後続のRewriteRuleを適用する
  RewriteCond %{DOCUMENT_ROOT}/$1.webp -f

  # *.jpg、*.pngファイルを*.webpファイルに内部的にルーティングする
  # Content-Typeはimage/webpにして環境変数acceptを1にする
  RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1]
</IfModule>

<IfModule mod_headers.c>
  # 環境変数acceptが真の(上記RewriteRuleで1が設定された)場合、
  # CDNやキャッシュサーバーがWebPとJpeg/PNGをそれぞれキャッシュできるように
  # VaryレスポンスヘッダにAcceptを追加する
  Header append Vary Accept env=REDIRECT_accept
</IfModule>

<IfModule mod_mime.c>
  # 拡張子.webpファイルはContent-Typeとしてimage/webpを返す
  AddType image/webp .webp
</IfModule>

WordPressのEWWW Image Optimizerプラグインが提示する方法なのでメジャー感がありますが、改善案を示してみたいと思います。

改善案について

WebP版ファイルの命名規則の改善提案

個人的には元のファイルの拡張子も含めた上で、.webpを追加する方がよい気がします(例: image.jpg → image.jpg.webp)。

  • 将来WebPを直接出力するケースも増えるのではないか。そのとき、変換によって生成されたファイルなのか(image.jpg.webp)、オリジナルとして出力したWebPなのか(image.webp)区別をつけやすい。
  • ファイル名が重複したとき(logo.pngとlogo.jpgなど)、衝突を避けられる。

無意味な心配や、拡張子が続くのが嫌といった意見もあるかと思います。

.htaccessの配置箇所が固定される

メジャー版の.htaccessファイルは、DocumentRootに設置する前提になっています。例えば/imagesディレクトリに設置すると、1箇所書き換える必要があります。

RewriteCondの工夫でできないかなーと思いましたが、拡張子置き換えの場合は無理そうでした(アイデアがあればコメントでぜひ!)。

メジャーな.htaccessの変更点
  # WebP版のファイルがある場合のみ後続のRewriteRuleを適用する
  # サブディレクトリの場合はそのパスが必要
  RewriteCond %{DOCUMENT_ROOT}/images/$1.webp -f

WebPに置換したときのみVaryヘッダにAcceptを返している

※ Webサーバから直接画像を配信している場合は影響ありません。

これがちょっとマズくない?と思ったところで、ChromeなどからのアクセスによりRewriteRuleが適用されてWebP版のファイルに置き換えられたときのみ、レスポンスヘッダにVary: Acceptが含まれます。

Firefoxからのアクセス時はVary: Acceptが含まれません。

これって、運悪く最初のリクエストがFirefoxからあってVary: AcceptなしでJpegファイルを返すと、その後ChromeがアクセスしてもキャッシュヒットしちゃってWebPを返さないんじゃない?と思いました。

本来は**.jpgや.pngへのアクセス全体にVaryヘッダを返す**という実装の方が望ましいのではないでしょうか。そんなことないよ!という場合はご指摘ください。

CloudFrontの場合

試してみましたが、FirefoxにVaryを返さないメジャーな方法でも問題ありませんでした。先にFirefoxでアクセスしてその後ChromeでアクセスしてもJpeg、WebPがそれぞれ返されました。

CloudFrontでは、リクエストヘッダのホワイトリストにAcceptを追加しなければなりませんが(指定しないとそもそもオリジンにリレーされないので)、その作用かもしれません。

以上を踏まえた改善案

メジャー版に対する変更点・改善点は以下の通りです。

  • 変更点 image.jpg→image.webp ではなく image.jpg→image.jpg.webpを前提とします。
    • この変更があるのでEWWW Image Optimizerとはそのまま併用できません。
  • 改善点 DocumentRoot以外の任意のディレクトリに設置しても動作します。
  • 改善点 WebPが返却されなかった場合もVary: Acceptを返却します。
WebPの選択レスポンスのための私的改善版
<IfModule mod_setenvif.c>
  # WebP版があるかもしれない画像へのリクエストは全てVaryレスポンスヘッダを返す
  SetEnvIf Request_URI "\.(jpe?g|png)$" _image_request
</IfModule>

<IfModule mod_rewrite.c>
  # Rewriteモジュールを有効にする
  RewriteEngine On

  # ブラウザから送信されるAcceptリクエストヘッダがimage/webpを含む場合のみ
  # 後続のRewriteRuleを適用する
  RewriteCond %{HTTP_ACCEPT} image/webp

  # WebP版のファイルがある場合のみ後続のRewriteRuleを適用する
  # SCRIPT_FILENAMEを使うことでサブディレクトリの補完が不要に
  RewriteCond %{SCRIPT_FILENAME}.webp -f

  # *.jpg、*.pngファイルを*.webpファイルに内部的にルーティングする
  # Content-Typeはimage/webpにする
  # リダイレクト先は $0.webp でも可
  RewriteRule .(jpe?g|png)$ %{SCRIPT_FILENAME}.webp [T=image/webp]
</IfModule>

<IfModule mod_headers.c>
  # 環境変数_image_requestが真の(上記SetEnvIfが設定された)場合、
  # VaryレスポンスヘッダにAcceptを追加する
  Header append Vary Accept env=_image_request
</IfModule>

<IfModule mod_mime.c>
  # 拡張子.webpファイルはContent-Typeとしてimage/webpを返す
  AddType image/webp .webp
</IfModule>
38
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?