转到文件下载器

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 (
    "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)
    }
}

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.

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.

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)