プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

独自ドメインで、メールとhttps化したWebサイトを別サーバーで運用する (1/2)

独自ドメインで運用するけれども、更新が頻繁にない静的サイトなら、S3でホスティングして費用を抑えられないかな?と考えています。

おまけに、AWSの場合、無料でSSLを利用することができるとのことです(Amazon Certificate Manager (ACM))。もっとも、AWSのサービスで使う用途に限られますけどね。 これをS3で使う場合は、CloudFront経由で設定することで、SSLを無料で利用することができます。

で、Webサイトはこれでいいとしても、独自ドメインのメールサーバーどうしようかというところが悩ましいところです。 個人的には、メールサーバーの運用なんてしたくないので、 メールはさくらインターネットのメールボックスを利用したらいいんじゃないのか?と思い立ちました。 値段も、月額約86円(年1029円)で、比較的お安く運用できそうですしね。

ちなみに、最初は、そんなにアクセス数がない静的サイトであれば、同じくさくらインターネットのレンタルサーバーのライトプラン(月額129円)でもいいかと考えました。 でも、SSLに対応しようとすると、自分でSSL証明書を用意して云々で費用的にもかさむし、RapidSSLがChromeで対応されなくなるという話も気になるので、AWSを使おうと思った次第です。

今回設定した作業をメモ書きとしてまとめておきます。

構成

さて、今回試した構成をまとめると、

  • ドメイン取得:さくらインターネット
  • ネームサーバー:Route53
  • メールサーバー:さくらのメールボックス
  • Webサイト:CloudFront+S3のホスティング
  • SSL証明書:ACM

という感じになりました。

設定の流れとしては、ACM での SSL証明書の発行には独自ドメインのメールアドレスが必要なので、

  1. ドメインを取得する
  2. メールサーバーで独自ドメインのメールの送受信ができるようにする
  3. S3でホスティングする
  4. CloudFront を設定する
  5. ACM で取得したSSL証明書を設定する
  6. https でアクセスできることを確認する

という流れです。Route53は適宜設定していきます。

ドメインの取得

独自ドメインは、どこでとってもいいかと思います。 今回は、たまたま既にさくらインターネットで取得したものがあったのでそれを使いました。

なお、試しで使った際に、ネームサーバー(さくらのネームサーバー)を設定していたので、一旦削除しておきます。

f:id:junichim:20170915090618p:plain

削除方法は、『会員メニュー』にログインして、ドメインメニューを開いて、対象とするドメインの『ゾーン編集』ボタンを押すと現在の設定内容が出てくるので、 左側のカラムにある『削除』ボタンから設定内容を削除しておきます。

さくらのメールボックスの契約

次に、さくらインターネットのメールボックスを契約します。 月額約86円(年額1029円)で運用できるのはありがたいですね。

とりあえず、取得したドメイン名(さくらインターネットのサブドメイン名、xxxx.sakura.ne.jp のやつです)でメールの送受信ができることを確認しておきます。

独自ドメインを Route53 で管理

S3でのホスティング時に、Zone Apex(サブドメインのないドメイン、 example.com のようなやつ。ネイキッドドメインなどとも呼ばれるようです)でのアクセスを行う場合、ドメインは Route53 で管理されている必要があります。

少し横道にそれますが、この理由について考えてみます。この理由の直接的な記述を見つけることができなかったのですが、『例: 独自ドメインを使用して静的ウェブサイトをセットアップする』にあるように、S3での公開名に対して独自ドメインでアクセスするためにCNAMEレコードが必要になり、Zone Apex に対してはCNAMEレコードを追加できないため、Route53独自のレコードであるAliasレコードを使う必要がある、ということだと思います。 いやはや、DNSまわりはややこしいですねー。

S3でのホスティングをサブドメインで行うなら、このステップはなくても大丈夫です。 (上記の理由の裏返しで、サブドメインにならCNAMEレコードを設定が可能なので、ドメインを取得した会社のネームサーバーでCNAMEレコードの編集ができるなら、Route53でなくてもOKです)

具体的な移行方法は次の記事を参考にしました。

Amazon Route 53 を既存ドメインの DNS サービスにする - Amazon Route 53

ホストゾーンの作成

AWSのコンソールにログインし、Route53を選んで、Create Hosted Zone を選択します。

f:id:junichim:20170915093948p:plain

管理するドメイン名を入力します。

f:id:junichim:20170915094120p:plain

コメント欄は空欄で大丈夫です。

問題なければ、このようにNSレコードとSOAレコードが登録された状態になります。

f:id:junichim:20170915094214p:plain

