Edited at

S3+CloudFrontでWebフォントを利用する(FireFox対応)

More than 5 years have passed since last update.

結論からいうと、以下の3点で対応可能です。

1.S3 のバケットに CORS の設定を行う。

 S3 Management Console で対象バケットを開き、右上の Properties ボタンを押します。

 項目がいくつか現れますが、その中の Permissions を展開して、Edit(Add) CORS Configuration

 ボタンを押し、表示される CORS Configuration Editor に以下のように入力して saveします。

 AllowedOrigin は Web フォントを使用するリクエスト元となるドメインです。

 必要な数だけ列挙します。

<?xml version="1.0" encoding="UTF-8"?>

<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>http://jobhub.jp</AllowedOrigin>
<AllowedOrigin>https://jobhub.jp</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Content-*</AllowedHeader>
<AllowedHeader>Host</AllowedHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

2.CloudFront の設定で、クエリ文字列の送信を許可する。

 対象の Distribution -> Behaviors -> Edit -> Forward Query Strings を Yes に変更する。

 デフォルトでは No になっています。

3.WebフォントのCSS定義に、FireFox専用の定義を追加する。


/* 既存のWebフォントの定義ここから */
@font-face {
font-family: "icomoon";
font-style: normal;
font-weight: normal;
src: url("//{your.cloudfront.net}/path/font.eot"),
src: url("//{your.cloudfront.net}/path/font.eot?#iefix") format("embedded-opentype"),
url("//{your.cloudfront.net}/path/font.woff") format("woff"),
url("//{your.cloudfront.net}/path/font.ttf") format("truetype"),
url("//{your.cloudfront.net}/path/font.svg#icomoon") format("svg");
}
/* 既存のWebフォントの定義ここまで */

/* Firefox hack start */
/* 非SSL用 */
@-moz-document url-prefix("http://jobhub.jp") {
@font-face {
font-family: "icomoon";
font-style: normal;
font-weight: normal;
src:url("//{your.cloudfront.net}/path/font.woff?firefox01") format("woff"),
url("//{your.cloudfront.net}/path/font.woff?firefox02") format("woff"),
url("//{your.cloudfront.net}/path/font.ttf?firefox01") format("truetype"),
url("//{your.cloudfront.net}/path/font.ttf?firefox02") format("truetype");
}
}
/* SSL用 */
@-moz-document url-prefix("https://jobhub.jp") {
@font-face {
font-family: "icomoon";
font-style: normal;
font-weight: normal;
src:url("//{your.cloudfront.net}/path/font.woff?firefox_ssl01") format("woff"),
url("//{your.cloudfront.net}/path/font.woff?firefox_ssl02") format("woff"),
url("//{your.cloudfront.net}/path/font.ttf?firefox_ssl01") format("truetype"),
url("//{your.cloudfront.net}/path/font.ttf?firefox_ssl02") format("truetype");
}
}
/* Firefox hack end */

FireFox専用の定義は、Web フォントのリクエスト元となるドメイン×スキーマの数だけ必要です。

Job-Hub ではリクエスト元ドメインは jobhub.jp だけですが、http と https 両方でアクセスされるのでふたつとも定義しています。

プログラム言語や使用する Web フォントに関わらず同様の定義で対応できると思いますが、もともとの @font-face の定義部分は若干違うので、適宜読み替えてください(サンプルコードは icomoon 使用のもの)。

Bootstrap3 でも Font-Awesome でも似たような記述になっているはずです。

設定変更後のテストは、以下のようなコマンドを利用しましょう。


  • S3向けのCORS テスト

curl -I -H 'Origin: http://jobhub.jp' https://{bucketname}.s3.amazonaws.com/path/font.woff

こんなレスポンスヘッダが返ってくれば成功です。

HTTP/1.1 200 OK

x-amz-id-2: {snip}
x-amz-request-id: {snip}
Date: Mon, 11 Nov 2013 07:47:23 GMT
Access-Control-Allow-Origin: http://jobhub.jp
Access-Control-Allow-Methods: GET, HEAD
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Vary: Origin, Access-Control-Request-Headers, Access-Control-Request-Method
Cache-Control: public, max-age=31557600
Expires: Thu, 06 Nov 2014 14:55:40 GMT
Last-Modified: Wed, 06 Nov 2013 08:55:41 GMT
ETag: "ef971c287ad279d9c9fc0bfa116629b3"
Accept-Ranges: bytes
Content-Type: application/font-woff
Content-Length: 28964
Server: AmazonS3


  • CloudFront 向けのテスト

curl -I -H 'Origin: http://jobhub.jp' https://{your.cloudfront.net}/path/font.woff?test01

HTTP/1.1 200 OK

Content-Type: application/font-woff
Content-Length: 28964
Connection: keep-alive
Date: Fri, 08 Nov 2013 10:52:29 GMT
Access-Control-Allow-Origin: http://jobhub.jp
Access-Control-Allow-Methods: GET, HEAD
Access-Control-Max-Age: 3000
Access-Control-Allow-Credentials: true
Cache-Control: public, max-age=31557600
Expires: Thu, 06 Nov 2014 14:55:40 GMT
Last-Modified: Wed, 06 Nov 2013 08:55:41 GMT
ETag: "ef971c287ad279d9c9fc0bfa116629b3"
Accept-Ranges: bytes
Server: AmazonS3
Age: 248201
X-Cache: Miss from cloudfront
Via: 1.1{snip}.cloudfront.net (CloudFront)
X-Amz-Cf-Id:{snip}

後述していますが、CloudFront はレスポンスヘッダまで含めてキャッシュしてしまうので、設定が正しくても Access-Control-Allow-Origin: ヘッダを含んだレスポンスを返してくれないことがあります。

