要約
- Amazon CloudFrontにおいてマルチオリジンを設定した場合、リクエストパスがオリジンへ転送されてしまう問題がある
- CloudFront Functionsをビューワーリクエストに設定することによって、パスの削除を行う。これにより、問題を解決する
- しかし、パスの削除によってキャッシュキーが競合する危険あり
- 同じくCloudFront Functionsによって削除したパスをクエリパラメータに追加する。これにより、問題を解決する
マルチオリジン設定時にリクエストパスがオリジンへ転送される問題
問題について
Amazon CloudFrontでは複数のオリジン(マルチオリジン)を設定することができます。
ビヘイビアにパスパターンを設定することで、「このパスにリクエストが来た時はオリジンA、あのパスにリクエストが来た時はオリジンB」といったように振り分けが可能です。
マルチオリジンを設定した際、リクエストパスがオリジンへのリクエスト時にも転送されるという問題が発生します。
例を示します。
オリジンサーバーとして以下の二つが存在するとします。
example1.com
example2.com
サーバーのドメイン/ファイル名
へアクセスすることでファイルをダウンロードすることができます。
(例: example1.com/sound.mp3
)
また、CloudFrontディストリビューションとして、以下が存在するとします。
foobar.cloudfront.net
このディストリビューションには以下のようなビヘイビアが設定されています。
- パスパターン:
/example1/*
, オリジンサーバー:example1.com
-
foobar.cloudfront.net/example1/*
へアクセスがあった場合、example1.com
がオリジンとなる
-
- パスパターン:
/example2/*
, オリジンサーバー:example2.com
-
foobar.cloudfront.net/example2/*
へアクセスがあった場合、example2.com
がオリジンとなる
-
この時、クライアントからhttps://foobar.cloudfront.net/example1/sound.mp3
へのリクエストを行います。
以下のようにリクエストが行われます。
クライアント(ビューワー)
|
| https://foobar.cloudfront.net/example1/sound.mp3
|
CloudFrontディストリビューション(foobar.cloudfront.net)
|
| https://example1.com/example1/sound.mp3
| ~~~~~~~~
オリジンサーバー(example1.com)
CloudFrontディストリビューションからオリジンサーバーへのリクエストURLに注目してください。
本来、オリジンサーバーに対してリクエストする際はサーバーのドメイン/ファイル名
としたい訳です。
しかし、クライアント→CloudFrontディストリビューションのリクエストパスが余計に転送されてきています。
これによりsound.mp3
を取得したいはずが、example1/sound.mp3
に対する意図しないリクエストが発生してしまいます。
解決方法
ビューワーリクエストにCloudFront Functionsを設定することで解決します。
CloudFront Functionsにてリクエストパスを削除するという対応を行います。
例えば、以下のようなCloudFront Functionsを作成し、それぞれのビヘイビアのビューワーリクエストに設定します。
// example1.comに対応するビヘイビアに設定するFunction
// ここはKeyValueStoreを使って外側から変更できるようにしても良い
const pathToDelete = '/example1'
function handler(event) {
const request = event.request;
request.uri = request.uri.replace(pathToDelete, '/');
return request;
}
// example2.comに対応するビヘイビアに設定するFunction
// ここはKeyValueStoreを使って外側から変更できるようにしても良い
const pathToDelete = '/example2' // example1.com版とはここが変わっているだけ
function handler(event) {
const request = event.request;
request.uri = request.uri.replace(pathToDelete, '/');
return request;
}
ちなみに、CloudFront Functionsではなく、Lambda@Edgeを利用することも可能です。
その際は、ビューワーリクエストではなくオリジンリクエストに設定します。
オリジンリクエストに設定した場合、後述の「キャッシュ競合の危険性」が発生しなくなるためです。
(今回CloudFront Functionsを利用しているのはコストが圧倒的に安いためです)
キャッシュ競合の危険性
問題の内容
CloudFront Functionsを利用してパスの削除を行うと、キャッシュが競合する危険が生まれます。
foobar.cloudfront.net/example1/sound.mp3
へアクセスした際、sound.mp3
のキャッシュキーは/sound.mp3
となります。
/example1/sound.mp3
ではありません。なぜなら、ビューワーリクエストのCloudFront Functionsで/example1
部分を削除してしまっているからです。
foobar.cloudfront.net/example2/sound.mp3
へアクセスした際、sound.mp3
のキャッシュキーは同じく/sound.mp3
となります。
したがって、foobar.cloudfront.net/example1/sound.mp3
とfoobar.cloudfront.net/example2/sound.mp3
のキャッシュが共通になってしまいます。
キャッシュが共通になってしまっても問題ない状況であれば良いのですが、それを避けたい場合、さらなる対処を講じる必要があります。
解決方法
CloudFront Functionsに処理を追加して、削除したパスをクエリパラメータとしてくっつけます。
クエリパラメータ名はなんでも良いですが、とりあえずここではdeleted_path
にしておきます。
foobar.cloudfront.net/example1/sound.mp3
↓
foobar.cloudfront.net/sound.mp3?deleted_path=/example1
(/example1
を削除してクエリパラメータをくっつける)
その上で、追加したクエリパラメータをキャッシュキーとして追加する必要があります。
ビヘイビアに設定してるキャッシュポリシーのキャッシュキー設定のクエリ文字列に、追加したクエリパラメータ名(ここではdeleted_path
)を追加します。
CloudFront Functionsのコード例
const pathToDelete = '/example1';
function handler(event) {
const request = event.request;
// URIから指定されたパスを削除
request.uri = request.uri.replace(pathToDelete, '/');
// クエリパラメータを追加
const queryString = request.querystring || '';
const newQueryString = queryString
? `deleted_path=${pathToDelete}&${queryString}`
: `deleted_path=${pathToDelete}`;
request.querystring = newQueryString;
return request;
}
ちなみに、リクエスト時に既にクエリパラメータを付加している場合はdeleted_path
を一番最初のクエリパラメータとしてくっつけます。
foobar.cloudfront.net/example1/sound.mp3?param=val
↓
foobar.cloudfront.net/sound.mp3?deleted_path=/example1¶m=val
なぜかというと、キャッシュ削除のことを考えるとその方が都合が良いからです。
CloudFrontにおけるキャッシュ削除時には*
(ワイルドカード)を利用することができますが、一番最後にしか指定することができません。
deleted_path
が最初に来ていることによって、以下のようにキャッシュを削除することができます。
/sound.mp3?deleted_path=/example1&*
(/example1
配下のキャッシュを全て削除する)
最後に
CloudFrontは機能としてはシンプルに保ち、それ以上のことをしたければCloudFront FunctionsやLambda@Edgeで補う、という方針だとは思いますが、流石にこれは、、、という気持ちになりました。