レコードセットの作成

次に、Route53にレコードを追加します。追加するのは、MXレコードとTXT(SPF)レコードです。

Create Record Set ボタンを押して、レコードセットを作成します。

まずはMXレコード

f:id:junichim:20170915094411p:plain

MXレコードが指すメールサーバーは、さくらのメールボックスのサーバー名です(xxxx.sakura.ne.jp のやつ)。

次に、TXT(SPFレコード)です。

f:id:junichim:20170915094457p:plain

SPFレコードの内容は

v=spf1 a:wwwxxxx.sakura.ne.jp ~all

としています。ここで、a: のうしろのホスト名としては、さくらインターネットのメールボックスのサーバー名を指定します。 コントロールパネルから『サーバー情報』を表示させると

f:id:junichim:20170915094841p:plain

のようにホスト名が表示されるので、これを使います。

参考

なお、ホストゾーンへのMXレコードとSPFレコードの追加は、以下のサイトを参考にしました。

他社で取得したドメインでさくらのメールボックスを利用する

独自ドメインのメールアドレスをさくらレンタルサーバーのメールボックスで取得しました。 - estomo Blog

メールボックスに独自ドメインを割り当て

契約したメールボックスのコントロールパネルにログインして作業を行います。

コントロールパネルの『ドメイン設定』を開き、『新しいドメインの追加』を選択します。 いくつか選択肢が表示されますので、 『5. 他社で取得したドメインを移管せずに使う』を選びます。

f:id:junichim:20170915095228p:plain

表示された画面で、ドメイン名を入力します。

f:id:junichim:20170915095333p:plain

問題なく、追加できていれば、下記の様な画面になります。

f:id:junichim:20170915114811p:plain

参考

なお、『詳細設定にすすむ』は行わなくても問題ありませんが、仮にリンクをクリックすると

f:id:junichim:20170915114937p:plain

のようにSPFレコードの設定確認がでてきます。 ここまで行うと、(さくらインターネット側で)追加したドメインのゾーン情報が作成され、 そのDNSレコードが

f:id:junichim:20170915100240p:plain

のように設定されます。 これらは、次のネームサーバーの切り替えにより、参照されなくなるのですがご参考までに。

あと、今回は試さなかったのですが独自ドメインの割り当ての際『2. さくらインターネットで取得したドメインを使う』を選んでも同じだったかなと思ってます。

f:id:junichim:20170915100839p:plain

次に類似の設定を行う機会があれば、試そうと思います。

ネームサーバーを Route53 に切り替え

今回の独自ドメインは、さくらインターネットで取得しているので、ネームサーバーをRoute53に切り替えます。

『会員メニュー』の『ドメインメニュー』から対象とする独自ドメインの『WHOIS情報』のボタンを押します。

f:id:junichim:20170915101238p:plain

画面の下のほうに、ネームサーバー1~4を記入しているところがあるので、

f:id:junichim:20170915101323p:plain

『変更』ボタンを押します。

編集状態になったら、Route53でホストゾーンを作成した際のNSレコードに記載されているサーバー名を4つとも入力します。

f:id:junichim:20170915101535p:plain

この値ですね。

参考

ここの作業では、下記を参考にさせていただきました。

Amazon Route 53と独自ドメインでさくらのレンタルサーバを利用する方法

Amazon Route 53 を既存ドメインの DNS サービスにする - Amazon Route 53

メールの送受信テスト

さて、しばらく待ってから、まずはSPFレコードが正しく設定されているか確認します。

SPF Query Tool

上記のサイトを利用して確認します。問題なければ、独自ドメインを使って、メールの送受信ができるかテストしておきます。

問題なければ、ここまでで一段落です。 次は、S3によるホスティングとhttpsでのアクセスのための設定になります。

Cognito ユーザープール使ってみました

以前の書籍を読んだ記事にも書きましたが、その書籍中では Cognito を使いましたが、ユーザープール (User Pools)については紹介だけで、チュートリアルはありませんでした。

blog.mori-soft.com

このユーザープールが気になり、簡単なサンプルを作って試してみましたので、メモにまとめておきます。

Cognito ユーザープールとフェデレーティッドアイデンティティの関係について

最初、ドキュメントを呼んでも、すっきりしなかったので、不正確かもしれませんが、自分なりに理解したユーザープールとフェデレーティッドアイデンティティの関係についてを書いておきます。

Cognito のドキュメントを読むと、ユーザープールは、独自で認証を実装するときの Users テーブルのようなものだとわかります(ディレクトリサービスといったほうがいいのかな)。 ユーザーの登録・管理・認証を提供してくれるサービスというところですかね。

