所以我正在从 URL 下载文件并将其拆分为带有
content-range
标头的块,并希望将下载文件流式传输到进度条中。目前,我正在努力将下载的字节写入块中。我的做法如下:
type Chunk struct {
response _http.Response
wg *sync.WaitGroup
index int
start int64
end int64
size int64
data []byte
ctx context.Context
}
func New(ctx context.Context, res _http.Response, index int, wg *sync.WaitGroup) *Chunk {
totalpart := int64(res.Totalpart)
partsize := res.Size / totalpart
start := int64(index * int(partsize))
end := start + int64(int(partsize)-1)
if index == int(totalpart)-1 {
end = res.Size
}
return &Chunk{
response: res,
wg: wg,
index: index,
start: start,
end: end,
size: partsize,
data: make([]byte, partsize),
ctx: ctx,
}
}
func (c *Chunk) download() error {
defer c.wg.Done()
http_ := &http.Client{}
part := fmt.Sprintf("bytes=%d-%d", c.start, c.end)
if c.size == -1 {
log.Printf("Downloading chunk %d with size unknown", c.index+1)
} else {
log.Printf("Downloading chunk %d from %d to %d (~%d MB)", c.index+1, c.start, c.end, (len(c.data))/(1024*1024))
}
req, err := http.NewRequest("GET", c.response.Url, nil)
if err != nil {
return err
}
start := time.Now()
req.Header.Add("Range", part)
res, err := http_.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var _100KB int64 = 1024 * 100
buffer := make([]byte, _100KB)
for {
n, err := io.ReadFull(res.Body, buffer)
if err == io.EOF {
break
}
//TODO: combine the downloaded bytes into c.data
c.data = append(c.data, buffer[:n]...)
runtime.EventsEmit(c.ctx, "transfered", c.index, n)
}
// c.data, err = io.ReadAll(res.Body)
// if err != nil {
// return err
// }
// runtime.EventsEmit(c.ctx, "transfered", len(c.data))
elapsed := time.Since(start)
log.Printf("Chunk %d downloaded in %v s\n", c.index+1, elapsed.Seconds())
return nil
}
而
_http.Response
是我的自定义结构,用于在下载之前获取 URL 的数据(例如文件总大小、cansplit?、总部分和内容类型)。
但是上面的代码有一个问题,就是
len()
(块数据)的c.data
变得比实际块大小大。例如,实际视频文件的块大小是 12 MB,但是使用上面的代码,块变成了两倍大,当然当所有块合并到一个视频文件中时,而不是占用 48 MB(例如) ,变成100MB,下载的视频无法播放
在我之前的尝试中,我使用了
io.ReadAll(res.Body)
(我评论的代码)然后将其写入c.data
。下载完成后可以播放视频文件,但是上面的方法不行。缺点是,我无法流式下载,因为我需要完整下载它
如果我要在下载块之前和之后记录文件的
len()
,那么下载过程之后的len()
将变得两倍大。
我认为带有append的扩展运算符足以从头到尾填充
c.data
的每个元素。而且我认为,因为它使用的是append
,所以数据是否超过数组启动时的len()
并不重要。有人知道这个问题吗?任何帮助表示赞赏。提前谢谢你
确保服务器响应部分内容响应。
这里是我假设在每个响应上运行的循环的一些修复:将报告的字节数附加到缓冲区。将读取的任何数据复制到 c.data 后中断。中断任何错误。
for {
n, err := io.ReadFull(res.Body, buffer)
c.data = append(c.data, buffer[:n]...)
if err != nil {
break
}
runtime.EventsEmit(c.ctx, "transfered", c.index, n)
}
无需使用 content-range 报告进度。请求整个资源并报告读取的字节,如上面的循环所示。
我找到答案了
事实证明,
append
实际上是附加到partsize
索引之后的下一个元素。我尝试在下载过程后重新分配c.data
var _100KB int64 = 1024 * 100
buffer := make([]byte, _100KB)
transfered := 0
for {
n, err := io.ReadFull(res.Body, buffer)
if err == io.EOF {
break
}
transfered += n
//TODO: combine the downloaded bytes into c.data
c.data = append(c.data, buffer[:n]...)
runtime.EventsEmit(c.ctx, "transfered", c.index, n)
}
c.data = c.data[transfered:]
现在可以用了。下载的视频文件可以播放
但是,经过进一步研究,我可以只用
cap
制作切片,所以我需要用以下代码启动切片以使其更简单,并且我不需要在下载过程后对数组进行切片完成
func New(ctx context.Context, res _http.Response, index int, wg *sync.WaitGroup) *Chunk {
//...
return &Chunk{
//...
data: make([]byte, 0, partsize),
//...
}
}