読者です 読者をやめる 読者になる 読者になる

Golang: httpで落としているファイルの進行状況を取得する

プログラミング Golang

まとめ

io.Readerを埋め込んだ構造体でReadをラップして読み込んだ値を保存する

メソッドとしては確実に使いにくいので、こういう方法もあるというサンプルとしての記録

目的

golangのhttp.Getで落としているファイルの進行状況を知ること

実装

golangのversionは1.5

最初はfileのstatとかで取れるんかなとか思っていたけど、そんなことはなかったのでresponseのBodyを埋め込んでReadで読み込んだ量を保存していくようにした

package ldprogress

import (
    "io"
    "net/http"
    "os"
)

type ReaderWrap struct {
    io.Reader
    Size     int64
    Progress int
    End      bool
}

func (r *ReaderWrap) Read(b []byte) (int, error) {
    n, err := r.Reader.Read(b)

    r.Progress += n

    return n, err
}

var rw *ReaderWrap = &ReaderWrap{}

func DownloadFile(filepath, url string) *ReaderWrap {
    rw := &ReaderWrap{
        End: false,
    }

    go func() {
        res, err := http.Get(url)
        if err != nil {
            rw.End = true
            return
        }
        defer res.Body.Close()

        rw.Size = res.ContentLength
        rw.Reader = res.Body

        out, err := os.Create(filepath)
        if err != nil {
            rw.End = true
            return
        }
        defer out.Close()

        _, err = io.Copy(out, rw)
        if err != nil {
            rw.End = true
            return
        }
        rw.End = true
    }()

    return rw
}

使用サンプル

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/mizkei/ldprogress"
)

func main() {
    if len(os.Args) < 3 {
        return
    }

    filepath := os.Args[1]
    url := os.Args[2]

    rw := ldprogress.DownloadFile(filepath, url)
    ticker := time.Tick(4 * time.Second)
    for !rw.End {
        select {
        case <-ticker:
            fmt.Printf("%d / %d\n", rw.Progress, rw.Size)
        }
    }
}

github

問題点

  • goroutineの中に全部詰め込んでいる
  • ファイルサイズを各ルーチンから読み書きしてるけど、Lockとかしてない(たぶん必要)
  • EndはClose見ていればいらないような気がする
  • エラーの処理を三回同じ事書いてる
  • テストがない 一応追加