もちろん、ユーザープール単独でも使えるのですが、Cognito フェデレーティッドアイデンティティ (Federated Identities) とつなげることで、

  1. ユーザープールで認証
  2. フェデレーティッドアイデンティティ で AWS アクセス用のロールを割り当て
  3. 割り当てられたロールを使って、AWSのサービスへのアクセスを行う

ということが可能になるようです。

で、わかりにくかったのが、このフェデレーティッドアイデンティティ との連携というところです。

最初の自分の理解だと、フェデレーティッドアイデンティティというと外部のIDプロバイダ(facebookやGoogle+)を利用するためのサービスだと、捉えていました。 でもこれって、外部のIDプロバイダだけじゃなくて、Cognito ユーザープールをIDプロバイダとして利用することができるようです。 そのあたりが分かると話がしっくりきます (あらためてドキュメントを読んでみると、Cognito User Poolsも使えるよとしっかり書ていますね)。

また、フェデレーティッドアイデンティティを利用するためには、 Identity Pools (IDプールとも書かれてます)を作成する必要があります。 ここでも、『プール』、というキーワードが出てくるので、ややこしく感じますね。前述のユーザープールとは別物ですので、気を付けましょう。

このIDプールは、ユーザープールを含むIDプロバイダのアカウントに対して紐づけられる、Identity ID を管理するためのものです。この Identity ID に対してawsのロールを割り当てることになります。 こうすることで、IDプロバイダのアカウントを直接扱わなくても、認証したユーザーを一意に特定・管理できるということのようです。

ネットの記事によっては、ユーザープールの利用という話題について、フェデレーティッドアイデンティティを使う、という表現ではなく、Identithi Pool を使う、とのみ書いている場合があります。 どちらの表現でも、やってることは同じなので、わかってしまえば問題ないのですが、よくわからないうちだと、2種類の用語が出てくるけど、どう違うんだ?となってました。ご参考までに。

Cognito ユーザープール と Identity Pools の作成

上記の関係性さえわかれば、ユーザープルおよび Identity Pools の作成は、今回のようなサンプルだとたいして難しくありません。

ユーザープールの作成と Identity Pools の作成は、下記記事などを参考にしました。

Amazon Cognito User Poolsを使って、webサイトにユーザ認証基盤を作る - Qiita

Cognito User Pools x ログイン認証 x API認証 - Qiita

ユーザープール作成時の注意点としては、『アプリクライアント』を追加する際に、『クライアントシークレットを生成する』のチェックボックスを外すことを忘れないでください(JavaScriptからはクライアントシークレットが利用できないためです)。こちらのドキュメントもご参考に。

ブラウザでの動作サンプル

Identity Pool の作成まで問題なく終われば、コンソールでの作業は終わりです。 次は、ブラウザから、Cognito ユーザープールを利用するサンプルを作ります。

JavaScript からCognito の機能を使うためには、

  • aws-cognito-sdk.js
  • amazon-cognito-identity.js

の2つのSDKが必要です。下記のGitHubページからダウンロードできます。

github.com

とりあえずローカルにダウンロードして、それを指定します(このサンプルを試した時点では v1.19 でした)。

また、ログイン時のサンプルでは、AWS SDK for JavaScript も必要になります。 このGitHubのページにあるように、scriptタグで指定すればOKです。

ダウンロード版がよければ、こちらのページからデフォルトビルドのダウンロードなどを選択すれば、ダウンロードページに行けます。

サンプルコードの動かし方

なお、参考までに、以下で載せているサンプルをGitHubにあげておきましたので、もし興味があればご参考にしてください。

github.com

ローカルで動作させます。サーバーはpythonを利用しています。 (インターネット上のサーバー上が良ければ、S3などで適当にホスティングしてください。)

./run.sh

なお、動作環境は、

  • Windows 10 Pro, 1703, 64bit
  • Bash on Ubuntu on Windows, 16.04.2 LTS

です。

サインアップ

サインアップ用ページはこんな感じにしました。

f:id:junichim:20170906160027p:plain

コードはこんな感じですね(app.jsというファイルにまとめて書いてます)。

'use strict'
var upsample = {};

upsample.poolData = {
    UserPoolId: 'ユーザープールのID',
    ClientId: 'アプリクライアントのID'
};
upsample.UserPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(upsample.poolData);

