CFでカスタムオリジンをApacheに向けている時のディレクトリ名スラッシュ補完時にオリジンのURLに転送されてしまう問題を解決してみた

長い...。つまり、オリジンサーバ
http://origin.example.com
に対して、CFのDistoributionを
http://cached.example.com
みたいに設定していたとして、
http://cached.example.com/sub
みたいなディレクトリ名に/を付けずにアクセスした際に、ブラウザのURLが
http://origin.example.com/sub/
となってしまう問題を解決する設定を考えてみました。

原因

まず、なぜ起こるのかを考えて見ましょう。

$ curl -v http://cached.example.com/sub
* About to connect() to cached.example.com port 80 (#0)
*   Trying 216.137.53.30... connected
* Connected to cached.example.com (216.137.53.30) port 80 (#0)
> GET /sub HTTP/1.1
> User-Agent: curl/7.19.7 (i686-pc-linux-gnu) libcurl/7.19.7 NSS/3.12.6.2 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
> Host: cached.example.com
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 301 Moved Permanently
< Date: Wed, 25 Apr 2012 17:14:37 GMT
< Server: Apache/2.2.22 (Amazon)
< Location: http://origin.example.com/sub/
< Content-Length: 233
< Content-Type: text/html; charset=iso-8859-1
< Age: 825
< X-Cache: Hit from cloudfront
< X-Amz-Cf-Id: LdWjKWqdaIuOwWgHQ86B8tvef8Vmofv-TqXtEaggtVRKPjcvzSyMWg==,qRrOXfbgF6Sic5Z27w99TKqTyFi24Ibq5Ptz2Uk1XYQjqHarGzapzQ==
< Via: 1.0 afc2ff739d4cc96648fcd7eae0aa3c71.cloudfront.net (CloudFront)
< Connection: close
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://origin.example.com/sub/">here</a>.</p>
</body></html>
* Closing connection #0

はい、思いっきり、"Location: http://origin.example.com/sub/" と言われています。CFは素直に受け取ったままブラウザに渡します。
ブラウザは言われたとおりに http://origin.example.com/sub/ に移動するので、

あー、オリジンサーバのURLが出ちゃってますね。

対処方法を考える

mod_rewriteを使いましょう。
要するに、

  • CFからアクセスが来たときにだけ
  • ディレクトリに対するアクセスで(ファイルに対するアクセスではない)
  • URIの一番後ろが/じゃなかった時に

キャッシュ用のFQDNへ飛ばしたらいいのでは。

やってみる

RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} ^Amazon\ CloudFront$
RewriteRule ^/([^\.])+[^/]$ http://cached.example.com%{REQUEST_URI}/ [R=301]
  1. まずUAをチェックして、CFのクライアントである場合に
  2. パスの中にドットが含まれない(≒拡張子がないのでファイルではない可能性が高い)、かつ終わりがスラッシュ以外の文字である

場合にキャッシュ用URLの当該ディレクトリへ/付きでリダイレクトします。

$ curl -v http://cached.example.com/sub
* About to connect() to cached.example.com port 80 (#0)
*   Trying 216.137.53.96... connected
* Connected to cached.example.com (216.137.53.96) port 80 (#0)
> GET /sub9 HTTP/1.1
> User-Agent: curl/7.19.7 (i686-pc-linux-gnu) libcurl/7.19.7 NSS/3.12.6.2 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
> Host: cached.example.com
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 301 Moved Permanently
< Date: Wed, 25 Apr 2012 17:37:11 GMT
< Server: Apache/2.2.22 (Amazon)
< Location: http://cached.example.com/sub/
< Content-Length: 233
< Content-Type: text/html; charset=iso-8859-1
< X-Cache: Miss from cloudfront
< X-Amz-Cf-Id: 75yDzxwfUh5C7Q3ELXyqWfXrSSMaWSxufnJps3RZDjy5_fl7F2J8xw==,J7xP0VptHjSq3_GFYTIIZ29PyXrlqGOh_zlhKrbYDIl3ffAvvbZ14w==,UagMrvsy7JXwBR5vNXPcZkpMiH5Px6XjxEWqTejV23S7HZqe5qeHFg==
< Via: 1.0 8362ec81036ea7c5f40b353d26ad89cb.cloudfront.net (CloudFront)
< Connection: close
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="http://cached.example.com/sub/">here</a>.</p>
</body></html>
* Closing connection #0

お、今度はちゃんと "Location: http://cached.example.com/sub/" となっているので、大丈夫そうですね。
ブラウザで http://cached.example.com/sub にアクセスしてみます。

FQDNはcached.example.comのまま、subの後ろに/が付きました!
しかも、UAをちゃんとチェックしているため、オリジンにダイレクトにブラウザでアクセスした場合には、

と、ちゃんとオリジン側のディレクトリ名に/を付けたURLに移動します。
コンテンツ制作時やアップロード確認時などにキャッシュURLへ飛ばされたりしません。

問題点

  1. ディレクトリ名にドットが入っているとアウト
  2. QUERY_STRINGやPATH_INFOを考慮していない

Rewriteルールがんばれば、なんとかなりそうな気も。

これって

CDPの種になりませんかね?(チラッ
Smart Custom Originパターンとか?