taketiyo.log

Web Engineering 🛠 & Body Building 💪

【Golang】S3上のデータをメモリ上に乗せずにストリーミングしてレスポンスへ流す【AWS】

Programming

  / /

GolangにてS3上のデータを取得した後、データ全体をメモリ上に乗せることなく、ストリーミングしながらHTTPレスポンスとして流す方法です。
S3からデータを取得するためのプロキシAPIを構築するようなケースを想定しています。
 
io.Readerioutil.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を使いたくなったら一旦留まろう。