ちょっと話題の記事

[新機能] Amazon CloudFrontがCORSに対応しました

2014.07.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CloudFrontに大量アップデートがやってきました。このエントリーではCORSに関して解説します。 Amazon CloudFront Adds Device Detection, Geo Targeting, Host Header Forwarding, CORS Support, and more!

これまでの課題

これまでもCloudFrontはOriginのレスポンスにAccess-Control-Allow-Originヘッダが含まれる場合はキャッシュしてくれました。ただし、ブラウザのドメインとCloudFrontのドメインが一致する場合はCORSにする必要が無いためブラウザはOriginヘッダをリクエストに含めません。結果としてレスポンスにAccess-Control-Allow-Originヘッダが含まれません。そうすると、最初にCloudFrontにアクセスするクライアントが同一ドメインの場合はAccess-Control-Allow-Originヘッダがキャッシュされず、その後他のドメインからOriginヘッダを含めてリクエストを投げてもCloudFrontのキャッシュにはAccess-Control-Allow-Originヘッダが存在しないためエラーとなっていました。

最初のアクセス Access-Control-Allow-Originヘッダ 次回以降がCORSの場合
同一ドメイン キャッシュされない エラーとなる
ドメインが一致しない(CORS) キャッシュされる 動作する

特に問題となるのがS3の場合でした。EC2インスタンス上のWebアプリケーションであればOriginヘッダの有無に関係なくAccess-Control-Allow-Originヘッダを返すこともできますが、S3のCORS機能は仕様通りの実装であるためOriginヘッダが存在しない場合はAccess-Control-Allow-Originヘッダを返しません。具体的には、以下の様な挙動になります。まずはOriginヘッダをつけない(同一ドメイン)の場合です。curlコマンドでStatic Website Hostingを有効にしたS3バケットhoge.example.com.s3-website-ap-1.amazonaws.comに対してアクセスします。

$ curl -I hoge.example.com.s3-website-ap-1.amazonaws.com/hoge
HTTP/1.1 200 OK
x-amz-id-2: M6gdDKXWgtxdJN+gB3Eiy6eYMMqYGh10YU/Rop8GtxWUDve1fyqEZxMVDJlcC1cj
x-amz-request-id: C886951A05948413
Date: Sat, 28 Jun 2014 09:04:00 GMT
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Content-Type: application/octet-stream
Content-Length: 0
Server: AmazonS3
Connection: keep-alive

普通にレスポンスが返ってきますね。では次にCORSとなるように-HオプションでOriginヘッダをつけてみます。

$ curl -I -H "Origin: http://fuga.example.com" hoge.example.com.s3-website-ap-northeast-1.amazonaws.com/hoge
HTTP/1.1 200 OK
x-amz-id-2: 5UQ/FD+mJlLGadzQTLqnMSbi+dtgPv4jvwjNxaF41stdvqWC1sAFfBXEIPnWDDHF
x-amz-request-id: E778FEF9A73981C4
Date: Sat, 28 Jun 2014 09:05:23 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Content-Type: application/octet-stream
Content-Length: 0
Server: AmazonS3
Connection: keep-alive

Access-Control-Allow-Originヘッダ、Access-Control-Allow-Methodsヘッダ、Varyヘッダが追加されていることがわかると思います。

前述のS3にアクセスした場合の挙動を押さえた上で、今度はCloudFront経由でS3にアクセスしてみたいと思います。ここではCORS設定を行っていないCloudFrontとしてcors.disabled.cloudfront.netを用意した前提で先ほどと同じようにcurlコマンドでアクセスしてみます。

$ curl -I cors.disabled.cloudfront.net/hoge
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 0
Date: Sat, 28 Jun 2014 09:49:46 GMT
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Miss from cloudfront
Via: 1.1 ce5b2b9206bda1204c308b238ba5622a.cloudfront.net (CloudFront)
X-Amz-Cf-Id: zahIdNc5_2t7dbPGEGBt0evQ4eHhUlBKeXcQpr2nZEnkUwvRnhN0nw==
Connection: keep-alive

