Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting Started with Websocket from API Gateway

AnD00
March 13, 2022

Getting Started with Websocket from API Gateway

AnD00

March 13, 2022
Tweet

More Decks by AnD00

Other Decks in Technology

Transcript

  1. Amazon API Gateway
    から
    WebSocket
    へ御入門
    - enza部 AnD00 -

    View Slide

  2. はじめに

    View Slide

  3. 自己紹介
    ● 安藤 尚之
    ● enza部 ディレクター/プロダクトオーナー
    ● GitHub: AnD00
    ● Tech: Ruby, Go, AWS, etc
    ● 2020年1月、ドリコムに中途入社。
    enza部に所属して、コード書いたりスクラムマスターやったりし
    てます。
    多摩美大入学から10年近く演劇に人生を捧げてきたのに突然
    エンジニアになってみた系エンジニア。

    View Slide

  4. enzaとは
    人気作品の新作タイトルが遊べるスマートフォン向けブラウザゲー
    ムプラットフォーム「enza」大好評配信中!
    気の合う仲間同士が輪になってワイワイ楽しめる場所、それがenza
    (エンザ)です。
    現在配信中タイトルの「アイドルマスター シャイニーカラーズ」など、
    ぜひお楽しみください!
    [引用
    元]https://www.bandainamcoid.com/portal/serviceDetail?sv=110000&backto=%2Fportal%2FserviceDetail%3Fs
    v%3D1203

    View Slide

  5. アジェンダ

    View Slide

  6. アジェンダ
    ● ターゲットとゴール
    ● Websocket APIについて
    ● つくるもの
    ● つくりかた
    ● まとめ

    View Slide

  7. ターゲットとゴール

    View Slide

  8. ターゲット
    ● Websocketについて知りたい
    ● サーバーレスについて知りたい
    ● AWSに興味がある

    View Slide

  9. ゴール
    ● Websocketアプリケーションの概要を理解する
    ● API Gatewayを使って、Websocket APIが作れる(ような気がする)

    View Slide

  10. Websocket APIについて

    View Slide

  11. Websocketとは
    ● WebサーバーとWebブラウザの間で、低コストで双方向通信できるようにするため
    の仕組み
    ● HTTPでは通常、「クライアントからのリクエストにサーバーがレスポンスを返す」とい
    う形でしか通信できない(ロングポーリング等やりようはある)
    ● WebSocketでは一度コネクションを確立すれば、クライアント・サーバーどちらから
    でも能動的にメッセージを送ることができる

    View Slide

  12. APIとは
    ● Application Programming Interfaceの略称
    ● ソフトウェアやプログラム、Webサービスの間で情報をやりとりするために使うイン
    ターフェースの仕様

    View Slide

  13. API Gatewayとは
    ● APIの管理や実行を簡単にしてくれる仕組み
    ● API Gateway自体はアプリケーションではなく、
    ○ クライアントからリクエストを受け取ってそれをバックエンドに渡す
    ○ バックエンドからレスポンスを受け取ってクライアントに返す
    というプロキシのような働きをするもの
    ● Lambdaと組み合わせるのが鉄板
    ○ サーバーレスでスケールを意識しなくて済むし、コストも使った分だけ!

    View Slide

  14. つくるもの

    View Slide

  15. チャット機能
    1. ユーザーは任意のルームに入室できる
    2. ユーザーは入室したルーム内でメッセージが送受信できる
    3. ユーザーは任意のルームから退出できる

    View Slide

  16. 技術スタック
    ● AWS Serverless Application Model(SAM)
    ● AWS Lambda
    ● Amazon API Gateway
    ● Amazon CloudWatch
    ● Amazon DynamoDB
    ● Golang

    View Slide

  17. システム構成

    View Slide

  18. つくりかた

    View Slide

  19. ソースコード
    ● https://github.com/AnD00/simple-websockets-chat-app

    View Slide

  20. フォルダ構成
    ├── Makefile
    ├── README.md
    ├── connect
    │ ├── Makefile
    │ └── connect.go
    ├── disconnect
    │ ├── Makefile
    │ └── disconnect.go
    ├── docker-compose.yml
    ├── go.mod
    ├── go.sum
    ├── lib
    │ ├── apigw
    │ └── dynamodb
    ├── publish
    │ ├── Makefile
    │ └── publish.go
    ├── samconfig.toml
    ├── template.yaml
    └── testdata
    ├── event_connection.json
    └── event_publish.json

    View Slide

  21. フォルダ構成
    ├── Makefile
    ├── README.md
    ├── connect
    │ ├── Makefile
    │ └── connect.go
    ├── disconnect
    │ ├── Makefile
    │ └── disconnect.go
    ├── docker-compose.yml
    ├── go.mod
    ├── go.sum
    ├── lib
    │ ├── apigw
    │ └── dynamodb
    ├── publish
    │ ├── Makefile
    │ └── publish.go
    ├── samconfig.toml
    ├── template.yaml
    └── testdata
    ├── event_connection.json
    └── event_publish.json
    チャットルーム入室の関数
    チャットルーム退室の関数
    外部サービスとやりとりする
    ためのライブラリ
    チャットメッセージ送信の関数
    SAMの設定やテンプレート

    View Slide

  22. AWS Lambda
    ● チャットルームへの入室
    ● チャットメッセージの送信
    ● チャットルームから退出

    View Slide

  23. connect.go
    package main
    import (
    "context"
    "fmt"
    "simple-websockets-chat-app/lib/apigw"
    "simple-websockets-chat-app/lib/dynamodb"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    )
    func main() {
    lambda.Start(handler)
    }
    func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    fmt.Println("websocket connect")
    err := dynamodb.PutConnection(req.RequestContext.ConnectionID, req.QueryStringParameters["room"])
    if err != nil {
    fmt.Println(err)
    return apigw.InternalServerErrorResponse(), err
    }
    fmt.Println("websocket connection cached")
    return apigw.OkResponse(), nil
    }

    View Slide

  24. connect.go
    package main
    import (
    "context"
    "fmt"
    "simple-websockets-chat-app/lib/apigw"
    "simple-websockets-chat-app/lib/dynamodb"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    )
    func main() {
    lambda.Start(handler)
    }
    func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    fmt.Println("websocket connect")
    err := dynamodb.PutConnection(req.RequestContext.ConnectionID, req.QueryStringParameters["room"])
    if err != nil {
    fmt.Println(err)
    return apigw.InternalServerErrorResponse(), err
    }
    fmt.Println("websocket connection cached")
    return apigw.OkResponse(), nil
    }
    WebsocketのコネクションIDと
    チャットルーム情報を DynamoDBに
    保存する

    View Slide

  25. disconnect.go
    package main
    import (
    "context"
    "fmt"
    "simple-websockets-chat-app/lib/apigw"
    "simple-websockets-chat-app/lib/dynamodb"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    )
    func main() {
    lambda.Start(handler)
    }
    func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    fmt.Println("websocket disconnect")
    err := dynamodb.DeleteConnection(req.RequestContext.ConnectionID)
    if err != nil {
    fmt.Println(err)
    return apigw.InternalServerErrorResponse(), err
    }
    fmt.Println("websocket connection deleted from cache")
    return apigw.OkResponse(), nil
    }

    View Slide

  26. disconnect.go
    package main
    import (
    "context"
    "fmt"
    "simple-websockets-chat-app/lib/apigw"
    "simple-websockets-chat-app/lib/dynamodb"
    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
    )
    func main() {
    lambda.Start(handler)
    }
    func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    fmt.Println("websocket disconnect")
    err := dynamodb.DeleteConnection(req.RequestContext.ConnectionID)
    if err != nil {
    fmt.Println(err)
    return apigw.InternalServerErrorResponse(), err
    }
    fmt.Println("websocket connection deleted from cache")
    return apigw.OkResponse(), nil
    }
    WebsocketのコネクションIDと
    チャットルーム情報を DynamoDBから
    削除する

    View Slide

  27. publish.go
    ...
    func handler(ctx context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    apiClient := apigw.NewAPIGatewayManagementClient(req.RequestContext.DomainName, req.RequestContext.Stage)
    input, err := new(ws.InputEnvelop).Decode([]byte(req.Body))
    if err != nil {
    return apigw.BadRequestResponse(), err
    }
    output := &ws.OutputEnvelop{
    Data: input.Data,
    Received: time.Now().Unix(),
    }
    data, err := output.Encode()
    if err != nil {
    return apigw.InternalServerErrorResponse(), err
    }
    conns, err := dynamodb.GetAllConnections(input.Room)
    if err != nil {
    return apigw.InternalServerErrorResponse(), err
    }
    sender := req.RequestContext.ConnectionID
    for _, conn := range conns {
    id := conn.ConnectionID
    if id == sender {
    continue
    }
    ws.Publish(apiClient, ctx, id, data)
    }
    return apigw.OkResponse(), nil
    }

    View Slide

  28. publish.go
    ...
    func handler(ctx context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
    apiClient := apigw.NewAPIGatewayManagementClient(req.RequestContext.DomainName, req.RequestContext.Stage)
    input, err := new(ws.InputEnvelop).Decode([]byte(req.Body))
    if err != nil {
    return apigw.BadRequestResponse(), err
    }
    output := &ws.OutputEnvelop{
    Data: input.Data,
    Received: time.Now().Unix(),
    }
    data, err := output.Encode()
    if err != nil {
    return apigw.InternalServerErrorResponse(), err
    }
    conns, err := dynamodb.GetAllConnections(input.Room)
    if err != nil {
    return apigw.InternalServerErrorResponse(), err
    }
    sender := req.RequestContext.ConnectionID
    for _, conn := range conns {
    id := conn.ConnectionID
    if id == sender {
    continue
    }
    ws.Publish(apiClient, ctx, id, data)
    }
    return apigw.OkResponse(), nil
    }
    リクエストボディをデコードする
    同じチャットルームのコネクションを
    DynamoDBから一括で取得する
    取得したコネクションすべてを対象に
    チャットメッセージを通知する

    View Slide

  29. Amazon DynamoDB
    ● 接続情報の保存
    ● 同じチャットルームの
    接続情報一覧の参照
    ● 接続情報の削除

    View Slide

  30. dynamodb/dynamodb.go
    package dynamodb
    import (
    "os"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/guregu/dynamo"
    "github.com/pkg/errors"
    )
    func getTable(db *dynamo.DB, tableName string) dynamo.Table {
    return db.Table(tableName)
    }
    func connect() (*dynamo.DB, error) {
    config := aws.Config{
    Endpoint: aws.String(os.Getenv("DYNAMO_ENDPOINT")),
    }
    dynamoSession, err := session.NewSession(&config)
    if err != nil {
    return nil, errors.WithStack(err)
    }
    return dynamo.New(dynamoSession), nil
    }

    View Slide

  31. dynamodb/dynamodb.go
    package dynamodb
    import (
    "os"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/guregu/dynamo"
    "github.com/pkg/errors"
    )
    func getTable(db *dynamo.DB, tableName string) dynamo.Table {
    return db.Table(tableName)
    }
    func connect() (*dynamo.DB, error) {
    config := aws.Config{
    Endpoint: aws.String(os.Getenv("DYNAMO_ENDPOINT")),
    }
    dynamoSession, err := session.NewSession(&config)
    if err != nil {
    return nil, errors.WithStack(err)
    }
    return dynamo.New(dynamoSession), nil
    }
    指定されたテーブルのハンドラを返す
    DynamoDBのクライアントを作成する

    View Slide

  32. dynamodb/connection.go
    package dynamodb
    import (
    "os"
    "github.com/pkg/errors"
    )
    const connectionsTableNameTemplate = "simple-websockets-chat-app-connections"
    type Connection struct {
    ConnectionID string `dynamo:"connectionId,hash"`
    Room string `dynamo:"room" index:"room-index,hash"`
    }
    func getConnectionsTableName() string {
    return connectionsTableNameTemplate + "-" + os.Getenv("STAGE_NAME")
    }
    func GetAllConnections(room string) ([]Connection, error) {
    db, err := connect()
    if err != nil {
    return nil, errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    var results []Connection
    err = table.Get("room", room).Index("room-index").All(&results)
    if err != nil {
    return nil, errors.WithStack(err)
    }
    return results, nil
    }
    ...
    func PutConnection(connectionId string, room string) error {
    db, err := connect()
    if err != nil {
    return errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    putModel := Connection{
    ConnectionID: connectionId,
    Room: room,
    }
    err = table.Put(putModel).Run()
    if err != nil {
    return errors.WithStack(err)
    }
    return nil
    }
    func DeleteConnection(connectionId string) error {
    db, err := connect()
    if err != nil {
    return errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    err = table.Delete("connectionId", connectionId).Run()
    if err != nil {
    return errors.WithStack(err)
    }
    return nil
    }

    View Slide

  33. dynamodb/connection.go
    package dynamodb
    import (
    "os"
    "github.com/pkg/errors"
    )
    const connectionsTableNameTemplate = "simple-websockets-chat-app-connections"
    type Connection struct {
    ConnectionID string `dynamo:"connectionId,hash"`
    Room string `dynamo:"room" index:"room-index,hash"`
    }
    func getConnectionsTableName() string {
    return connectionsTableNameTemplate + "-" + os.Getenv("STAGE_NAME")
    }
    func GetAllConnections(room string) ([]Connection, error) {
    db, err := connect()
    if err != nil {
    return nil, errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    var results []Connection
    err = table.Get("room", room).Index("room-index").All(&results)
    if err != nil {
    return nil, errors.WithStack(err)
    }
    return results, nil
    }
    ...
    func PutConnection(connectionId string, room string) error {
    db, err := connect()
    if err != nil {
    return errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    putModel := Connection{
    ConnectionID: connectionId,
    Room: room,
    }
    err = table.Put(putModel).Run()
    if err != nil {
    return errors.WithStack(err)
    }
    return nil
    }
    func DeleteConnection(connectionId string) error {
    db, err := connect()
    if err != nil {
    return errors.WithStack(err)
    }
    tableName := getConnectionsTableName()
    table := getTable(db, tableName)
    err = table.Delete("connectionId", connectionId).Run()
    if err != nil {
    return errors.WithStack(err)
    }
    return nil
    }
    Global secondary indexを使い、
    同じroomの値を持つレコードを
    一括取得する
    Key/Valueを指定して、
    レコードを作成する
    Primary Keyを指定して、
    レコードを削除する

    View Slide

  34. docker-compose.yml
    version: '3'
    services:
    dynamodb-local:
    container_name: dynamodb-local
    image: amazon/dynamodb-local:latest
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath /data
    volumes:
    - dynamodb-local-data:/data
    ports:
    - 8000:8000
    dynamodb-admin:
    container_name: dynamodb-admin
    image: aaronshaf/dynamodb-admin:latest
    environment:
    - DYNAMO_ENDPOINT=http://host.docker.internal:8000
    ports:
    - 8001:8001
    depends_on:
    - dynamodb-local
    volumes:
    dynamodb-local-data:

    View Slide

  35. docker-compose.yml
    version: '3'
    services:
    dynamodb-local:
    container_name: dynamodb-local
    image: amazon/dynamodb-local:latest
    user: root
    command: -jar DynamoDBLocal.jar -sharedDb -dbPath /data
    volumes:
    - dynamodb-local-data:/data
    ports:
    - 8000:8000
    dynamodb-admin:
    container_name: dynamodb-admin
    image: aaronshaf/dynamodb-admin:latest
    environment:
    - DYNAMO_ENDPOINT=http://host.docker.internal:8000
    ports:
    - 8001:8001
    depends_on:
    - dynamodb-local
    volumes:
    dynamodb-local-data:
    dbPathのオプションを指定して、
    コンテナ終了時にデータが消えないようにする
    dynamodb-localを接続先に指定する

    View Slide

  36. dynamodb-admin

    View Slide

  37. dynamodb-admin

    View Slide

  38. AWS
    Serverless Application
    Model
    ● API GatewayやLambdaなど、
    アプリケーションに必要な
    リソースを構築する

    View Slide

  39. AWSTemplateFormatVersion: "2010-09-09"
    Transform: AWS::Serverless-2016-10-31
    ...
    Resources:
    WebSocket:
    Type: AWS::ApiGatewayV2::Api
    Properties:
    Name: !Ref ApplicationName
    ProtocolType: WEBSOCKET
    RouteSelectionExpression: "$request.body.message"
    ConnectFunction:
    Type: AWS::Serverless::Function
    Metadata:
    BuildMethod: makefile
    Properties:
    Policies:
    - DynamoDBCrudPolicy:
    TableName: !Ref ConnectionsTable
    ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
    RouteKey: $connect
    ApiId: !Ref WebSocket
    AuthorizationType: NONE
    OperationName: ConnectRoute
    Target: !Join
    - "/"
    - - "integrations"
    - !Ref ConnectIntegration
    ...
    template.yml
    ConnectIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
    ApiId: !Ref WebSocket
    IntegrationType: AWS_PROXY
    IntegrationUri: !Sub
    arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn
    }/invocations
    ConnectFunctionPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
    - WebSocket
    Properties:
    Action: lambda:InvokeFunction
    Principal: apigateway.amazonaws.com
    FunctionName: !Ref ConnectFunction
    ConnectFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    DependsOn:
    - ConnectFunction
    Properties:
    RetentionInDays: 30
    LogGroupName: !Sub /aws/lambda/${ConnectFunction}
    ...

    View Slide

  40. AWSTemplateFormatVersion: "2010-09-09"
    Transform: AWS::Serverless-2016-10-31
    ...
    Resources:
    WebSocket:
    Type: AWS::ApiGatewayV2::Api
    Properties:
    Name: !Ref ApplicationName
    ProtocolType: WEBSOCKET
    RouteSelectionExpression: "$request.body.message"
    ConnectFunction:
    Type: AWS::Serverless::Function
    Metadata:
    BuildMethod: makefile
    Properties:
    Policies:
    - DynamoDBCrudPolicy:
    TableName: !Ref ConnectionsTable
    ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
    RouteKey: $connect
    ApiId: !Ref WebSocket
    AuthorizationType: NONE
    OperationName: ConnectRoute
    Target: !Join
    - "/"
    - - "integrations"
    - !Ref ConnectIntegration
    ...
    template.yml
    ConnectIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
    ApiId: !Ref WebSocket
    IntegrationType: AWS_PROXY
    IntegrationUri: !Sub
    arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn
    }/invocations
    ConnectFunctionPermission:
    Type: AWS::Lambda::Permission
    DependsOn:
    - WebSocket
    Properties:
    Action: lambda:InvokeFunction
    Principal: apigateway.amazonaws.com
    FunctionName: !Ref ConnectFunction
    ConnectFunctionLogGroup:
    Type: AWS::Logs::LogGroup
    DependsOn:
    - ConnectFunction
    Properties:
    RetentionInDays: 30
    LogGroupName: !Sub /aws/lambda/${ConnectFunction}
    ...
    APIを作成する
    Lambda関数を
    作成する
    APIのルートを
    作成する
    APIにLambda関数
    を呼び出す権限を
    付与する
    APIの統合リクエ
    ストを作成する
    Lambda関数の
    ログを出力

    View Slide

  41. template.yml
    Deployment:
    Type: AWS::ApiGatewayV2::Deployment
    DependsOn:
    - ConnectRoute
    ...
    Properties:
    ApiId: !Ref WebSocket
    Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
    StageName: !Ref StageName
    ApiId: !Ref WebSocket
    DeploymentId: !Ref Deployment
    DefaultRouteSettings:
    LoggingLevel: INFO
    DataTraceEnabled: true
    DetailedMetricsEnabled: true
    ...
    ConnectionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
    AttributeDefinitions:
    - AttributeName: connectionId
    AttributeType: S
    - AttributeName: room
    AttributeType: S
    KeySchema:
    - AttributeName: connectionId
    KeyType: HASH
    ProvisionedThroughput:
    ReadCapacityUnits: 5
    WriteCapacityUnits: 5
    GlobalSecondaryIndexes:
    - IndexName: room-index
    KeySchema:
    - AttributeName: room
    KeyType: HASH
    ProvisionedThroughput:
    ReadCapacityUnits: 5
    WriteCapacityUnits: 5
    Projection:
    ProjectionType: ALL
    SSESpecification:
    SSEEnabled: False
    TableName: !Sub ${ApplicationName}-connections-${StageName}
    Outputs:
    WebSocketEndpoint:
    Value: !Sub
    wss://${WebSocket}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/

    View Slide

  42. template.yml
    Deployment:
    Type: AWS::ApiGatewayV2::Deployment
    DependsOn:
    - ConnectRoute
    ...
    Properties:
    ApiId: !Ref WebSocket
    Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
    StageName: !Ref StageName
    ApiId: !Ref WebSocket
    DeploymentId: !Ref Deployment
    DefaultRouteSettings:
    LoggingLevel: INFO
    DataTraceEnabled: true
    DetailedMetricsEnabled: true
    ...
    ConnectionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
    AttributeDefinitions:
    - AttributeName: connectionId
    AttributeType: S
    - AttributeName: room
    AttributeType: S
    KeySchema:
    - AttributeName: connectionId
    KeyType: HASH
    ProvisionedThroughput:
    ReadCapacityUnits: 5
    WriteCapacityUnits: 5
    GlobalSecondaryIndexes:
    - IndexName: room-index
    KeySchema:
    - AttributeName: room
    KeyType: HASH
    ProvisionedThroughput:
    ReadCapacityUnits: 5
    WriteCapacityUnits: 5
    Projection:
    ProjectionType: ALL
    SSESpecification:
    SSEEnabled: False
    TableName: !Sub ${ApplicationName}-connections-${StageName}
    Outputs:
    WebSocketEndpoint:
    Value: !Sub
    wss://${WebSocket}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/
    APIのステージ
    (環境)を作成する
    APIのデプロイメント
    を作成する
    DynamoDBの
    テーブルを作成する
    スタックのプロパティに
    含める値を指定する

    View Slide

  43. sam build
    ~/g/g/A/simple-websockets-chat-app ❯❯❯ make clean build
    /Library/Developer/CommandLineTools/usr/bin/make -C connect clean
    rm -rfv bin
    /Library/Developer/CommandLineTools/usr/bin/make -C disconnect clean
    rm -rfv bin
    /Library/Developer/CommandLineTools/usr/bin/make -C publish clean
    rm -rfv bin
    building handlers for aws lambda
    sam build
    Building codeuri: /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app runtime: go1.x metadata: {'BuildMethod': 'makefile'} architecture: x86_64
    functions: ['ConnectFunction']
    Running CustomMakeBuilder:CopySource
    Running CustomMakeBuilder:MakeBuild
    Current Artifacts Directory : /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app/.aws-sam/build/ConnectFunction
    Building codeuri: /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app runtime: go1.x metadata: {'BuildMethod': 'makefile'} architecture: x86_64
    functions: ['DisconnectFunction']
    Running CustomMakeBuilder:CopySource
    Running CustomMakeBuilder:MakeBuild
    Current Artifacts Directory : /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app/.aws-sam/build/DisconnectFunction
    Building codeuri: /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app runtime: go1.x metadata: {'BuildMethod': 'makefile'} architecture: x86_64
    functions: ['PublishFunction']
    Running CustomMakeBuilder:CopySource
    Running CustomMakeBuilder:MakeBuild
    Current Artifacts Directory : /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app/.aws-sam/build/PublishFunction
    Build Succeeded
    Built Artifacts : .aws-sam/build
    Built Template : .aws-sam/build/template.yaml
    Commands you can use next
    =========================
    [*] Invoke Function: sam local invoke
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
    [*] Deploy: sam deploy --guided

    View Slide

  44. sam local invoke
    ~/g/g/A/simple-websockets-chat-app ❯❯❯ sam local invoke ConnectFunction -e testdata/event_connection.json
    Invoking bootstrap (go1.x)
    Skip pulling image and use local one: public.ecr.aws/sam/emulation-go1.x:rapid-1.37.0-x86_64.
    Mounting /Users/naoyukiando/ghq/github.com/AnD00/simple-websockets-chat-app/.aws-sam/build/ConnectFunction as /var/task:ro,delegated
    inside runtime container
    START RequestId: d3edc6df-0d32-4844-bcca-d44a1aad4e4f Version: $LATEST
    websocket connect
    websocket connection cached
    {"statusCode":200,"headers":null,"multiValueHeaders":null,"body":""}END RequestId: d3edc6df-0d32-4844-bcca-d44a1aad4e4f
    REPORT RequestId: d3edc6df-0d32-4844-bcca-d44a1aad4e4f Init Duration: 0.33 ms Duration: 181.49 ms Billed Duration: 182 ms Memory
    Size: 512 MB Max Memory Used: 512 MB

    View Slide

  45. sam deploy
    ~/g/g/A/simple-websockets-chat-app ❯❯❯ sam deploy
    Uploading to simple-websockets-chat-app/4c81dd4bb11c6d37226e889c7bd580e7 4054042 / 4054042 (100.00%)
    Uploading to simple-websockets-chat-app/c81a285b0602438de0e3bf25a9f5b597 4032349 / 4032349 (100.00%)
    Uploading to simple-websockets-chat-app/c5bc02bd98696c8959e5cebe9ca8b9e8 4145071 / 4145071 (100.00%)
    Deploying with following values
    ===============================
    Stack name : simple-websockets-chat-app
    Region : ap-northeast-1
    Confirm changeset : True
    Disable rollback : False
    Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-1xj859y6o8i0b
    Capabilities : ["CAPABILITY_IAM"]
    Parameter overrides : {"ApplicationName": "simple-websockets-chat-app", "DynamoEndpoint": "", "StageName": "develop"}
    Signing Profiles : {}
    Initiating deployment
    =====================
    Uploading to simple-websockets-chat-app/eb99c7fac22b19b31309262a174e7c72.template 7491 / 7491 (100.00%)
    Waiting for changeset to be created..
    CloudFormation stack changeset
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    Operation LogicalResourceId ResourceType Replacement
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    + Add ConnectFunctionLogGroup AWS::Logs::LogGroup N/A
    ...
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:317496045611:changeSet/samcli-deploy1647007398/ae55b142-9151-4d60-82d6-3b6ab18ca217
    Previewing CloudFormation changeset before deployment
    ======================================================
    Deploy this changeset? [y/N]:

    View Slide

  46. sam deploy
    2022-03-11 23:04:59 - Waiting for stack create/update to complete
    CloudFormation events from stack operations
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    CREATE_IN_PROGRESS AWS::DynamoDB::Table ConnectionsTable -
    CREATE_IN_PROGRESS AWS::ApiGatewayV2::Api WebSocket -
    CREATE_IN_PROGRESS AWS::DynamoDB::Table ConnectionsTable Resource creation
    ...
    CREATE_COMPLETE AWS::CloudFormation::Stack simple-websockets-chat-app -
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    CloudFormation outputs from deployed stack
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    Outputs
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    Key WebSocketEndpoint
    Description -
    Value wss://r8mgbs7a31.execute-api.ap-northeast-1.amazonaws.com/develop/
    --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    -----------------------
    Successfully created/updated stack - simple-websockets-chat-app in ap-northeast-1

    View Slide

  47. simple-websockets-chat-app

    View Slide

  48. 実際に
    動かしてみる
    ● チャットメッセージの送信
    ● 接続情報の保存
    ● ログの出力

    View Slide

  49. チャットメッセージの送信

    View Slide

  50. 接続情報の保存

    View Slide

  51. ログの出力

    View Slide

  52. システム構成
    完成

    View Slide

  53. お金の話 ● 各サービスの料金
    ● 後片付け

    View Slide

  54. 各サービスの料金(API Gateway)

    View Slide

  55. 各サービスの料金(Lambda)

    View Slide

  56. 各サービスの料金(DynamoDB)

    View Slide

  57. 各サービスの料金(CloudWatch)

    View Slide

  58. 後片付け(sam delete)
    ~/g/g/A/simple-websockets-chat-app ❯❯❯ sam delete --config-file samconfig.toml
    Are you sure you want to delete the stack simple-websockets-chat-app in the region ap-northeast-1 ? [y/N]: y
    Are you sure you want to delete the folder simple-websockets-chat-app in S3 which contains the artifacts? [y/N]: y
    - Deleting S3 object with key simple-websockets-chat-app/4c81dd4bb11c6d37226e889c7bd580e7
    - Deleting S3 object with key simple-websockets-chat-app/c81a285b0602438de0e3bf25a9f5b597
    - Deleting S3 object with key simple-websockets-chat-app/c5bc02bd98696c8959e5cebe9ca8b9e8
    - Deleting S3 object with key simple-websockets-chat-app/9dc03003f3c1f0b4378ec79252c43630
    - Deleting S3 object with key simple-websockets-chat-app/a0739d16f65ef707e530bd11d4f7a97b
    - Deleting S3 object with key simple-websockets-chat-app/b5edd04a5d3d134c63bf3799fbefab1e.template
    - Deleting S3 object with key simple-websockets-chat-app/c8879faaa0a1bbe9d15e2b33fe23a2e2
    - Deleting S3 object with key simple-websockets-chat-app/eb99c7fac22b19b31309262a174e7c72.template
    - Deleting Cloudformation stack simple-websockets-chat-app
    Deleted successfully

    View Slide

  59. まとめ

    View Slide

  60. まとめ
    ● API Gatewayを使うと、簡単にWebsocket APIが作れる
    興味あれば是非つくってみて、
    そして動かしてみてください 🔧🔨

    View Slide

  61. ご静聴
    🎉 ありがとうございました 🎉

    View Slide