upsample.signup = function() {
    var email = $('#inputEmail').val();
    var username = $('#inputUserName').val();
    var password = $('#inputPassword').val();
    if (!email | !username | !password) { return false; }

    var attributeEmail = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute({Name: 'email', Value: email});
    var attributeList = [];
    attributeList.push(attributeEmail);

    var message_text;
    upsample.UserPool.signUp(username, password, attributeList, null, function(err, result){
        if (err) {
            console.log(err);
            message_text = err;
        } else {
            var cognitoUser = result.user;
            console.log('user name is ' + cognitoUser.getUsername());

            message_text = cognitoUser.getUsername() + ' が作成されました';
        }
        $('#message').text(message_text);
        $('#message').show();
    });
}

ユーザープールを設定した際に、デフォルト設定のままだと、e-mail のみが必須入力となっているので、ユーザー名、パスワード、メールアドレスを指定しています。

サインアップの確認

画面はこんな感じ。

f:id:junichim:20170906143428p:plain

コードはこちら。

upsample.verify = function() {
    var username = $('#inputUserName').val();
    var vericode = $('#inputVerificationCode').val();
    if (!username | !vericode) { return false; }

    var userData = {
        Username: username,
        Pool: upsample.UserPool
    };

    var message_text;
    var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
    cognitoUser.confirmRegistration(vericode, true, function(err, result) {
        if (err) {
            console.log(err);
            message_text = err;
            $('#message').text(message_text);
            $('#message').append($('<a href="resend.html">再送信</a>')); // 再送信リンクの表示
        } else {
            console.log('call result ' + result);

            message_text = cognitoUser.getUsername() + ' が確認されました';
            $('#message').text(message_text);
        }
        $('#message').show();
    });
}

サインアップページからサインアップの確認ページが表示されないのはご愛敬にしてください。

ログイン

ログインページはこんな感じです。

f:id:junichim:20170906143623p:plain

コードはこんな感じ。

upsample.login = function() {
    var username = $('#inputUserName').val();
    var password = $('#inputPassword').val();
    if (!username | !password) { return false; }

    var authenticationData = {
        Username: username,
        Password: password
    };
    var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);

    var userData = {
        Username: username,
        Pool: upsample.UserPool
    };

    var message_text;
    var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
    cognitoUser.authenticateUser(authenticationDetails, {
        onSuccess: function(result) {
            console.log('access token + ' + result.getAccessToken().getJwtToken());

            AWS.config.region = 'ap-northeast-1';
            AWS.config.credentials = new AWS.CognitoIdentityCredentials({
                IdentityPoolId: 'Identity Pool の ID',
                Logins: {
                    'cognito-idp.リージョン名.amazonaws.com/ユーザープールID': result.getIdToken().getJwtToken()
                }
            });
            
            AWS.config.credentials.refresh(function(err) {
                if (err) {
                    console.log(err);
                } else {
                    console.log("success");
                    console.log("id:" + AWS.config.credentials.identityId);                    
                }

                $(location).attr('href', 'mypage.html');
            });
            //console.log("id:" + AWS.config.credentials.identityId);
            
            //$(location).attr('href', 'mypage.html');
        },

        onFailure: function(err) {
            alert(err);
        }
    });

}

もし、ログイン後、AWSのサービスにアクセスするなど、Identity ID が必要になるならばここでidentityIDを取得することができます。 上記のサンプルだと、コンソールに出力してみて確認しているだけですが。

AWS のコンソールから確認すると、

f:id:junichim:20170906145936p:plain

のように、IDがちゃんと割り当てられているのが分かります。

マイページ

ログインに成功するとマイページが表示されます。

f:id:junichim:20170906143939p:plain

コードはこんな感じ。 現在のユーザーとセッションを確認して、セッションが有効であれば、ユーザー情報を表示しています。

upsample.checkSession = function () {

    var cognitoUser = upsample.UserPool.getCurrentUser();
    if (cognitoUser != null) {
        cognitoUser.getSession(function (err, sessionResult) {
            if (sessionResult) {
                var attrs;
                cognitoUser.getUserAttributes(function (err, attrs) {
                    if (err) {
                        console.log(err);
                        return;
                    }
                    $('#username').text('Username:' + cognitoUser.getUsername());

                    for (var i = 0; i < attrs.length; i++) {
                        console.log('name:' + attrs[i].getName() + ", value: " + attrs[i].getValue() );
                        if (attrs[i].getName() == 'email') {
                            $('#email').text('Email: ' + attrs[i].getValue());
                        }
                    }
                });
            } else {
                console.log("session is invalid");
                $(location).attr('href', 'login.html');
            }

        });
    } else {
        console.log("no user");
        $(location).attr('href', 'login.html');
    }
}

ログアウト

