2013-08-23 72 views
1

我有下面的代碼,這是假設下載文件通過拆分成多個部分。但現在它只適用於圖像,當我嘗試下載其他文件,如tar文件輸出是一個無效的文件。轉到文件下載器

更新:

使用os.WriteAt,而不是os.Write和刪除os.O_APPEND文件模式。

package main 

import (
    "errors" 
    "flag" 
    "fmt" 
    "io/ioutil" 
    "log" 
    "net/http" 
    "os" 
    "strconv" 
) 

var file_url string 
var workers int 
var filename string 

func init() { 
    flag.StringVar(&file_url, "url", "", "URL of the file to download") 
    flag.StringVar(&filename, "filename", "", "Name of downloaded file") 
    flag.IntVar(&workers, "workers", 2, "Number of download workers") 
} 

func get_headers(url string) (map[string]string, error) { 
    headers := make(map[string]string) 
    resp, err := http.Head(url) 
    if err != nil { 
     return headers, err 
    } 

    if resp.StatusCode != 200 { 
     return headers, errors.New(resp.Status) 
    } 

    for key, val := range resp.Header { 
     headers[key] = val[0] 
    } 
    return headers, err 
} 

func download_chunk(url string, out string, start int, stop int) { 
    client := new(http.Client) 
    req, _ := http.NewRequest("GET", url, nil) 
    req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, stop)) 
    resp, _ := client.Do(req) 

    defer resp.Body.Close() 
    body, err := ioutil.ReadAll(resp.Body) 
    if err != nil { 
     log.Fatalln(err) 
     return 
    } 

    file, err := os.OpenFile(out, os.O_WRONLY, 0600) 
    if err != nil { 
     if file, err = os.Create(out); err != nil { 
      log.Fatalln(err) 
      return 
     } 
    } 
    defer file.Close() 

    if _, err := file.WriteAt(body, int64(start)); err != nil { 
     log.Fatalln(err) 
     return 
    } 

    fmt.Println(fmt.Sprintf("Range %d-%d: %d", start, stop, resp.ContentLength)) 
} 

func main() { 
    flag.Parse() 
    headers, err := get_headers(file_url) 
    if err != nil { 
     fmt.Println(err) 
    } else { 
     length, _ := strconv.Atoi(headers["Content-Length"]) 
     bytes_chunk := length/workers 
     fmt.Println("file length: ", length) 
     for i := 0; i < workers; i++ { 
      start := i * bytes_chunk 
      stop := start + (bytes_chunk - 1) 
      go download_chunk(file_url, filename, start, stop) 
     } 
     var input string 
     fmt.Scanln(&input) 
    } 
} 

基本上,它只是讀取文件的長度,使用HTTP的範圍頭部的工人則每個文件下載的次數除以它,下載它試圖如該塊被寫入文件中的位置之後。

回答

3

如果您真的忽略了上面所見的許多錯誤,那麼您的代碼不應該可靠地用於任何文件類型。

但是,我想我可以看到您的代碼中的問題。我認爲混合O_APPEND和seek可能是一個錯誤(尋找這個模式應該被忽略)。我建議使用(*os.File).WriteAt來代替。

IIRC,O_APPEND強制任何寫入發生在文件的[當前]結尾。但是,文件部分的download_chunk函數實例可能以不可預知的順序執行,從而「重新排序」文件部分。結果是一個損壞的文件。

+0

哇靠!有效!乾杯! – Marconi

1

1.例行程序的順序不確定 例如:在執行可能的結果如下:

...

文件長度:20902

範圍10451-20901:10451

範圍0-10450:10451

...

所以塊不能只追加。

2,當寫入塊DATAS必須有一個sys.Mutex

(我的英語很差,請忘記它)

+0

但我正在使用seek,因此,哪個工人先完成並不重要。 – Marconi

+0

@ Marconi,我在本地(win7 + wamp)和遠程(github.com上的zip文件)測試了它。 本地測試,zip下載是正確的,但是遠程測試是錯誤的。遠程測試,resp.ContentLength總是等於頭[[Content-Length]],代碼「req.Header.Add(」Range「,fmt.Sprintf(」bytes =%d-%d「,start,停止))「沒有效果。但是本地測試一切正常 – justin

+0

是的,它依賴於'Content-Length'頭部和服務器支持'Range'頭部的文件。沒有這些,將無法工作。 – Marconi