S3の場合と同様に普通にレスポンスが返ってきますね。では次にCORSとなるように-HオプションでOriginヘッダをつけてみます。

$ curl -I -H "Origin: http://fuga.example.com" cors.disabled.cloudfront.net/hoge
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 0
Date: Sat, 28 Jun 2014 09:49:46 GMT
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Accept-Ranges: bytes
Server: AmazonS3
Age: 8
X-Cache: Hit from cloudfront
Via: 1.1 ee0dd13be5688393446d3eadfad27909.cloudfront.net (CloudFront)
X-Amz-Cf-Id: pIcp6BnKDPJYRRAwhyljcB8YOweqoU8FxInvxiuoMG4SFByp6wK_1Q==
Connection: keep-alive

S3の場合とは異なり、Access-Control-Allow-Originヘッダなどが返されません。理由としてはX-CacheヘッダにあるようにCloudFrontがキャッシュを返しているためです。

新機能として追加されたこと

OriginヘッダをOriginサーバー側に転送できるようになり、その際にOriginヘッダ毎にレスポンスをキャッシュしてくれるようになりました。この結果、同一ドメインの場合はAccess-Control-Allow-Originヘッダなしのレスポンスがキャッシュされ、ドメインが一致しないCORS対象となる場合はAccess-Control-Allow-Originヘッダ付きのレスポンスがキャッシュされるようになりました。

以下にS3オリジンの場合のマネジメントコンソールのスクリーンショットを貼ります。CloudFrontを作成する際に[Forward Headers]を[Whitelist]に変更し、[Whitelist Headers]でOriginヘッダを[Add>>]ボタンで追加します。 CORS用にCloudFrontでOriginヘッダ毎のキャッシュ設定を行う

動作確認

では実際に[Forward Headers]を設定することで挙動が変化したのか確認したいと思います。今度は[Forward Headers]を設定したcors.enabled.cloudfront.netというCloudFrontを用意した前提で先ほどと同じ手順について確認します。

$ curl -I cors.enabled.cloudfront.net/hoge
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 0
Date: Sat, 28 Jun 2014 11:16:08 GMT
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Miss from cloudfront
Via: 1.1 ee0dd13be5688393446d3eadfad27909.cloudfront.net (CloudFront)
X-Amz-Cf-Id: UlkEH-6PiMyTC42Kzq-EBhPLeefBvvDnJvnEMUjhg7ULR3119UMzsg==
Connection: keep-alive

S3の場合と同様に普通にレスポンスが返ってきますね。では次にCORSとなるように-HオプションでOriginヘッダをつけてみます。

$ curl -I -H "Origin: http://fuga.example.com" cors.enabled.cloudfront.net/hoge
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 0
Date: Sat, 28 Jun 2014 11:15:53 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD
Last-Modified: Sat, 28 Jun 2014 09:03:34 GMT
x-amz-expiration: expiry-date="Mon, 30 Jun 2014 00:00:00 GMT", rule-id="Rule for the Entire Bucket"
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Accept-Ranges: bytes
Server: AmazonS3
Vary: Origin
X-Cache: Miss from cloudfront
Via: 1.1 b291b21c612e764f4bf23bc28c9e37f5.cloudfront.net (CloudFront)
X-Amz-Cf-Id: hA9qseHsYyHqklRcShNRdYFuU_MO-Gs6kLQYDqYRTdzKkYC3wmZeTg==
Connection: keep-alive

S3の場合と同様にAccess-Control-Allow-Originヘッダなどが返ってきていますね!

まとめ

これでやっとCloudFront+S3でCORSに対応することが出来ます。S3についてはだいぶ前からCORS対応していたのですが、CloudFrontと組み合わせた際に問題となるケースがあったので待ち望んでいた機能でした。

今回のCloudFrontの機能追加はリクエストヘッダをKeyとしてキャッシュを制御するという非常にシンプルなものですが、応用範囲が広いことについて驚きました。詳細はアナウンスページからもリンクがありますが、以下のページをご参照下さい。 Configuring CloudFront to Cache Objects Based on Request Headers - Amazon CloudFront