Goとlambda-http-adaptorでAPIGW・ALBで動くLambdaを作る
概要
ご存じの通り、Goはクロスコンパイルが容易な上、成果物がシングルバイナリになります。
その為、AWS Lambdaに容易にデプロイできる上、コールドスタート直後からパフォーマンスも良く[1]とてもおすすめです。
この特徴を気軽に活用できるように、net/http
ベースのアプリケーションをそのまま利用する事が出来るlambda-http-adaptorを作りましたので紹介します。
これを使うと、net/http
向けに書いたhttp.Handler
が、1つのLambdaで同時に以下の環境で使う事が出来ます。
- APIGateway Lambda統合(REST、HTTP、WebSocket[2])
- Application Load Balancer(以下ALB)のLambda Target
package main
import (
"github.com/yacchi/lambda-http-adaptor"
_ "github.com/yacchi/lambda-http-adaptor/all"
"log"
"net/http"
)
func main() {
log.Fatalln(adaptor.ListenAndServe("", http.HandlerFunc(echoReplyHandler)))
}
func echoReplyHandler(w http.ResponseWriter, r *http.Request) {
m := r.URL.Query().Get("message")
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(m))
}
特徴
- APIGatewayのRESTAPI・HTTPAPI・WebSocketのいずれのモードでも、同じコードで動きます
- ALBのTargetGroupに指定するコードも、同じコードで動きます
- RequestのContextから生Lambda EventやLambda Contextを取得することが出来ます
- MultiValueHeaderやMultiValueQueryの設定に依らず動作します
使い方
例の通り、いつものhttp.ListenAndServe
に任意のハンドラを渡すだけで利用できます。
実際にサーバーを起動するわけでは無い為、第一引数のアドレス・ポートの指定は不要です(現状、指定しても何も起きません)
ステージ変数の扱い
API Gatewayを利用した場合、URL中にステージ変数が入る場合があります。APIGateway専用のコードであれば問題ありませんが、net/http
向けに普通に書かれたコードの場合はパスが変わってしまう為、都合が悪い場合があります。
この場合、付属のミドルウェア[APIGatewayStripStageVar]を使うことで、ステージ変数を削除すると良いかもしれません。
ミドルウェアの使用例
package main
import (
adaptor "github.com/yacchi/lambda-http-adaptor"
_ "github.com/yacchi/lambda-http-adaptor/all"
api2 "github.com/yacchi/lambda-http-adaptor/example/simple/api"
"github.com/yacchi/lambda-http-adaptor/middlewares"
"log"
)
func main() {
api := api2.ProvideAPI()
log.Fatalln(adaptor.ListenAndServe("0.0.0.0:8888", middlewares.APIGatewayStripStageVar(api)))
}
WebSocketの扱い
デフォルトでは、WebSocketのリクエストは次のパスへのリクエストとして動作します。
- 接続イベント(
$connect
) =>/websocket/$connect
- 切断イベント(
$disconnect
) =>/websocket/$disconnect
- デフォルトルート(
$default
) =>/websocket/$default
接続・切断のタイミングではレスポンスを返す事はできません。デフォルトルートではResponseWriter
に書き込むことで、APIGatewayのPostToConnection
APIにより、リクエスト元へレスポンスが返ります。
背景
AWSのAPIGateway+Lambda統合やALBのTargetGroupとして指定するLambdaをGoで作成する場合、APIGatewayの種別やALBなどに合わせてコードを書く必要があります。
このコードはnet/http
モジュールを利用したごく一般的なアプリケーションとは全く異なるため、既存のnet/http
で動作するアプリケーションをそのまま利用する事はできません。
APIGatewayのRESTAPIを使う場合のGoコード例
package main
import (
"encoding/json"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(request *events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
// requestを解釈し、APIGatewayProxyResponseとして返す
res, err := json.Marshal(request)
if err != nil {
return nil, err
}
return events.APIGatewayProxyResponse{
Body: string(res),
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
一応公式に aws-lambda-go-api-proxyと言う物があります。
一応こちらでもAPI GatewayのRESTAPIもしくはHTTPAPIには対応できます。しかし、
- 双方向で通信できるWebSocketを使いたい
- ALBのTargetに指定して、IPアドレス制限などを実施したい
などWebSocketを使いたい、そもそもAPI GatewayではなくApplication Load Balancer以外を使いたいと言う場合には利用できません。
また、予めRESTAPIもしくはHTTPAPI向けに作る必要があります。
-
Rustに並び非常に優れたパフォーマンスという比較もあります。
AWS Lambda battle 2021: performance comparison for all languages (cold and warm start) ↩︎ -
APIGatewayの動作の都合上、メッセージが送られる度にLambdaが起動されます。 ↩︎
Discussion