我创建了一个函数,用于读取 HTTP GET 响应正文的一部分并创建一个迭代器。
func chunkDownload(ctx context.Context, url string, chunkSizeInBytes uint64) (iter.Seq2[io.Reader, error], error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("creating http request: %w", err)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("making http GET request: %w", err)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unsuccesful status code: %s", res.Status)
}
return func(yield func(io.Reader, error) bool) {
buf := make([]byte, chunkSizeInBytes)
defer res.Body.Close()
for {
select {
case <-ctx.Done():
yield(nil, ctx.Err())
return
default:
n, err := res.Body.Read(buf)
if err != nil && err != io.EOF {
yield(nil, fmt.Errorf("unable to read: %w", err))
return
}
fmt.Printf("Read %d bytes\n", n)
if n == 0 {
return
}
if !yield(bytes.NewReader(buf[:n]), nil) {
return
}
}
}
}, nil
}
我观察到,
res.body.Read(buf)
仅读取少量字节。日志示例:
78 downloaded chunk
Read 24576 bytes
79 downloaded chunk
Read 7168 bytes
80 downloaded chunk
Read 17408 bytes
81 downloaded chunk
Read 26357 bytes
82 downloaded chunk
Read 59392 bytes
83 downloaded chunk
Read 32768 bytes
84 downloaded chunk
Read 27744 bytes
85 downloaded chunk
为什么会这样?幕后到底发生了什么?有什么方法可以让我的用例正常工作(我知道
Range
请求,我想为不支持它的服务器实现此功能)。
我观察到 res.body.Read(buf) 仅读取少量字节。 ...为什么会这样?
io.Reader文档解释了原因:
Read 将最多 len(p) 个字节读取到 p 中。 ...如果某些数据可用,但不是 len(p) 个字节,则读取通常会返回可用数据,而不是等待更多数据。
对
res.Body.Read(buf)
的调用会返回可用的内容,而不是等待 len(buf)
字节的数据从服务器到达。
使用 io.ReadFull 等待
len(buf)
字节的数据。
n, err := io.ReadFull(res.Body, buf)
这不是您的问题,但问题中的代码无法正确处理
res.Body.Read(buf)
的返回值。 具体来说,该代码不处理返回数据和错误的情况。 这是改进代码的尝试。
n, err := res.Body.Read(buf)
if n > 0 {
// Handle any returned data before handling errors.
if !yield(bytes.NewReader(buf[:n]), nil) {
return
}
}
if err == io.EOF {
// Reached end of stream. Done!
return
}
if err != nil {
// Something bad happened. Yield the error and done!
yield(nil, fmt.Errorf("unable to read: %w", err))
return
}
更好的解决方案是
io.Copy(part, rest.Body)
,其中 part
是应用程序写入数据的位置。