AWS Lambda+Goで、fujiwara/ridgeからapex/gatewayへ移行する
AWS Lambdaでは以前から、非公式にNode.jsをランタイムとして、Go実装のバイナリを実行させるツールがある。例えばApexとか。さらに、Apex、API Gatewayとの組み合わせを前提としたfujiwara/ridgeというライブラリがあり、net/http
のインターフェースにそった実装ができるようになっていた。
実際に会社でも個人的にも、Apex+Go+fujiwara/ridgeという組み合わせで稼働させているものがある。現時点ではランタイムはNode.jsで動いているが、公式にGoがサポートされるようになったので、移行をしたくてしばらく検証していた。
公式のライブラリ、aws/aws-lambda-goを使用した実装に直接入れ替えるのはしんどそうな感じだったが、apex/gatewayを見つけたのでそれを試してみた。
結果、問題なさそうだったので、とりあえず個人的なやつはそちらに移行した。
移行に伴う修正点
どちらもnet/http
のインターフェースを前提としているので、スムーズに移行できる。
ridgeのサンプルを変更すると以下になる。importするパッケージは当然変わるが、Multiplexerはそのまま使用できる。直接aws-lambda-goを使う必要がないので、大きな変更がなくて助かる。
--- main_old.go 2018-04-07 00:06:44.000000000 +0900 +++ main.go 2018-04-07 00:09:00.000000000 +0900 @@ -4,7 +4,7 @@ "fmt" "net/http" - "github.com/fujiwara/ridge" + "github.com/apex/gateway" ) var mux = http.NewServeMux() @@ -15,7 +15,7 @@ } func main() { - ridge.Run(":8080", "/api", mux) + gateway.ListenAndServe(":8080", mux) }
ちょっとapex/gatewayのコードを見てみる
https://github.com/apex/gateway/blob/master/gateway.go#L16
// ListenAndServe is a drop-in replacement for // http.ListenAndServe for use within AWS Lambda. // // ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, h http.Handler) error { if h == nil { h = http.DefaultServeMux } lambda.Start(func(ctx context.Context, e events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { r, err := NewRequest(ctx, e) if err != nil { return events.APIGatewayProxyResponse{}, err } w := NewResponse() h.ServeHTTP(w, r) return w.End(), nil }) return nil }
ListenAndServe
の中では、lambda.Start
を呼び出しつつnet/http
のインターフェースに沿うように対応されている。w.End
でevents.APIGatewayProxyResponse
が返却されるはず。もう少し読み込んでみる。
https://github.com/apex/gateway/blob/master/response.go#L13
// ResponseWriter implements the http.ResponseWriter interface // in order to support the API Gateway Lambda HTTP "protocol". type ResponseWriter struct { out events.APIGatewayProxyResponse buf bytes.Buffer header http.Header wroteHeader bool closeNotifyCh chan bool }
ResponseWriter
はこんな構造になっている。
https://github.com/apex/gateway/blob/master/response.go#L39
// Write implementation. func (w *ResponseWriter) Write(b []byte) (int, error) { if !w.wroteHeader { w.WriteHeader(http.StatusOK) } return w.buf.Write(b) }
Write
のなかではResponseWriter.buf
に対しての書き込みが行われるようになっている。
https://github.com/apex/gateway/blob/master/response.go#L77
// End the request. func (w *ResponseWriter) End() events.APIGatewayProxyResponse { w.out.IsBase64Encoded = isBinary(w.header) if w.out.IsBase64Encoded { w.out.Body = base64.StdEncoding.EncodeToString(w.buf.Bytes()) } else { w.out.Body = w.buf.String() } // notify end w.closeNotifyCh <- true return w.out }
ヘッダーを見て、バイナリのデータかどうか確認しているっぽい。AWS API Gatewayではバイナリデータを返す際にはBase64エンコードされている必要があるので、それにも対応している。
out.Body
が実際のレスポンスになるわけで、そこにResponseWriter.buf
に書き込まれたデータが代入される。
ResponseWriter.closeNofityCh
にtrue
が送信されるようになっているが、これはapex/gateway内部では利用していないっぽい。利用側に通知するためのチャネルのようだ。(経緯としてはfeat(Response) Add CloseNotify support. by wolfeidau · Pull Request #7 · apex/gatewayに書いてある)
最終的には、ResponseWriter
のout
がevents.APIGatewayProxyResponse
になっているので、それを返すようにしている。
気になるところ
ちゃんとコード全体を見ていないのだけど。
fujiwara/ridge
ではできていた以下の点が apex/gateway
でできるのかきちんと確認してない(必要性もないので)
- Proxy Integrationに対応していて、リクエストがあったパスの情報をきちんとMultiplexerで処理できるようになっていた
- 自分の要求としては
/
を前提にしているので、そこまで確認してない
- 自分の要求としては
- ローカルでの起動と実行
- 自分ではそこまで必要性がなく、実際今まで使ったことがないんだけど