CloudFront・S3・API GatewayでマルチオリジンなSPAサイトを作ってみる

はじめに

AWSでサーバーレスアーキテクチャのSPAサイトを組むにあたって、フロントエンドをS3(+CloudFront)から配信し、バックエンドをAPI Gatewayに置くのは一般的な構成だと思います。

フロントエンドのサイトとバックエンドのAPIで二つのドメインで運用することもできますが、前段にCloudFrontを置いて一つのドメインから両オリジンにアクセスができる構成を作ってみます。 構成図にするとこんな感じ。

S3

設定はすべてデフォルト。

API Gateway

REST APIを作成。
わざわざLambdaを作るのも面倒なのでMockを使って仮のレスポンスを返します。
APIをデプロイします。ステージ名は「api」とします。

得られたエンドポイントからAPIを呼んでみます。
$ curl https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/api/hello
{
  "message" : "hello"
}

CloudFront

まずはS3オリジンのディストリビューションを作成します。
まずはS3をオリジンにOAIでアクセスできるよう設定します。
その他の項目は必要に応じて変更します。
ここではWAFやSSL証明書の設定を行いました。

ディストリビューションの作成したら次にAPIのオリジンを追加します。
発行されたAPI Gatewayのドメインをオリジンドメインに設定します。
API Gatewayへはhttps接続のみが許可されます。

APIのオリジンが追加されたらビヘイビアを追加します。
「api/*」のパスパターンならAPIのオリジンにリクエストが流れるよう設定します。
API Gatewayのステージ名が「api」だったのでパスパターンを合わせています。

その他APIの要件に応じて許可するメソッドだったりヘッダーやクエリ文字列の送信設定をします。
注意事項として「Host」リクエストヘッダーは送信してはいけません。


これでディストリビューションのデプロイが完了すれば設定は完了です。

SPAページのデプロイ・動作確認

簡易的なVue.jsとAxiosを使ったページをS3にデプロイします。
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="utf-8">
  <title>Sample SPA</title>
</head>

<body>
<div id="app">
  <pre><code>{{ result }}</code></pre>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
new Vue({
  el: '#app',
  data: {
    result: ''
  },
  mounted: function(){
    const api = axios.create({
      baseURL: "https://[cloudfront_url]/api/", // CloudFrontに割り当てたドメイン
      headers: {
        'Content-Type': 'application/json'
      },
      responseType: 'json',
      timeout: 3000
    })

    api.get("hello")
    .then(response => {
      this.result = response.data
    })
    .catch(err => {
      console.error(err)
    })
  }
})
</script>

<style>
pre {
  font-size: 15px;
  color: #2F4F4F;
  background-color: #F5F5F5;
}
</style>
</body>

</html>
上記のHTMLをS3に置いて、「https://[CloudFrontに割り当てたドメイン]/index.html」でアクセスして、下記のようになっていればOK。

同一オリジンにすることでCORS周りの設定を考えなくてもAPIを呼び出せてるのが便利です。