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

※ [nginx向けの設定](https://qiita.com/miyanaga/items/94447efae0bf767b9f2b)をこちらに書きました。

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

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

[WebP-images-with-htaccess](https://github.com/vincentorback/WebP-images-with-htaccess/tree/ab1cf2dec6a3f67872d0ed5087991d524d499bfa)

※ 2018/10/31追記 Varyヘッダの送出条件に関する[プルリクがマージ](https://github.com/vincentorback/WebP-images-with-htaccess/pull/5/commits/e375869d09c12abfe3d650a09896b3c0378e8339)されました。混乱するので、以前のコミットにリンク先を変更します。

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

```apache: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](https://ja.wordpress.org/plugins/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の工夫でできないかなーと思いましたが、拡張子置き換えの場合は無理そうでした(アイデアがあればコメントでぜひ！)。

```apache:メジャーな.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`を返却します。

```apache: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>
```