upsample.logout = function() {

    var cognitoUser = upsample.UserPool.getCurrentUser();
    if (cognitoUser != null) {
        cognitoUser.signOut();
        location.reload();
    }

}

その他

実は、ログイン後、 Identity ID を取得するところでずいぶんはまりました。ようは、refreshメソッドを呼び出す必要があったんですが、それに気づかなかったのです。 ドキュメントのサンプルを見ると、ちゃんと書いてました。

きちんと読むべきですね。

あと、Identity ID (フェデレーティッドアイデンティティの Identity Pools の Identity ID)をコンソールで削除後、再度ブラウザからアクセスすると、『リソースがない』という旨のエラーで落ちることがありました。 調べてみると、 Cognito はlocalstorageを使っているようで、そのキャッシュが残っている関係で落ちるそうです。

stackoverflow.com

とりあえず、ブラウザの履歴を削除して、再度アクセスすると問題なく動作しました。

ただこの場合、Identity ID の値は、削除前の値から変更されています。なので、Identity ID を削除する必要があり、かつ、新旧のマッチを取る必要がある場合は、自分でなんとかしないといけなさそうですので、気を付けましょう。

参考

Cognito, Lambda, API Gateway のサンプル。Reactで作ってるので、React分かる人にはよいかも。

Cognito User Pools x ログイン認証 x API認証 - Qiita

Cognito利用時のログインの流れの図がわかりやすい

[ Serverless ] Cognito、S3、Lambdaで認証機能付きのWebサイトを作ってみました - Qiita

コード全般は下記記事を参考にしました。

AWS SDK for JavaScriptを使ってブラウザーからCognito User Poolsへサインアップしてみた | Developers.IO

AWS SDK for JavaScriptでCognito User Poolsを使ったログイン画面を作ってみた | Developers.IO

書籍『サーバーレスシングルページアプリケーション』を試してみました

書籍『サーバーレスシングルページアプリケーション』を読んで、試してみました。

いやー、この本いいですねー。実践的でかつ分かりやすい。最近は、FaaS (Function as a Service) っていうんですか?流行りのようですので、興味ある人はぜひ一度試してみるといいと思います。

さて、本の書評はネットを検索するといろいろと出てきます。

dev.classmethod.jp

yoshiyoshifujii.hatenablog.com

他にもいくつか目にしたのですが、忘れました(すいません)。

ということで、私などがあれこれいうのもおこがましいので、ここでは、実際にチュートリアルを試した際に気になったことやつまづいたことなどを、雑多にメモにしておきたいと思います。 似たようなところで、はまってしまう人の参考になればと思います。

ちなみに、はまったところを書いておられる方もいらっしゃいました。

kakakakakku.hatenablog.com

こちらもご参考にしてください。

さて、あれこれ書き始める前に、今回のチュートリアルを試した環境を書いておきます。

  • Windows 10 Pro 64bit, 1703 (Creators Update)
  • Bash on Ubuntu on Windows, 16.04.2LTS
  • sspa のリポジトリは 2017/7/31 現在のものをfork
  • 書籍:初版第1刷(2017/6/26発行)

BoW 便利ですねー。何の(大きな)問題もなく試せました。

第1章

p.12, sspa スクリプトが失敗する

で、早速、勇んで始めたら、第1書 p.12 のsspaスクリプトの実行で早速躓いてしまいました。

mor@DESKTOP-RLA4CF1:~/learnjs$ ./sspa server
Can't find Python. You need Python 2.7 or later to use this.

調べてみると、sspa スクリプトはpython2.7以上が必要とあり、スクリプト内部では python コマンドで呼び出していました。

で、Bash on Ubuntu on Windows の場合 python3 がデフォルトで入っているのですが、pythonコマンドでは起動できない(python3で呼び出すようです)。 なので、デフォルト状態では、pythonがないと判断されて実行できないとうおちでした。

解決方法は、シンボリックリンクを作成するだけです。

cd /usr/bin
sudo ln -s python3 python

これで無事に動きました。

p.17, AWS CLI のセットアップ

BoW なら Ubuntu と同じようにすれば、本のとおりでも(たぶん)うまくいくと思いますが、

sudo apt-get install python-pip

で pip をインストールしてみると、 python2.x に関連するパッケージもインストールされてしまい、気持ち悪かったです。

なので、今回は AWS CLI のインストールガイドのページを参考にインストールスクリプトをダウンロードし

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
sudo python ./get-pip.py
sudo pip install awscli

としました(sudo経由でルート権限で実行すると、ホームディレクトリではなく、システム全体にpipをインストールしてくれる)。

