Go文件下载器

huangapple go评论123阅读模式
英文:

Go file downloader

问题

我有以下代码,它应该将文件分割成多个部分进行下载。但是现在它只能用于下载图像,当我尝试下载其他文件(如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")
    flag.StringVar(&filename, "filename", "", "下载的文件名")
    flag.IntVar(&workers, "workers", 2, "下载工作线程数")
}

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("文件长度:", 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的Range头部字段下载每个文件块,下载完成后将其写入文件的相应位置。

英文:

I have the following code which is suppose to download file by splitting it into multiple parts. But right now it only works on images, when I try downloading other files like tar files the output is an invalid file.

UPDATED:

Used os.WriteAt instead of os.Write and removed os.O_APPEND file mode.

package main
import (
&quot;errors&quot;
&quot;flag&quot;
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;log&quot;
&quot;net/http&quot;
&quot;os&quot;
&quot;strconv&quot;
)
var file_url string
var workers int
var filename string
func init() {
flag.StringVar(&amp;file_url, &quot;url&quot;, &quot;&quot;, &quot;URL of the file to download&quot;)
flag.StringVar(&amp;filename, &quot;filename&quot;, &quot;&quot;, &quot;Name of downloaded file&quot;)
flag.IntVar(&amp;workers, &quot;workers&quot;, 2, &quot;Number of download workers&quot;)
}
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(&quot;GET&quot;, url, nil)
req.Header.Add(&quot;Range&quot;, fmt.Sprintf(&quot;bytes=%d-%d&quot;, 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(&quot;Range %d-%d: %d&quot;, 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[&quot;Content-Length&quot;])
bytes_chunk := length / workers
fmt.Println(&quot;file length: &quot;, length)
for i := 0; i &lt; workers; i++ {
start := i * bytes_chunk
stop := start + (bytes_chunk - 1)
go download_chunk(file_url, filename, start, stop)
}
var input string
fmt.Scanln(&amp;input)
}
}

Basically, it just reads the length of the file, divides it with the number of workers then each file downloads using HTTP's Range header, after downloading it seeks to a position in the file where that chunk is written.

答案1

得分: 3

如果你真的忽略了上面所见的许多错误,那么你的代码就不应该可靠地适用于任何文件类型。

然而,我猜我可以在你的代码中看到一个问题。我认为在使用O_APPEND和seek混合的情况下可能是一个错误(在这种模式下应该忽略seek)。我建议改用(*os.File).WriteAt

据我所知,O_APPEND会强制任何写操作发生在文件的[当前]末尾。然而,你的download_chunk函数实例针对文件部分可能以不可预测的顺序执行,从而“重新排序”文件部分。结果就是一个损坏的文件。

英文:

If you really ignore many errors like seen above then your code is not supposed to work reliably for any file type.

However, I guess I can see on problem in your code. I think that mixing O_APPEND and seek is probably a mistake (Seek should be ignored with this mode). I suggest to use (*os.File).WriteAt instead.

IIRC, O_APPEND forces any write to happen at the [current] end of file. However, your download_chunk function instances for file parts can be executing in unpredictable order, thus "reordering" the file parts. The result is then a corrupted file.

答案2

得分: 1

  1. Go例程的顺序是不确定的。例如,执行结果可能如下所示:

...

文件长度:20902

范围 10451-20901:10451

范围 0-10450:10451

...

因此,块不能只是追加。

  1. 在写入块数据时必须使用 sys.Mutex(互斥锁)。

(我的英语很差,请忽略它)

英文:

1.the sequence of the go routine is not sure。
eg. the execute result maybe as follows:

...

file length:20902

Range 10451-20901:10451

Range 0-10450:10451

...

so the chunks can't just append.

2.when write chunk datas must have a sys.Mutex

(my english is poor,please forget it)

huangapple
  • 本文由 发表于 2013年8月23日 14:20:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/18396079.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定