【问题标题】:Downloading content with range request corrupts使用范围请求下载内容损坏
【发布时间】:2021-03-24 12:47:42
【问题描述】:

我在 Github 上建立了一个基础项目:https://github.com/kounelios13/range-download。 本质上,这个项目尝试使用 HTTP Range 请求下载文件,组装它,然后将其保存回磁盘。我正在尝试关注this article(暂时除了goroutines)。当我尝试使用范围请求下载文件时,在合并所有请求数据后,最终大小大于我将获得的原始大小并且最终文件已损坏。

这里是负责下载文件的代码

type Manager struct{
    limit int
}

func NewManager(limit int) *Manager{
    return &Manager{
        limit: limit,
    }
}

func (m *Manager) DownloadBody(url string ) ([]byte ,error){
    // First we need to determine the filesize
    body := make([]byte ,0)
    response , err := http.Head(url) // We perform a Head request to get header information

    if response.StatusCode != http.StatusOK{
        return nil ,fmt.Errorf("received code %d",response.StatusCode)
    }
    if err != nil{
        return nil , err
    }

    maxConnections := m.limit // Number of maximum concurrent co routines
    bodySize , _ := strconv.Atoi(response.Header.Get("Content-Length"))
    bufferSize :=(bodySize) / (maxConnections)
    diff := bodySize % maxConnections
    read := 0
    for i:=0;i<maxConnections;i++{
        min := bufferSize * i
        max := bufferSize * (i+1)
        if i==maxConnections-1{
            max+=diff // Check to see if we have any leftover data to retrieve for the last request
        }
        req , _ := http.NewRequest("GET" , url, nil)
        req.Header.Add("Range" ,fmt.Sprintf("bytes=%d-%d",min,max))
        res , e := http.DefaultClient.Do(req)
        if e != nil{
            return body , e
        }
        log.Printf("Index:%d . Range:bytes=%d-%d",i,min,max)
        data , e :=ioutil.ReadAll(res.Body)
        res.Body.Close()
        if e != nil{
            return body,e
        }
        log.Println("Data for  request: ",len(data))
        read = read + len(data)
        body = append(body, data...)
    }
    log.Println("File size:",bodySize , "Downloaded size:",len(body)," Actual read:",read)
    return body, nil
}

我还注意到,我设置的限制越大,原始文件内容长度与所有请求正文组合的实际大小之间的差异就越大。

这是我的main.go

func main() {
    imgUrl := "https://media.wired.com/photos/5a593a7ff11e325008172bc2/16:9/w_2400,h_1350,c_limit/pulsar-831502910.jpg"
    maxConnections := 4
    manager := lib.NewManager(maxConnections)
    data , e:= manager.DownloadBody(imgUrl)
    if  e!= nil{
        log.Fatalln(e)
    }
    ioutil.WriteFile("foo.jpg" , data,0777)
}

注意:目前我对让代码并发不感兴趣。

有什么我可能会遗漏的想法吗?

注意:我已经确认服务器使用下面的 curl 命令返回了 206 部分内容:

curl -I https://media.wired.com/photos/5a593a7ff11e325008172bc2/16:9/w_2400,h_1350,c_limit/pulsar-831502910.jpg

【问题讨论】:

  • 检查服务器是否实际返回带有Content-Range 标头的 206 响应,而不是普通的 200。服务器不需要接受范围请求。
  • Server Indeed 返回 206
  • 在你的情况下,与其给你解决方案,不如邀请你写一个测试。请参阅这篇文章以了解如何在 go stackoverflow.com/questions/36540610/… 中提供范围请求,另请参阅 golang.org/pkg/net/http/httptest 如果您难以编写测试,它可能看起来像这样 play.golang.org/p/SoMew01qsFo
  • @mh-cbon 感谢您提供这些链接。明天有空的时候我会试着多读一些

标签: http go range-requests


【解决方案1】:

感谢@mh-cbon,我设法编写了一个简单的测试来帮助我找到解决方案。这是固定代码

for i:=0;i<maxConnections;i++{
        min := bufferSize * i
        if i != 0{
            min++
        }
        max := bufferSize * (i+1)
        if i==maxConnections-1{
            max+=diff // Check to see if we have any leftover data to retrieve for the last request
        }
        req , _ := http.NewRequest("GET" , url, nil)
        req.Header.Add("Range" ,fmt.Sprintf("bytes=%d-%d",min,max))
        res , e := http.DefaultClient.Do(req)
        if e != nil{
            return body , e
        }
        log.Printf("Index:%d . Range:bytes=%d-%d",i,min,max)
        data , e :=ioutil.ReadAll(res.Body)
        res.Body.Close()
        if e != nil{
            return body,e
        }
        log.Println("Data for  request: ",len(data))
        read = read + len(data)
        body = append(body, data...)
    }

问题是我没有以 . 开头的正确最小值。所以可以说我有以下范围可供下载:

  • 0-100
  • 101 - 200

我的代码将从0-100 下载字节,然后再次从100-200 而不是101-200 下载字节

所以我确保每次迭代(第一次除外)都将 min 递增 1,以免与之前的范围重叠

这是我设法从作为 cmets 提供的文档中修复的一个简单测试:

func TestManager_DownloadBody(t *testing.T) {
    ts := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {

        http.ServeContent(writer,request ,"hey" ,time.Now() ,bytes.NewReader([]byte(`hello world!!!!`)))
    }))

    defer ts.Close()


    m := NewManager(4)
    data , err := m.DownloadBody(ts.URL)
    if err != nil{
        t.Errorf("%s",err)
    }

    if string(data) != "hello world!!!!"{
        t.Errorf("Expected hello world!!!! . received : [%s]",data)
    }
}

当然还有更多的测试要写,但这是一个好的开始

【讨论】:

  • 可以改写if want:="hello world!!!!"; string(data) !=want { t.Errorf("Expected %s . received : [%s]",want, data)等最后一条语句
  • 是的,你是对的。我只是想快速编写一个测试来查找错误。我现在已经写了更多的测试,很快就会提交并将它们推送到我的 github 项目中
  • 当你准备好后,你可能想在 stackreview 上发布它更适合代码审查
  • 很久没用code review了。不过我可以试一试
  • 而不是 0-100、101-200、201-300 等,您可以使用 0-99、100-199、200-299 等,这不需要任何特殊情况: )
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-25
  • 2011-08-21
  • 2018-05-21
相关资源
最近更新 更多