GolangにてS3上のデータを取得した後、データ全体をメモリ上に乗せることなく、ストリーミングしながらHTTPレスポンスとして流す方法です。
S3からデータを取得するためのプロキシAPIを構築するようなケースを想定しています。
io.Reader
をioutil.ReadAll
を用い[]byte
に変換した場合、データ全体がメモリ上に乗ってしまうため、数百メガ~数ギガといった巨大なファイルを扱っていると簡単にメモリ不足に陥ります。
そのため、io.Reader
のようなstreamを扱う場合は、[]byte
への変換を挟まずにそのままstreamとして扱いio.Writer
等に流してしまうほうがメモリ効率が良くなります。
目次
達成出来ること
- S3上のデータをストリーミングにて取得し、データ全体をメモリ上に乗せることなく、HTTPレスポンスとして返却するプロキシを実装する
io.Reader
からio.Writer
へデータを流す
io.Reader
からio.Writer
へデータを流す際は、io.Copy
を用います。
例)
func write(writer http.ResponseWriter, body io.ReadCloser) {
defer body.Close()
_, err := io.Copy(writer, body)
if err != nil {
panic(err)
}
}
Gin と AWS SDK を用いたサンプル
func main() {
r := gin.Default()
r.GET("/proxy", func(c *gin.Context) {
// セッションを作成
ses, err := session.NewSession(&aws.Config{
Region: aws.String(endpoints.ApNortheast1RegionID),
})
// S3のサービスクライアントを作成
svc := s3.New(ses)
// バケット名とダウンロード対象ファイルのキーを指定
bucket := "your-bucket-name"
key := "path/to/key/filename.ext"
resp, err := svc.GetObject(&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
// 必要に応じてヘッダー、レスポンスコードを設定
c.Writer.Header().Set("Content-Type", "application/json")
c.Status(200)
defer resp.Body.Close()
// resp.Body(io.ReadCloser) を io.Writer へ渡す
_, err = io.Copy(c.Writer, resp.Body)
if err != nil {
panic(err)
}
})
_ = r.Run()
}
おわりに
streamは極力streamのまま扱うべし。ioutil.ReadAll
を使いたくなったら一旦留まろう。