ロコガイド テックブログ

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

「地域のくらしを、かしこく、たのしく」する、株式会社ロコガイドの社員がいろいろな記事を書いています。

デバイスの種類ごとに表示の異なるサイトにCloudFrontを導入した話

はじめに

こんにちは、サービス基盤グループのfukajunです。各種サービスのインフラ全般を担当しています。 先日、弊社で運営するメディアサイトにCloudFrontを導入しました。 対象のサイトは、PC/スマホ/アプリそれぞれに表示が異なるものでした。 導入にあたっての導入前の振る舞い、CloudFrontでの実現方法、あわせて進めるにあたってのTIPSについて書きたいと思います。

デバイスごとの出し分け

PC/スマホ

まずはデバイスの種類ごとの表示の出しわけです。もともとはアプリケーション側で PC/スマホ/アプリぞれぞれのuser-agentごとに表示を切り替えていました。 CloudFrontでは、デバイスを判定した結果として以下のような特定のヘッダーを設定してくれます。 このヘッダーをCloudFrontのキャッシュキーとして追加することでPC/スマホのキャッシュを わけることができました。

参考に、デバイス判定でCloudFrontが追加してくれるヘッダーを載せておきます。

CloudFront-Is-Android-Viewer - ビューワーが Android オペレーティングシステム搭載のデバイスであると CloudFront が判断すると、true に設定します。
CloudFront-Is-Desktop-Viewer - ビューワーがデスクトップデバイスであると CloudFront が判断すると、true に設定します。
CloudFront-Is-IOS-Viewer – ビューワーが iPhone、iPod touch、その他の iPad デバイスなど、Apple モバイルオペレーティングシステム搭載のデバ イスであると CloudFront が判断すると、true に設定します。
CloudFront-Is-Mobile-Viewer - ビューワーがモバイルデバイスであると CloudFront が判断すると、true に設定します。
CloudFront-Is-SmartTV-Viewer - ビューワーがスマートテレビであると CloudFront が判断すると、true に設定します。
CloudFront-Is-Tablet-Viewer - ビューワーがタブレットであると CloudFront が判断すると、true に設定します。

参考: ビューワーの場所を特定するためのヘッダー

アプリについて

同サイトにはアプリも存在しており、スマホ版に似たスタイルをウェブビューで表示しています。特殊な対応をせずCloudfrontのデバイス判定に任せた場合、もちろんアプリという判定はないのでスマホと同様のページが表示されます。 もともとアプリケーション側では、アプリからのリクエストでは、user-agentヘッダーに特定の文字列が含まれているかどうかでアプリと判定をしていました。Cloudfront側でも、user-agentヘッダーをキャッシュキーとして含めることで表示をだしわけることができますが、値の種類が多すぎてスマホやPCのキャッシュヒット率が下がってしまいます。 そこで、user−agentヘッダーに特定の文字が含まれていればx-app-nameヘッダーという独自のヘッダーを追加し、このヘッダーをキャッシュキーとして使うことにしました。これについては、 Lambda@EdgeもしくはCloudFront Functionsを使うことでできることがわかったのですが、実際には次の理由からCloudFront Functionsを使って行うことにしました。 参考:関数を使用してエッジでカスタマイズ

  • CloudFrontにリクエストがいく手前での処理であること(ビューワーリクエスト)
  • 特定の文字が含まれているかチェックする処理ですぐ終わる(1ミリ秒以下)
  • コード量も少ない(10KB以下)
  • 簡単な処理のためメモリ使用量も少ない(2MB以下)
  • Lambda@Edgeと比較してリクエストあたりの料金が安い

実装した内容について

function handler(event) {
    var request = event.request;
    var userAgent = request.headers['user-agent'];
    if(userAgent && userAgent.value.match(/XxxxxApp/)) {
        request.headers['x-app-name'] = {value: 'Xxxxx'};
    }
    return request;
}

Lambda@Edgeを利用したキャッシュ動作のデバッグ

上記の通り、CloudFrontのデバイス判定を利用してデバイス種類ごとの出し分けを行っていましたが 本番環境に導入するとスマホでアクセスしたときに、PC用の表示になってしまう現象が起きました。 なかなか状況がつかめずに困っていましたが、デバッグにLambda@Edgeを利用することで状況をつかむことできました。 デバッグ用の処理は、オリジンがレスポンスをCloudFrontに対して返却するオリジンレスポンスのタイミングに 設定します。 このタイミングであれば、CloudFrontが付与している上記のヘッダーや、アプリケーションが返しているレスポンスヘッダーやボディに対してもアクセスすることができ、内容をログに出力して確認することができます。 またこの方法であれば、アプリケーションへのデバッグ用コードの追加が不要です。

オリジンレスポンスに一時的に設定したLambda@Edgeのコードサンプル

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

const querystring = require('querystring');
export const handler = async (event, context, callback) => {
    const request = event.Records[0].cf.request;
    console.log('Method:', request.method, 'ClientIp:', request.clientIp, 'Uri:', request.uri, 'Headers:', JSON.stringify(request.headers));
    callback(null, request);
};

ちなみにデバッグした結果、たまにアクセスのあるbotのuser-agentに対し判定結果としてCloudFrontがCloudFront-Is-Mobile-Viewerヘッダーをtrueで返していることが判明しました。 通常、アプリケーションがCloudFrontが判定したヘッダーをみて表示を出し分けるように変更していれば問題になりませんが、今回はできるだけアプリケーションコードに手を入れない形で進めていたため、アプリケーション側ではCloudFrontの判定結果を参照することなく、以前のまま独自でデバイス判定を行い、このbotに関しては、CloudFront側の判定とは異なりるPCであると判定したため、結果スマホのためのキャッシュにPC版の表示がキャッシュされてしまったのでした。実際に判定内容などを目で見える形にしてみるのは大切ですね。目でみてデバッグするのは大切ですね。 しかしiOSでもAndroidでもないものがモバイルと判定されるとは思っていなかったので、予想もしていませんでした。 今回は、CloudFront-Is-Mobile-ViewerのかわりにCloudFront-Is-IOS-ViewerCloudFront-Is-Android-Viewerをキャッシュキーとして利用することでひとまず現象の発生を防ぐことができました。

Lambda@Edgeを使う上でのハマりどころ

デバッグ用のLambda@Edgeからログを出力して参照するときに、ハマったので書いておきます。 まず、Lambda@Edgeはus-east-1リージョンでのlambda関数の作成しなければならないのですが、 実際にログが出力するときは、us-east-1のリージョンではなく、CloudFrontで実際にアクセスのあったリージョン のCloudwatch Logsにログが出力されます。 今回は、アクセスされるCloudFrontが主に日本だったのでap-norttheast-1のCloudFrontにログが出力されていました。 さらに出力されるロググループ名も一般的な/aws/lambda/<Lambda関数名>ではなくus-east-1を追加した/aws/lambda/us-east-1.<Lambda関数名> 出力されるため注意が必要です。

まとめ

  • アプリケーションでモバイル、PCなど表示が異なってもCloudFrontは使える
  • デバッグに見える化はたいせつ
  • Lambda@Edge, CloudFront Functionsを使うことで実現できることが増える