0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS日記17 (API Gateway)

Last updated at Posted at 2020-08-29

はじめに

今回は API Gateway の WebSocket を試します。
簡易なチャットページを作成します。
[Lambda関数・SAMテンプレート]
(https://github.com/tanaka-takurou/serverless-chat-page-go)

準備

AWS SAM CLI環境の準備をします

[Amazon API Gatewayの資料]
Amazon API Gateway
Amazon API Gateway とは
Amazon API Gateway の料金

AWS SAM テンプレート作成

AWS SAM テンプレートで API-Gateway , Lambda , DynamoDb, S3 の設定をします。

[参考資料]
AWS SAM テンプレートを作成する

template.yml
template.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Chat Page

Parameters:
  ApplicationName:
    Type: String
    Default: 'Serverless Chat Page'
  ChatWebSocketApiName:
    Type: String
    Default: 'ChatWebSocket'
  ChatFrontApiName:
    Type: String
    Default: 'ChatFront'
  ChatOnConnectFunctionName:
    Type: String
    Default: 'ChatOnConnectFunction'
  ChatOnDisconnectFunctionName:
    Type: String
    Default: 'ChatOnDisconnectFunction'
  ChatOnSendFunctionName:
    Type: String
    Default: 'ChatOnSendFunction'
  ChatCronFunctionName:
    Type: String
    Default: 'ChatCronFunction'
  ChatFrontFunctionName:
    Type: String
    Default: 'ChatFrontFunction'
  ConnectionTableName:
    Type: String
    Default: 'chat_connection'
  MessageTableName:
    Type: String
    Default: 'chat_message'
  LimitConnectionCount:
    Type: String
    Default: '10'
  LimitMessageCount:
    Type: String
    Default: '100'

Metadata:
  AWS::ServerlessRepo::Application:
    Name: Serverless-Application-Simple-Chat
    Description: 'Serverless Application Simple Chat'
    Author: tanaka-takurou
    SpdxLicenseId: MIT
    LicenseUrl: LICENSE.txt
    ReadmeUrl: README.md
    Labels: ['ServerlessRepo']
    HomePageUrl: https://github.com/tanaka-takurou/serverless-chat-page-go/
    SemanticVersion: 0.0.1
    SourceCodeUrl: https://github.com/tanaka-takurou/serverless-chat-page-go/

Resources:
  ServerlessChatWebSocket:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: !Ref ChatWebSocketApiName
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: "$request.body.action"
  ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: $connect
      AuthorizationType: NONE
      OperationName: ConnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref ConnectInteg
  ConnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Connect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocations
  DisconnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: $disconnect
      AuthorizationType: NONE
      OperationName: DisconnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref DisconnectInteg
  DisconnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Disconnect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocations
  SendRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: send
      AuthorizationType: NONE
      OperationName: SendRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref SendInteg
  SendInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Send Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnSendFunction.Arn}/invocations
  Deployment:
    Type: AWS::ApiGatewayV2::Deployment
    DependsOn:
    - ConnectRoute
    - SendRoute
    - DisconnectRoute
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
  Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      StageName: Prod
      Description: Prod Stage
      DeploymentId: !Ref Deployment
      ApiId: !Ref ServerlessChatWebSocket
  ConnectionTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
      - AttributeName: "connectionId"
        AttributeType: "S"
      KeySchema:
      - AttributeName: "connectionId"
        KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      SSESpecification:
        SSEEnabled: True
      TableName: !Ref ConnectionTableName
  MessageTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
      - AttributeName: "id"
        AttributeType: "N"
      KeySchema:
      - AttributeName: "id"
        KeyType: "HASH"
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      SSESpecification:
        SSEEnabled: True
      TableName: !Ref MessageTableName
  ImgBucket:
    Type: AWS::S3::Bucket
  OnConnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref ChatOnConnectFunctionName
      CodeUri: api/connect/bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Chat OnConnect Function'
      Environment:
        Variables:
          CONNECTION_TABLE_NAME: !Ref ConnectionTableName
          LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount
          LIMIT_CONNECTION_COUNT: !Ref LimitConnectionCount
          REGION: !Ref 'AWS::Region'
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref ConnectionTableName
  OnConnectPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
      - ServerlessChatWebSocket
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OnConnectFunction
      Principal: apigateway.amazonaws.com
  OnDisconnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref ChatOnDisconnectFunctionName
      CodeUri: api/disconnect/bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Chat OnDisconnect Function'
      Environment:
        Variables:
          CONNECTION_TABLE_NAME: !Ref ConnectionTableName
          REGION: !Ref 'AWS::Region'
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref ConnectionTableName
  OnDisconnectPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
      - ServerlessChatWebSocket
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OnDisconnectFunction
      Principal: apigateway.amazonaws.com
  OnSendFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref ChatOnSendFunctionName
      CodeUri: api/send/bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Chat OnSendFunction Function'
      Environment:
        Variables:
          CONNECTION_TABLE_NAME: !Ref ConnectionTableName
          MESSAGE_TABLE_NAME: !Ref MessageTableName
          BUCKET_NAME: !Ref ImgBucket
          LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount
          LIMIT_CONNECTION_COUNT: !Ref LimitConnectionCount
          REGION: !Ref 'AWS::Region'
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref ConnectionTableName
      - DynamoDBCrudPolicy:
          TableName: !Ref MessageTableName
      - S3CrudPolicy:
          BucketName: !Ref ImgBucket
      - Statement:
        - Effect: Allow
          Action:
          - 'execute-api:ManageConnections'
          Resource:
          - !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessChatWebSocket}/*'
  SendMessagePermission:
    Type: AWS::Lambda::Permission
    DependsOn:
      - ServerlessChatWebSocket
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref OnSendFunction
      Principal: apigateway.amazonaws.com
  ServerlessChatFrontPage:
    Type: AWS::Serverless::HttpApi
  FrontPageFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref ChatFrontFunctionName
      CodeUri: bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Chat Front Function'
      Events:
        testapi:
          Type: HttpApi
          Properties:
            Path: '/'
            Method: get
            ApiId: !Ref ServerlessChatFrontPage
      Environment:
        Variables:
          BUCKET_NAME: !Ref ImgBucket
          MESSAGE_TABLE_NAME: !Ref MessageTableName
          LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount
          WEBSOCKET_URL: !Join [ '', [ 'wss://', !Ref ServerlessChatWebSocket, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/Prod'] ]
          REGION: !Ref 'AWS::Region'
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref MessageTableName
  ChatApiPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref FrontPageFunction
      Principal: apigateway.amazonaws.com
  CronFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Ref ChatCronFunctionName
      CodeUri: api/cron/bin/
      Handler: main
      MemorySize: 256
      Runtime: go1.x
      Description: 'Chat Cron Function'
      Environment:
        Variables:
          CONNECTION_TABLE_NAME: !Ref ConnectionTableName
          REGION: !Ref 'AWS::Region'
          STACK_NAME: !Ref 'AWS::StackName'
      Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref ConnectionTableName
  ScheduledRule:
    Type: AWS::Events::Rule
    Properties:
      Description: ScheduledRule
      ScheduleExpression: 'rate(24 hours)'
      State: 'ENABLED'
      Targets:
        - Arn: !GetAtt CronFunction.Arn
          Id: TargetCronFunction
  CronFunctionPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref CronFunction
      Action: lambda:InvokeFunction
      Principal: 'events.amazonaws.com'
      SourceArn: !GetAtt ScheduledRule.Arn

Outputs:
  WebSocketURI:
    Description: "The WSS Protocol URI to connect to"
    Value: !Join [ '', [ 'wss://', !Ref ServerlessChatWebSocket, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/Prod'] ]

  FrontPageURI:
    Description: "The Front Page URI to connect to"
    Value: !Join [ '', [ 'https://', !Ref ServerlessChatFrontPage, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/'] ]

WebSocket用のAPI-Gatewayの設定


  ServerlessChatWebSocket:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: !Ref ChatWebSocketApiName
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: "$request.body.action"

WebSocket接続用のルートの設定

  ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: $connect
      AuthorizationType: NONE
      OperationName: ConnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref ConnectInteg
  ConnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Connect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocations

WebSocket接続先にデータを送る用のルートの設定

  SendRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: send
      AuthorizationType: NONE
      OperationName: SendRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref SendInteg
  SendInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Send Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnSendFunction.Arn}/invocations

WebSocket切断用のルートの設定


  DisconnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      RouteKey: $disconnect
      AuthorizationType: NONE
      OperationName: DisconnectRoute
      Target: !Join
        - '/'
        - - 'integrations'
          - !Ref DisconnectInteg
  DisconnectInteg:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref ServerlessChatWebSocket
      Description: Disconnect Integration
      IntegrationType: AWS_PROXY
      IntegrationUri:
        Fn::Sub:
            arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocations

Lambda関数作成

※ Lambda関数は aws-lambda-go を利用し、apigatewayの周りの処理は aws-sdk-go-v2 を利用しました。

WebsocketのコネクションIDを取得するには APIGatewayWebsocketProxyRequest.RequestContext.ConnectionID を使う

func HandleRequest(ctx context.Context, request events.APIGatewayWebsocketProxyRequest) (Response, error) {

        ...

	if err == nil && int(*connectionCount) < limitCount {
		err = putConnection(ctx, request.RequestContext.ConnectionID)
	} else if int(*connectionCount) >= limitCount {
		err = errors.New("too many connections")
	}

        ...

}

WebSocket接続先にデータを送るには PostToConnectionRequest を使う

connectionRequest := apigatewayClient.PostToConnectionRequest(&apigatewaymanagementapi.PostToConnectionInput{
	Data:         jsonBytes,
	ConnectionId: &connectionId,
})
_, err := connectionRequest.Send(ctx)

終わりに

これまでAPI Gatewayは、ほぼREST APIのみ利用してきましたが、用途に合わせて HTTP API や WebSocket API も使い分けていこうと思います。

参考資料
[simple-websockets-chat-app](https://github.com/aws-samples/simple-websockets-chat-app) [APIGatewayでWebSocketが利用可能になったのでチャットAPIを構築してみた](https://qiita.com/G-awa/items/472bc1a9d46178f3d7a4) [WebSocket - AWSのサンプルで API Gateway を使ったchatアプリを作ろうとしたらハマった件](https://qiita.com/anfangd/items/e3e8cafdc365af3e7678)
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?