Shogo's Blog

Feb 6, 2024 - 2 minute read - aws aws-lambda go golang

AWS Lambda Function URLsのイベントをnet/httpで扱えるridgenativeとlambtripを書いた

AWS Lambda関数をGoの標準ライブラリのインターフェイスに変換するライブラリを作りました。

ridgenative

Lambda Function URLsではリクエストとレスポンスがJSON形式になって渡ってきます。 これらをGo標準ライブラリの net/http.Requestnet/http.ResponseWriter で扱えるように書いたアダプターが shogo82148/ridgenativeです。

同じコードをHTTPサーバーやAWS Lambda上で動かす

これのうれしいポイントは net/http.Handler の実装をひとつ用意すれば、 普通のHTTPサーバーとしても、AWS Lambda上でも動かすことができるという点です。

たとえば以下のコードは go run main.go のように実行すると、 8080ポート上で動く普通のHTTPサーバーになります。

package main

import (
  "fmt"
  "net/http"

  "github.com/shogo82148/ridgenative"
)

func main() {
  http.HandleFunc("/hello", handleRoot)

  // httpの代わりにridgenativeを呼び出す
  ridgenative.ListenAndServe(":8080", nil)
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "text/plain")
  fmt.Fprintln(w, "Hello World")
}

bootstrap という名前でコンパイルして、zipで固めてアップロードすれば、 Lambda Function URLsやAPI Gatewayで動くHTTP APIの完成です。

GOOS=linux GOARCH=arm64 CGOENABLED=0 go build -o bootstrap main.go
zip archive.zip bootstrap

ソースコードには一切手を触れることなく、まったく同じコードが動きます。

祝日APIやURL圧縮サイトの裏側ではridgenativeが動いています。 動作確認はローカルでできるので非常に便利です。


まあ、アイデア自体はfujiwara/ridgeからの借り物なんですが・・・。 ridgeから Apex 依存を取り除いたものという意味で、ridgenativeという名称で開発を始めました。 しかし、ridge本家からApex依存が取り除かれてしまったため、この点ではメリットがなくなってしまいました。

ridgenativeでResponse Streamを扱う

ridgenativeはResponse Streamにも対応しています。 通常のLambda関数は実行が完了するまで結果を受け取ることができません。 Response Streamを有効化すると、Lambda関数の実行中に少しずつ実行結果を返すことができます。

RIDGENATIVE_INVOKE_MODE 環境変数を RESPONSE_STREAM に設定することで有効化できます。

lambtrip

ridgenativeはHTTPサーバー側の実装でした。 HTTPクライアント側でも似たようなことができるのでは?と思い作ったのが、shogo82148/lambtripです。 lambtripはLambda関数の呼び出しを net/http.RoundTripper に変換します。

新しいプロトコルとして登録する

http.Transportに新しいプロトコルとして追加すれば、普通のHTTP呼び出しと透過的に扱えます。

// AWS SDKを初期化する
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
  panic(err)
}
svc := lambda.NewFromConfig(cfg)

// lambdaプロトコルを登録する
t := &http.Transport{}
t.RegisterProtocol("lambda", lambtrip.NewBufferedTransport(svc))
c := &http.Client{Transport: t}

// 普通のHTTP呼び出しと同じ感覚で扱える
resp, err := c.Get("lambda://function-name/foo/bar")
if err != nil {
  panic(err)
}
defer resp.Body.Close()

リバースプロキシする

Go標準のインターフェイスに合わせることのメリットは、http.Client以外にも組み合わせの幅が広がるという点です。 たとえばhttputil.ReverseProxyと組み合わせてみましょう。

// AWS SDKを初期化する
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
  panic(err)
}
svc := lambda.NewFromConfig(cfg)

// リバースプロキシ
proxy := &httputil.ReverseProxy{
  Director: func(req *http.Request) {
    req.URL.Host = "function-name"
  },
  Transport: lambtrip.NewBufferedTransport(svc),
}
if err := http.ListenAndServe(":8080", proxy); err != nil {
  panic(err)
}

HTTPリクエストを受け取ってAWS Lambdaを起動する、自作のFunction URLsを簡単に作ることができます。

lambtripでResponse Streamを扱う

lambtripもResponse Streamに対応しています。 Response Streamを有効化するには、lambtrip.BufferedTransportの代わりに lambtrip.ResponseStreamTransportを使用します。

// AWS SDKを初期化する
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
  panic(err)
}
svc := lambda.NewFromConfig(cfg)

// lambtrip.NewResponseStreamTransportを使ってlambdaプロトコルを登録する
t := &http.Transport{}
t.RegisterProtocol("lambda", lambtrip.NewResponseStreamTransport(svc))
c := &http.Client{Transport: t}

まとめ

AWS Lambda関数をGoの標準ライブラリのインターフェイスに変換するライブラリを作りました。

標準ライブラリのインターフェイスなので、色々な組み合わせで遊べると思います。 ぜひ試してみてください。

参考