ちなみに、同じawsの説明ページでもこちらにある手順だと、ユーザー環境へのインストールになるのでご注意を(場合によってはこれのほうがいい場合もあるかな)。

p.19, AWS CLI のセットアップ

リージョンとして ap-northeast-1 を指定してみましたが、以後特に問題なく使うことができました。 第6章だったかな?コード内にリージョン名を記入するときには、自分が指定したものを使うようにします。

第3章

テストの書き方がわからない

第3章以降は本文中ではテストの書き方に言及しなくなっています(まあ、紙面の都合上仕方ないでしょうね)。 なので、著者のサイト の Resources からソースコード一式をダウンロードして、自分が今読んでるソースコード(learnjs/xxxx/public.app.js)に該当する番号(章・節番号を元にしているようです)内のテストコードなどを参考に、自分で書いて試すが一番よさそうです。

p.57, コード

マイナス記号が抜けてます。オライリーの正誤表のページにも記載されています。

p.58, 3.2.2『データモデルを成長させる』

個人的には、この節のようにすればフォーマッターを適用できますよ、という紹介のように受け取れました。 ダウンロードしたコードにも実装がないようですしね。

あれか?自分でフォーマット用のコードを書けばすんなりチュートリアルとして使えるということなのかな? いずれにしても、チュートリアルとしては若干補う必要があるので、場合によっては飛ばすのもありかと思います。

第4章

p.84, 4.2.1 節

./sspa create_pool conf/cognito/identity_pools/learnjs

このまま実行すると

support/jsed.py

でエラーになります。

理由は、support/jsed.py 16行目の print 文がpython3 に対応していないためです。

ということで、ここを print関数に修正します。

mor@DESKTOP-RLA4CF1:~/learnjs/support$ diff jsed.py.org jsed.py
16c16
< print search(doc, keys)
---
> print( search(doc, keys) )
mor@DESKTOP-RLA4CF1:~/learnjs/support$

diff とるとこんな感じです。

p.91, refresh 関数はどこで使う?

ここを読んだだけでは、このrefresh 関数の使われ方がわからなくて、しばし戸惑ってしまいました。 読み進めると、p.113 の 5.4.1『ファイルセーフのデータアクセス関数』のところで、実際に使われており、なるほどと納得できました。 第4章では使われてないので、気にせず読み進めるのが良いのではないか?と思います。

第6章

Node.js のセットアップはどうする?

下記の記事にまとまっているように、

http://qiita.com/7tsuno/items/e666223d6be22d657542qiita.com

BoW の場合 (付録でも触れらている) nvm を利用するのが良さそうです。

インストール方法は、 nvm のサイトに書いてあるので、それに従います。

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash

一度ログアウトして再ログイン後、インストールします。

nvm install 4.3

※ node.jsのバージョンは 4.3 にしました。今だと、Lambda が 6.10 もサポートしているのですが sspa スクリプト内で固定的に 4.3 が指定されているので、それに合わせています。

ちなみに、最初はrootユーザーでインストールしようとしたんだけど、

stackoverflow.com

このコメントに『rootでインストールして共有して使うのを想定していない』とあったので、やめました。

細かいですが、BoWだと zip もなかったので、ついでにインストールしておきます。

sudo apt-get install zip

p125, 6.1.3 のLambdaの課金単位

本文中では『GB/秒』とかいてあるけど、AWS Lambda の料金のページ等を見ると『GB・秒』の表記のほうが正しいと思います。

6.3の『プロジェクト』と6.3.1の『プロジェクト』は同じ意味?

確証はないですが、個人的には多分違うんじゃないかなと思ってます。 6.3節では『プロジェクト』がモノリシックな状態を作り出すものを意味しているのに対して、6.3.1節ではプロジェクトがLambda関数をまとめるファイルのような意味合いで使われているように思われます。

こういう時、原著を読むと分かったりするのですが、実際のところどうなんでしょうかね?

p137, このコラムはどうやって試せばよい?

自分でCLIで試すということじゃないかと思ってます。試したのをメモとして残しておきます。

p.138 の popularAnswers で dynamoDB を使った処理
mor@DESKTOP-RLA4CF1:~/learnjs$ aws --profile admin dynamodb scan --table-name "learnjs" --filter-expression "problemId = :probId" --expression-attribute-values '{":probId": {"N":"1"}}'
{
    "ScannedCount": 2,
    "Count": 1,
    "ConsumedCapacity": null,
    "Items": [
        {
            "userId": {
                "S": "ap-northeast-1:abcdefgh-1234-5678-ijkl-xxxxxxxxxxxx"
            },
            "problemId": {
                "N": "1"
            },
            "answer": {
                "S": "true"
            }
        }
    ]
}
mor@DESKTOP-RLA4CF1:~/learnjs$
lambda関数呼び出しの場合
mor@DESKTOP-RLA4CF1:~/learnjs/public$ aws --profile admin lambda invoke --function-name 'popularAnswers' --payload '{"problemNumber": 1}' tmp
{
    "StatusCode": 200
}
mor@DESKTOP-RLA4CF1:~/learnjs/public$ cat tmp
{"true":1}

