GoでHTTPリクエストでファイルをバルクダウンロードするスニペット
仕様
ダウンロードするファイルのURL一覧を渡すと、発生したエラー回数を返す。
- downloadの際に、エラーが発生した際のリトライ回数の制御:
maxAttempts
- サーバーへの同時接続数をセマフォで制御:
maxConcurrents
コード
package download
import (
"io"
"log"
"net/http"
"os"
"path"
"sync"
"sync/atomic"
)
const (
maxConcurrents = 64
maxAttempts = 4
)
var output = "path/to/dir"
func Do(list []string) int64 {
var wg sync.WaitGroup
var sem = make(chan struct{}, maxConcurrents)
var errCounts int64
for _, v := range list {
wg.Add(1)
go func(target string) {
defer wg.Done()
var err error
for i := 0; i < maxAttempts; i++ {
sem <- struct{}{}
err = download(target)
<-sem
if err == nil {
break
}
}
if err != nil {
atomic.AddInt64(&errCounts, 1)
log.Printf("Failed to download: %s", err)
}
}(v)
}
wg.Wait()
return errCounts
}
func download(target string) error {
resp, err := http.Get(target)
if err != nil {
return err
}
defer resp.Body.Close()
_, fileName := path.Split(target)
file, err := os.Create(path.Join(output, fileName))
if err != nil {
return err
}
_, err = io.Copy(file, resp.Body)
if closeErr := file.Close(); err == nil {
err = closeErr
}
return err
}
制御出来ていないところ
-
httpクライアントを変更できない。
-
goroutineの数は、
maxConcurrents
以上に増える。
c.f. http://blog.kaneshin.co/entry/2016/08/18/190435 -
ステータスコード毎のリトライロジック。
- 4xx (
Client Error
)が返ってきた際は、リトライせずにスキップ - 5xx が返ってきた際は、Back-offを設定出来るとより良い
- 4xx (
おまけ
簡易的には上のスニペットで、問題ないかと思いますが、
上記の3点もカバーした、worker型でファイルダウンロードするライブラリも書きました。
https://github.com/yyoshiki41/parallel-dl