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. 自己紹介 • 安藤 尚之 • enza部 ディレクター/プロダクトオーナー • GitHub: AnD00

    • Tech: Ruby, Go, AWS, etc • 2020年1月、ドリコムに中途入社。 enza部に所属して、コード書いたりスクラムマスターやったりし てます。 多摩美大入学から10年近く演劇に人生を捧げてきたのに突然 エンジニアになってみた系エンジニア。
  2. API Gatewayとは • APIの管理や実行を簡単にしてくれる仕組み • API Gateway自体はアプリケーションではなく、 ◦ クライアントからリクエストを受け取ってそれをバックエンドに渡す ◦

    バックエンドからレスポンスを受け取ってクライアントに返す というプロキシのような働きをするもの • Lambdaと組み合わせるのが鉄板 ◦ サーバーレスでスケールを意識しなくて済むし、コストも使った分だけ!
  3. 技術スタック • AWS Serverless Application Model(SAM) • AWS Lambda •

    Amazon API Gateway • Amazon CloudWatch • Amazon DynamoDB • Golang
  4. フォルダ構成 ├── 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
  5. フォルダ構成 ├── 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の設定やテンプレート
  6. 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 }
  7. 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に 保存する
  8. 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 }
  9. 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から 削除する
  10. 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 }
  11. 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から一括で取得する 取得したコネクションすべてを対象に チャットメッセージを通知する
  12. 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 }
  13. 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のクライアントを作成する
  14. 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 }
  15. 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を指定して、 レコードを削除する
  16. 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:
  17. 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を接続先に指定する
  18. 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} ...
  19. 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関数の ログを出力
  20. 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}/
  21. 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の テーブルを作成する スタックのプロパティに 含める値を指定する
  22. 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
  23. 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
  24. 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]:
  25. 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
  26. 後片付け(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