ayumu_aoの日記

SIerから事業会社に転職したエンジニアが技術についてや組織論、本の話、今までの体験談などなどを個人的に垂れ流しています。

cloud frontとlambda@edgeでUA判定をしてPCページとSPページを出し分けてみた

Qiitaに出してた記事ですが、Qiitaそのものが炎上してるようなのでこちらにも同じ記事を展開

cloud frontとlambda@edgeでUA判定をしてPCページとSPページを出し分けてみた

Webサービスを構築する仕事がきたのですが、ビジネスロジックが不要なサービスだったので できるだけカンタンに作ろうと思いサーバーレス構築に挑戦してみた際にやってみたレポートになります。

使ったAWSのサービス

  • cloud front
  • lambda( @Edgeはバージニア北部リージョンで関数作成する必要があります
  • S3
  • Route53(ドメイン設定の話なので今回は特に登場させません)
  • ACMSSL証明書の話なので今回は特に登場させません)
  • Cloud Watch(lambdaを利用したら自動でついてきた)

やったこと

  1. S3にHTMLを配置
  2. cloud frontとS3を紐付け
  3. ドメインにアクセスした際にどのHTMLを出すか設定(トップページの設定)
  4. lambda@edgeにUA判定処理を実装
  5. lambda@edgeとcloud frontの紐付け(トリガー設定)今回はViewerリクエストをトリガーに設定

ざっくり動くまででだいたい2〜3時間くらいでできました。

lambda側に設定する必要のあるポリシーの信頼関係※記載わすれてたので追記しました(2019/07/10)

アクセス権限はS3とCloudFrontによしなに設定してあげてください。 お手軽にやるならフル権限つけてしまってよいかと思います。

~~~ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" }, { "Effect": "Allow", "Principal": { "Service": "edgelambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ~~~

1〜3はいろいろなところに記事が落ちているのでlambdaのソースと引っかかったポイントを紹介します。

lambdaに実装したソース

~~~javascript 'use strict';

const whitelist = [ 'android', 'iphone', 'ipad' ];

const isSpBrowser = (uas) => { if (uas && Array.isArray(uas) && uas.length > 0) { return uas.some(ua => whitelist.some(w => ua.value.toLowerCase().indexOf(w) !== -1)); } return false; };

// User-AgentからPC・SP判定を行ない描画ページを切り替える exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const headers = request.headers;

// console.log('headers.user-agent. = ' + headers['user-agent']);
if (headers['user-agent']) {
    // URI整理
    // cloud frontにトップページとして設定したindex.htmlがURIに入ってくるので除去
    var uri = request.uri.slice(-10) === ('index.html') ? request.uri.slice(0, -10) : request.uri;
    uri = uri.slice(-1) === ('\/') ? uri : uri + '\/';
    console.log('headers[user-agent] = ' + headers['user-agent'][0].value);
    // URI判定
    // ファイル読み込みで呼び出された場合中断
    if(uri.match(/( \.png|\.css|\.js|\.jpeg|\.jpg|\.bmp|\.gif|\.pdf|\.svg|\.zip)/)) {
        callback(null, request);
        return;
    }
    // UA判定
    if (isSpBrowser(headers['user-agent'])) {
        uri = uri + 'SP用描画ファイル名'
    } else {
        uri = uri + 'PC用描画ファイル名'
    }
    console.log('uri = ' + uri);
    request.uri = uri
    callback(null, request);
    return;
}
callback(null, request);

}; ~~~

PC/SPの判定においてはこういう書き方もできます。(というか今はこっちのほうが推奨されてます) ※下記ソースで行う場合トリガーをオリジンリクエストイベントに設定する必要があります。 (ビューワーリクエストイベントの後に CloudFront-Is-*-Viewer ヘッダーを追加されるため) ※Whitelist HeadersにHostsを混ぜるとS3へのリクエストはCloudFrontが署名を行う形になり想定外の挙動になる可能性があるので要注意

javascript // UA判定 前述コードのUA判定部分と置き換える if (headers['cloudfront-is-desktop-viewer'] && headers['cloudfront-is-desktop-viewer'][0].value === 'true') { uri = uri + 'PC用描画ファイル名' } else if (headers['cloudfront-is-mobile-viewer'] && headers['cloudfront-is-mobile-viewer'][0].value === 'true') { uri = uri + 'SP用描画ファイル名' } else if (headers['cloudfront-is-tablet-viewer'] && headers['cloudfront-is-tablet-viewer'][0].value === 'true') { uri = uri + 'SP用描画ファイル名' // タブレットでSP画面で見せる場合 } else if (headers['cloudfront-is-smarttv-viewer'] && headers['cloudfront-is-smarttv-viewer'][0].value === 'true') { uri = uri + 'PC用描画ファイル名' // SmartTVVでPC画面で見せる }

参考: https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-redirecting-examples

ポイント

  1. ログはCloud Watchに出力されるがアクセスされたcloud frontのリージョンに出力されたのでlambdaを配置しているリージョンではない
  2. ドキュメントは基本英語版が正
  3. トリガー設定はlambda側からやるほうがらく
  4. 困ったら迷わず有料サポートを使う(結果そのほうが早いのでトータルでの費用が下がる)

最後に

イベントページなどの後ろにビジネスロジックがいらないサービスであればこの構成は結構おすすめできると思います。 インフラ作業部分(エンジニアが必要な作業分)は大体5人日程度で収まったのでその分デザインやプロモーションにコストがまわせるのも強みかと ※初回なのでこれくらいですが慣れればもっと短くなる予感