Top View


Author kakudaisuke

AWS SAMでHTTP APIを実装する。CORSも設定する。REST APIと微妙に違う!

2021/12/10

Table of Contents

背景

AWS SAMでせっせとサーバーレスな処理を構築していました。

AWS Quick Start TemplatesのHello World Exampleから作成した経緯があって、デフォルトでAPI GatewayがREST APIタイプになっていたのですが、HTTP APIという別のタイプを先輩から教えていただき、そちらに変更することになりました。

そのREST→HTTPの変更とCORS設定に取り組んでいたのですが、こまごまと罠があってややハマっていました。そんな迷える子羊は僕だけではなかったようで、aws/aws-sam-cliのissueに困っている方々のコメントが寄せられていました。最終的にうまく行ったので、彼らにも届け!という思いで整理をしておきたいと思います。

ちなみにREST APIでのCORS設定はこちらのQiita記事(AWS SAMのCORSを設定する(REST APIバージョン))にまとめてあります。

HTTP APIとREST API

当初はそもそもAPI Gatewayにタイプがあったことすら知らなかった私ソリューションアーキテクトアソシエイトなのですが、HTTP APIの方が低レイテンシーで低コストだそうで、機能の詳しい違いは公式ドキュメントにまとまっています。

REST APIタイプの方が歴史が深く、現時点ではより多機能で、料金も高いようです。(料金表

タイプリクエスト数 (月間)価格 (100 万あたり)
HTTP API最初の 3 億1.29USD
REST API最初の 3 億 3,300 万4.25USD

実装

REST APIのCORS設定テンプレート例

比較対象として、SAMのデフォルトのAPI Gatewayである、REST APIでのtemplate.yaml例を先に載せておきます。

Globals:
  Function:
    Runtime: ruby2.7
    Timeout: 3

Resources:
  HelloWorldLogsApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: 'dev'
      Cors:
        AllowOrigin: "'http://127.0.0.1:3000'"
        AllowCredentials: true
        AllowMethods: "'POST'"
        AllowHeaders: "'Content-Type,X-CSRF-TOKEN'"

  HelloWorldLogsFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world_logs/
      Handler: app.lambda_handler
      Runtime: ruby2.7
      Events:
        ApiEvent:
          Type: Api
          Properties:
            RestApiId: !Ref HelloWorldLogsApi
            Path: /hello_world_logs
            Method: post

HTTP APIのテンプレート例

いよいよHTTP APIです。

基本的にはAWS::Serverless::HttpApiのドキュメントに書いてありますが、試行錯誤の末うまくいったテンプレートを実装例として示します。

Resources:
  # API Gatewayを明示的に書く
  HelloWorldApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      DefinitionBody: # ここが必要らしい
        openapi: 3.0.1
        info:
          title: !Ref 'AWS::StackName'
        paths: {}
      CorsConfiguration: # Cors設定はKey-Value形式ではなくリスト形式で書く
        AllowOrigins: # AllowOriginsは複数形!(REST APIでは単数形AllowOrigin)
          - "*"
        AllowCredentials: true
        AllowMethods: 
          - POST
        AllowHeaders:
          - Content-Type
          - X-CSRF-TOKEN

  HelloWorldLogsFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Join
        - '-'
        - - hello_world
          - !Ref Stage
      CodeUri: hello_world_logs/
      Handler: app.lambda_handler
      Runtime: ruby2.7
      Events:
        HelloWorldLogs: # => ここは何でも良さげ。ドキュメントはApiEvent:となっている
          Type: HttpApi # 追加
          Properties:
            ApiId: !Ref MyApi # RestApiId→ApiId追加
            Path: /hello_world_logs
            Method: post

REST APIとの違いを踏まえつつポイントを。

  • ResourcesにAPI Gatewayを明記する。SAMのデフォルトがRESTらしく、何も書かないとAWS::Serverless::Function、つまりREST APIが作られるようです。
  • DefinitionBodyプロパティは、AWSドキュメントではRequired: Noになっていますが、僕は入れないとエラりました。
  • REST APIでのCORS設定プロパティはCorsですが、HTTP APIではCorsConfigurationになる。
  • CORSのAllow兄弟は、Key: Value形式ではなく-を使ったリスト形式で書く。
  • REST APIでは、単数形AllowOriginだったのが、HTTP APIではAllowOriginsと複数形になる。
  • PropertiesRestApiIdApiIdにする。

AWSドキュメント「HTTP API の CORS の設定

CORSのheadersをLambdaのレスポンスに含める

ちなみに、RESTとHTTPの違いとはあまり関係ありませんが、LambdaのレスポンスヘッダーにもCORSの設定を書いてあげる必要があります。

def lambda_handler(event:, context:)

  { 
    statusCode: 200,
    body: {
      message: 'hello_world'
    }.to_json,
    headers: {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Credentials": true,
      "Access-Control-Allow-Methods": "POST",
      "Access-Control-Allow-Headers": "Content-Type,X-CSRF-TOKEN",
    }
  }

end

最後に小ネタ:一度デプロイしたらAPIタイプは変更できないぽい

一度SAMプロジェクトをデプロイして、APIのタイプを変えて再デプロイしようとすると下のようなエラーになります。

Waiting for changeset to be created.. Error: Failed to create changeset for the stack: HelloWorldStack, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Update of resource type is not permitted. The new template modifies resource type of the following resources: [HelloWorldApi]

なので、一旦スタックを削除してからデプロイするのが吉なようです。

kakudaisuke

kakudaisuke

Twitter X

IoT