p138, コード中の リージョン名

第1章で指定したリージョンに合わせて編集しましょう。

p138, コード中の byCount 関数

引き算のマイナスが抜けているみたい。たぶんこうなるはず。

function byCount(e1, e2) {
  return e2[0] - e1[0];
}

p.139, 6.4がチュートリアルとして扱いにくい

仕方ないのかなー?

6.3.4のビューおよびp.140のポリシーのサンプルなどもあると分かり易いんだけど、これがないので、若干試しにくいんだと思います(原著者のサイトやダウンロードしたものにも含まれてないようですしね)。

ということで、簡単ですが自分で試してみました。 popularAnswer はログインしており、かつ、解答が正解した場合に、次の問題へのリンクの下に、『Show Popular Answer』というリンクが表示され、それをクリックすると、その問題について多い正解が表示されるという形にしました。

popularAnswerのビューの例

public/index.html にビュー(popular-view)をtemplatesに追加します

        <div class='popular-view'>
          <h3 class='title'></h3>
          <div class='popular-list'></div>
        </div>

また、リンク表示用の要素も追加します。

        <div class='popular-link'>
          <a>Show popular answer</a>
        </div>
popularAnswer 表示処理

public/app.js に addPopularLink 関数および popularView 関数を追加します。

learnjs.addPopularLink = function(parentElem, problemNumber) {
  var link = learnjs.template('popular-link');
  link.find('a').attr('href', '#popular-' + problemNumber);
  parentElem.append(link);
}
learnjs.popularView = function(data) {
  var problemNumber = parseInt(data, 10);
  var view = learnjs.template('popular-view');
  var popular = view.find('.popular-list');
  learnjs.popularAnswers(problemNumber).then(function(items) {
    var objs = JSON.parse(items.Payload);
    for (var key in objs) {
      popular.append($('<p>').text('answer: ' + key + ', count: ' + objs[key]));
    }
  });
  view.find('.title').text('Popular Answer for Problem #' + problemNumber);
  return view;
}

既存の buildCorrectFlash 関数を修正して、認証が通っている場合に、リンクを追加するようにします。

learnjs.buildCorrectFlash = function(problemNum) {
  var correctFlash = learnjs.template('correct-flash');
  var link = correctFlash.find('a');
  if (problemNum < learnjs.problems.length) {
    link.attr('href', '#problem-' + (problemNum+1));
  } else {
    link.attr('href', '');
    link.text("You're Finished!");
  }

  learnjs.identity.done(function() {
    learnjs.addPopularLink(correctFlash, problemNum);
  });
  return correctFlash;
}

showView 関数に popularView を表示するためのルートを追加します。

learnjs.showView = function(hash) {
  var routes = {
    '#problem': learnjs.problemView,
    '#profile': learnjs.profileView,
    '#popular': learnjs.popularView,
    '#': learnjs.landingView,
    '': learnjs.landingView
  };
  var hashParts = hash.split('-');
  var viewFn = routes[hashParts[0]];
  if (viewFn) {
    learnjs.triggerEvent('removingView', []);
    $('.view-container').empty().append(viewFn(hashParts[1]));
  }
}

Lamda ポリシーの追加

p.140 にあるようにCognitoロールにLambda関数の呼び出しを許可するポリシーを追加します。 今回はコンソールで実行しました。

ブラウザで AWS のコンソールにログインして、 IAM -> ロール -> learnjs_cognito_authenticated を選択します 『インラインポリシー』の『ロールポリシーの作成』をクリックします。 今回は、Policy Generator を使い、下記の様なポリシーを追加しました。

(Lambda関数の呼び出し, invoke を許可するポリシー)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1502980684000",
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                Lambda関数のARN
            ]
        }
    ]
}

一応、これで手元の環境ではうまく動きました。 テストファーストを押しているチュートリアルですが、テストには手が回りませんでした・・・

p.140, FunctionName は 'learnjs_popularAnswers' ではなく 'popularAnswers' では?

Lambda の設定内容次第ですが、本文に従っているなら、多分間違いだと思います(popularAnswers が正しいと思います)。

