LoginSignup
3
2

More than 5 years have passed since last update.

SAMでAPIGateWay+Lambdaのサーバーレスアプリを作って見た

Last updated at Posted at 2018-09-14

概要

AWS SAMはサーバーレスアプリケーションを定義するモデルで、その定義に従ってAWS上に環境構築してくれるCloudFormationのサーバーレス版です。
CloudFormationでも同じことができますが、権限周りや設定すべき項目が桁違いに増えるので、LambdaやAPIGateWayを使ったサーバーレス環境を構築するときはSAM一択になると思います。

今回はこのSAMを使って、よく見かけるサーバーレスアプリを構成して見ます。

作成内容

簡単なツイートアプリを作ります。
できることはツイートと過去のツイート一覧表示のみです。誰からもツイートは見られないし他の人のを見ることもできません。シンプルイズベスト。
ソースコードはこちらhttps://github.com/YuuSatoh/sam-lambda

説明することしないこと

  • 説明すること
    • SAMでサーバーレスアプリを作った話
    • 実装の簡単な説明
  • 説明しないこと
    • SAMのインストール方法とか使い方とか細かい話

アーキテクチャ

S3に置いたHTMLファイルをウェブサイトホスティングして、バックエンドはAPIGateWay+Lambdaのサーバーレス。DBはDynamoDBを使用します。
Web App Reference Architecture.png

CORSの有効化

SAMでLambdaの呼び出し元をAPIGateWayにすると、構築されたAPIGateWayはデフォルトでLambdaプロキシ統合の使用にチェックが入ってしまう
(リファレンス見ても外し方がわからなかった…誰か知ってたら教えてください…)

そのためAPIGateWayでCORSを有効化するだけではうまくいきませんでした。
スクリーンショット 2018-09-12 17.42.59.png
スクリーンショット 2018-09-12 18.22.36.png

Lambdaプロキシ統合はAPIGateWayがこれまで丸めていたHTTPヘッダーの情報をLambdaに丸ごと送るからそっちでいい感じにしてね!となっているようなので、Lambdaの方でCORSを有効化する処理を入れる必要がありました。

結果ヘッダーに"Access-Control-Allow-Origin”を入れるだけだったけどCORSは結構ハマって辛かった…

コード

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "サーバーレスハンズオン"

Globals:
    Function:
        Environment: 
            Variables:
                TABLE_NAME: 
                    !Ref HandsOnTable
        Runtime: nodejs8.10


Resources:
    GetFunction:
        Type: AWS::Serverless::Function 
        Properties:
            CodeUri: functions/src/
            Handler: index_get.handler
            Policies: AmazonDynamoDBReadOnlyAccess
            Events:
                getAPI:
                    Type: Api
                    Properties:
                        Path: /tweet
                        Method: get
    PostFunction:
        Type: AWS::Serverless::Function 
        Properties:
            CodeUri: functions/src/
            Handler: index_post.handler
            Policies: AmazonDynamoDBFullAccess
            Events:
                postAPI:
                    Type: Api
                    Properties:
                        Path: /tweet
                        Method: post
    HandsOnTable:
        Type: AWS::Serverless::SimpleTable
        Properties:
            PrimaryKey:
                Name: tweetId
                Type: String
            ProvisionedThroughput:
                ReadCapacityUnits: 5
                WriteCapacityUnits: 5

Outputs:
    HandsOn:
        Description: "API Gateway endpoint URL for Prod stage for HandsOn function"
        Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/tweet/"


  • Resources:
    • LambdaやDynamoDBといったリソースを設定するところ(そのまんま)
    • CodeUri
      • Lambdaのソースコードを相対パス指定する
      • packaged.yamlに変換後S3にアップロードされたソースへのパスが自動的に入る
    • パスとかメソッドとかハンドラーとかポリシーとかは見たまんま
  • Outputs:
    • CloudFormationの出力欄に表示されるもの
    • APIGateWayのエンドポイントを出力するようにしているのでAPIGateWayに見に行かなくていい。便利
  • Globals:
    • リソースに共通するところをまとめて書ける
      • ランタイムとか環境変数とか
    • Resources:で同じパラメータを指定すると上書きされる

Lambda(index_get.js)

'use strict';

var aws = require('aws-sdk');
var docClient = new aws.DynamoDB.DocumentClient({ region: 'ap-northeast-1' });

exports.handler = function (event, context, callback) {
    docClient.scan({ TableName: process.env.TABLE_NAME }, function (err, data) {
        if (err) {
            context.fail(err);
        } else {
            var response = {
                statusCode: 200,
                headers: {
                    "Access-Control-Allow-Origin": "*"
                },
                body: JSON.stringify(data),
                isBase64Encoded: false
            };
            callback(null, response);
        }
    });
};

DynamoDBからツイートデータを取得するコード。DynamoDBにフルスキャンかけてます。
レスポンスにはCORSを有効化するためヘッダーに許可するリソースを指定します。今回はとりあえず全てを許可する設定。

Lambda(index_post.js)


'use strict';

var aws = require('aws-sdk');
var docClient = new aws.DynamoDB.DocumentClient({ region: 'ap-northeast-1' });

exports.handler = function (event, context, callback) {
  var requestBody = JSON.parse(event.body);
  var message = decodeURIComponent(requestBody.message);
  var item = {
    tweetId: new Date().getTime().toString(),
    message: message
  };

  var params = {
    TableName: process.env.TABLE_NAME,
    Item: item
  };

  docClient.put(params, function (err, data) {
    if (err) {
      console.log(err);
    } else {
      var response = {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Origin": "*"
        },
        body: JSON.stringify(data),
        isBase64Encoded: false
      };
      console.log("Success:", data);
      callback(null, response);
    }
  });
};

こちらはDynamoDBにツイートデータをputするコードです。DynamoDBにputしてるだけ。
こちらも同様にCORSを許可するヘッダーを付加

index.html(一部)

<script language="javascript">
    // tweetのget
    $.ajax({
      type: 'get',
      url: "APIGatewayURL Change Todo",
      success: function (res) {
        res.Items.forEach(function (item) {
          var tweet = `
            <div class="popover right show" style="position:relative; max-width:100%;">
            <div class="arrow"></div>
            <div class="popover-content">${item.message}</div>
          </div>`;
          $("#tweets").append(tweet);
        });
      }
    });
    // tweetのpost
    function post() {
      var $form = $('#tweet-form');
      var JSONdata = {
        message: $('#message').val()
      };

      $.ajax({
        url: $form.attr('action'),
        type: $form.attr('method'),
        data: JSON.stringify(JSONdata),
        timeout: 10000,
        complete: function (xhr, textStatus) {
          window.location.reload(); // post完了後に画面のリロード
        },
      });
    }
  </script>

HTMLファイルのJavaScript部分はこんな感じ
Ajaxで非同期にツイートのgetとpostをしてます。
通信先のurlはCloudFormationの出力に表示されるAPIGateWayのエンドポイントを入れてあげます

動作確認

1. SAMでパッケージしてデプロイ
2. HTMLのツイートを投稿/取得するURLをAPIGateWayのエンドポイントに修正
3. S3にHTMLをアップロードしてウェブサイトホスティングして公開

スクリーンショット 2018-09-12 18.28.45.png

 コマンド(備忘録)

  • パッケージ化
    • sam package --template-file template.yaml --s3-bucket "s3BucketName" --output-template-file packaged.yaml
  • デプロイ
    • sam deploy --template-file packaged.yaml --stack-name ServerlessHandsON --capabilities CAPABILITY_IAM
3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2