API Gateway+AWS SAM+GolangでWebSocket通信を行う

AWS認定 SAPの勉強に日々精進しております。やっさんでございます。
今回は、AWS SAMとGo言語を用いて、
WebSocket通信によるチャット機能を実践してみたいと思います。

概要図と処理内容

Amazon API GatewayでWebSocketが利用可能になったのは、
去年の2018年12月になりますので、まだまだ記憶に新しい出来事です。
[発表]Amazon API GatewayでWebsocketが利用可能

これまでは双方向の振る舞いを行うための永続的な管理を担当する
ホストサーバーが必要でしたが、サーバーレスで実現することで不要になります。

API GatewayによるWebSocket通信の概要図

以下の概要図から簡単に処理内容を説明いたします。
AWS公式から引用

  • ⑵ クライアントがWebSocketのエンドポイントに接続した時に onConnect のLabmdaが実行されます。この時、コネクション情報をDynamoDBに永続化します。
  • ⑶ クライアントがサーバーにメッセージを送信します。
  • sendMessage のLambdaは全てのコネクション情報を宛先としてメッセージを送ります。
  • ⑸ クライアントがWebSocket通信から切断すると、 onDisconect のLambdaが実行されます。この時、コネクション情報をDynamoDBから削除します。

詳細なコード内容はGitHubもご参照下さい。
https://github.com/x-blood/xblood-go-sam-websocket

それでは、実践していきましょう!

onConnectの処理内容

onConnectのLambdaファンクションは、
コネクション情報をDynamoDBに永続化します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func HandleRequest(request events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("start on_connect")
connectionID := request.RequestContext.ConnectionID
fmt.Printf("connectionId : %s ¥n", connectionID)

// コネクション情報をPutする
err := dynamodb.Put(connectionID)
if err != nil {
fmt.Println(err)
return response.Create500response()
}

fmt.Println("end on_connect")
return response.Create200response()
}

DynamoDBへのCRUDをより便利にするライブラリguregu/dynamoを利用しています。

sendMessageの処理内容

sendMessageは、全てのコネクション情報を取得し、
クライアントから送信されたメッセージ
接続中の全てのクライアントに送信します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func HandleRequest(request events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("start send_message")

// 全てのコネクション情報を取得
connections, err := dynamodb.GetAll()
if err != nil {
response.Create500response()
}

var config *aws.Config
newSession, err := session.NewSession(config)
if err != nil {
response.Create500response()
}

// クライアントから送信されたメッセージを取得する
var postData postData
err = json.Unmarshal([]byte(request.Body), &postData)
if err != nil {
response.Create500response()
}

svc := apigatewaymanagementapi.New(newSession)
svc.Endpoint = fmt.Sprintf("https://%s/%s", request.RequestContext.DomainName, request.RequestContext.Stage)

for _, connection := range connections {
connectionID := connection.ConnectionID

// コネクション情報を宛先としメッセージを送信する
svc.PostToConnection(&apigatewaymanagementapi.PostToConnectionInput{
ConnectionId: &connectionID,
Data: []byte(postData.Data),
})
}

fmt.Println("end send_message")
return response.Create200response()
}

onDisconnectの処理内容

onDisconnectは、クライアントが切断した場合に
コネクション情報をDynamoDBから削除します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func HandleRequest(request events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("start on_disconnect")
connectionID := request.RequestContext.ConnectionID
fmt.Printf("connectionId : %s ¥n", connectionID)

// コネクション情報をDeleteする
err := dynamodb.Delete(connectionID)
if err != nil {
fmt.Println(err)
return response.Create500response()
}
fmt.Println("end on_disconnect")
return response.Create200response()
}

AWS SAM template.ymlの内容

template.ymlの内容は盛りだくさんのため、
GitHubの当該コードをご参照下さい。
https://github.com/x-blood/xblood-go-sam-websocket/blob/master/template.yml

API Gateway、Lambda、DynamoDBなど全てのリソースを、
AWS SAMを用いて生成しています。

チャットを起動してみる

今回はコマンドラインツールの wscat を利用して動作確認してみます。

1. 接続

wscatでWebSocketのエンドポイントに接続します。
分かりやすいように、クライアント1、2でターミナルの色を分けました。

  • クライアント1

  • クライアント2

connected と表示され、接続に成功していることが確認できます。

2. 接続後のコネクション情報

接続に成功すると、DynamoDBにもコネクション情報が保存されています。

3. メッセージ送信

それでは、クライアント1からメッセージを送信してみます。

問題なくメッセージが送信できています。

4. メッセージ受信

クライアント2からメッセージが受信できることを確認します。

クライアント1で送信したメッセージを、
クライアント2が受信できていることを確認できました!!

5. コネクション切断

コネクションを切断してみます。

6. コネクション切断後のコネクション情報

DynamoDBからもコネクション情報が削除されていることを確認できました!

いかがでしたでしょうか。
サーバレスによるWebSocket通信は、
クライアントアプリケーションを強化するためのバックエンドサービスとして
比較的簡単に導入することができるでしょう。

弊社ではGo言語とAWS SAMを活用したサーバレスアプリケーションの開発実績があります

少しでもご興味を持ちましたら、弊社にエントリーしていただければ幸いです。
一緒に楽しく成長しあえる仲間を募集しています。

リモートワークでAWSなサーバーレスシステムに携わりたいエンジニア大募集

以上です。

このエントリーをはてなブックマークに追加