第7章

p.152, 7.3.2 を実装したが、どうテストを書けばよい?

Webワーカーを導入した場合、テストも修正しないといけないのですが、ダウンロードしたコードには変更が反映されていないようです。 自分で書かないといけないのですが・・・(略)。

p.161, 7.6.1 にある CloudFront を試したい

ということで、CloudFront を試してみました。CloudFront の一般的な話は下記などを参考にしてください。

dev.classmethod.jp

設定方法自体は少しネットを検索すれば出てくると思うので、詳しくは触れませんが、S3のバケット名を指定して、下記画像のように独自ドメイン名を指定することぐらいで設定完了でした。

f:id:junichim:20170818133228p:plain

その他として、この本で作っているアプリで扱うときは、

  • ドメイン名(/終わり)でアクセスするには Default Root Object に index.html などを指定しておく

とよいかと思います。(細かい設定は全然見てませんが)予想以上に、この本で使ったS3の公開用バケットに対して CloudFront を設定して公開するのは簡単にできるな、と思いました。

Restrict Bucket Access の設定方法について

本文中でも直接S3のバケットにアクセスできないように設定しましょう、とあるので試してみました。

設定手順について簡単に示すとだいたい次の通りになります。

  1. 作成した distribution を選択し、Origins タブを表示し、 Edit を実行
  2. Restrict Bucket Access を Yes にする
  3. Origin Access Identity, Comments, Grant Read Permissions on Bucket が表示される
  4. Origin Access Identity は Create a New Identity を選択
  5. Comments は適当に
  6. Grant Read Permissions on Bucket は Yes, Update Bucket Policy を選択
  7. Yes, Edit ボタンを押して、変更を有効にする

ちなみに、Origin Access identity はCloudFrontからS3へアクセスする際のIdentityを新たに作るか既存のものを利用するかを指定するものです。 新規の場合は、Createでよいらしい。

Grant Read Permissions on Bucket は S3 のバケットポリシーを自動で編集するために読み取り許可を与えるか否かを指定するものです。Noを選んだ場合は、自分でS3のバケットポリシーを編集しないといけないので、Yesを選択したほうが楽なようです。

なお、Restrict Bucket Access の設定方法は下記記事を参考にさせていただきました。

S3の特定バケットへのアクセスを特定のCloudFrontからのみ許可する。 - Qiita

AngularJSで作ったSPAをAWS上の「S3+CloudFront」でお手軽ホスティングして、クラウドサービスってやっぱ素晴らしいなと思った話 | I am mitsuruog

オリジンアクセスアイデンティティを使用して Amazon S3 コンテンツへのアクセスを制限する - Amazon CloudFront

CloudFront 経由のみのアクセスに限定?

で、これで、S3で公開しているURLではアクセスできないかと思いきや、全然できてしまいます。 なんでだ?

というので調べてみると、sspaスクリプトがS3にアップロードする際に、ACLとして public-read を与えていたためのようです。これは、S3 の static web hosting を行う際には、誰でも読み込み権限を与える必要があるために、わざわざ設定しているようです(AWS のドキュメントにも説明がありますね)。

docs.aws.amazon.com

ちなみに、ネットでS3の静的ホスティングの記事などを見ると、バケットポリシーを設定するという記事(例えば、これとか)などが良くありますが、これも同じことで、ACLで指定するか、バケットポリシーで指定するかの違いだけのようです。

なので、CloudFront 経由のみにアクセスを限定したい場合は、上記の Restrict Bucket Access に加えて、public-readのACLを削除する必要があります。 やり方はいろいろあると思いますが、今回でしたら、一旦公開バケットの中のファイルをすべて削除して、コマンドラインから

aws --profile admin s3 sync public/ s3://learnjsバケット名 --acl private

のように private で再アップロードすればOKです。

なお、ついでに、 static web hosting も無効にしておきます(有効でも公開されてないのでアクセスできないのですが)。 これで、CloudFront 経由のアクセスのみになりました。

最後に

あれこれ書きましたが、一言でいうなら、おすすめできる本です。

若干チュートリアルとして扱いにくいところもありますが、まあ、新しいものに触れる際は、自分で試行錯誤しないといけないので、ま、その訓練と思えばいいんじゃないでしょうか。

改訂されることがあれば、チュートリアルとしてより扱いやすい本になることを期待しています!

にしても、AWS 使えば使うほど、amazon にベンダーロックインされてる感がすごいなー。まさにこの本の第一章で書かれている状態に近づきつつあるや。ま、これはこれとして楽しみましょう。