この手の設定ではトライアンドエラーが常なので、結構ハマりやすいポイントです(はまりました)。

CloudFront へのテストでは、例にあるように毎回異なるクエリ文字列をつけてください。

デタラメで構いません。

X-Cache: ヘッダが miss になっていれば成功、hit になっていれば失敗と考えておくと間違いがないでしょう。


解説



なぜこんな対応が必要なのか?

Same Domain Policy(Same Origin Policy)という考え方があります。

詳細についてはこの辺りを読んでいただいた方が早いです。

Same-Origin Policy とは何なのか。 - 葉っぱ日記

このポリシーが採用されているのって Cookie や Ajax(XHR) あたりだよね、って認識が一般的だと思いますが、FireFox に限ってはWebフォントに対しても適用しています。

すべてのリソースを同一ドメインから返却しているような環境であれば何も考える必要はないのですが、CSSやJS、画像などの静的ファイルを別ドメインから配信している場合、この問題に直面します。

Chrome や IE では起きない問題なので、割と気づかずにいることが多いかもしれません。

ちなみにここ Qiita でも Web フォントが使われていますが、ドメインが同じなのでこの記事のような対応は特に不要なようですね。


なぜ CORS の定義を S3 に行うのか?

CloudFront はただのキャッシュ的動作を行うので、ヘッダの生成元はリソースの配布元(オリジンサーバ)であるS3が担当しています。

オリジンサーバを S3 ではなく EC2 などその他のサーバに設定している場合は、そちらで CORS 対応を行う必要があります。

例えば heroku + Rails がオリジンサーバの場合は、rack-cors を導入することで対応できると思います。

EC2 上の Apache であれば、 httpd.conf の修正で対応できるでしょう。


そもそもCORS ってなに?

Same Domain Policy の適用例外の設定です。

詳細はこちらを参照。

HTTP access control (CORS)

通常であれば同じドメインからのみしかリソースリクエストを受け付けないところ、あらかじめ設定されているドメインからのリソースリクエストであれば受け付けるようにすることが可能です。

Job-Hub の場合、CloudFront 上の Web フォントへのリクエストを、CloudFront のドメインではない http://jobhub.jphttp://jobhub.jp からのリソースリクエストを許容する設定を行っています。

CORS のレスポンスヘッダは、UA が Origin: ヘッダを発行した時のみ返却されます。

CORS のレスポンスヘッダのうち特に重要なのは Access-Control-Allow-Origin: ですが、クライアントからCORS リクエストがない限り生成されないので、注意が必要です。

あと、S3の仕様として、Access-Control-Allow-Origin: ヘッダに * がつくことはありません。

オリジンサーバによっては * を返却するものもあるようですが、S3の場合は常にリクエストヘッダの Origin: と同じものが返却されます。

ちなみに、Origin: ヘッダで指定されるドメインはリソース要求元ドメインを意味します。

オリジンサーバとは意味が違うので、注意してください。


なぜ CSS 側の修正が必要なの?

CloudFront はレスポンスヘッダまでキャッシュしてしまいます。

同一のリクエストURL の場合、同一のレスポンスヘッダと同一のリソースを返却します。

リクエストURL 以外のリクエストヘッダを考慮しません。

例えば IE で //{your.cloudfront.net}/path/font.woff へのアクセスが発生した場合、IE は Web フォントに CORS は不要なので、Origin: ヘッダを付けずにリクエストを行います。

CloudFront は受け取ったリクエストヘッダをそのまま転送する形でS3へリソースを取得しにいき、レスポンスヘッダごとキャッシュしてしまいます。

当然キャッシュされたヘッダには、CORS ヘッダは含まれていません。

この後にFireFoxで //{your.cloudfront.net}/path/font.woff へアクセスした場合、リクエストヘッダには Origin: ヘッダがついていますが、CloudFront はキャッシュされたリソースとヘッダをそのまま返すので、CORS ヘッダは含まれていません。

結果、FireFox は Web フォントをきちんと取得するものの、利用できないと判断してページ表示に使用してくれない、という現象が発生する訳です。

このような事態に対処するため、以下のような対応が必要になっています。

+ CloudFront でクエリ文字列の使用を許可する。

+ FireFox 専用の @font-face 定義をクエリ文字列付きで、リソースリクエスト元の数だけ追加する。

S3以外をオリジンサーバに使用しており、Access-Control-Allow-Origin: ヘッダに * を返却できる環境であれば、FireFox専用の定義はひとつだけ(url-prefixの定義なし)で大丈夫だと思います。

なお、FireFox の定義に woff と ttf の定義が二つずつありますが、本当に必要なのは一番上の woff の定義だけです。

残り三つの定義は予備というか保険で設定しているだけ。


さいごに


調べるの大変でした。。。

ローカル環境では発生しない現象だし、FireFox って画面表示の乱れとかってあんまり起きないので、Web フォントの代わりに麻雀牌みたいな文字が表示されてるのに気づいたのは本番実装時だったりして。

調査の上で非常に参考になったのは以下のサイト。

Amazon S3 CORS (Cross-Origin Resource Sharing) and Firefox cross-domain font loading - stack overflow

AWS Forum:A CORS configured S3 origin in CloudFront

一番心配なのは FireFox 向けの CSS ハック部分だったりもします。

これがうまく行かない場合は、大本の @font-face の woff や ttf の下にクエリ文字列付きを2、3個付けてあげれば、多分どうにかなりますかねえ。。

UA は自分が使えるフォントを上から順番に試すようにできているようなので。

問題に直面してはじめて調べて知ったことが多く、間違いや無駄が含まれている可能性があります。

お気づきの方がいらっしゃったら、お気軽にご指摘くださいませ。

長文を最後までお読みいただきありがとうございます。