Skip to content

Instantly share code, notes, and snippets.

@bachue
Last active October 14, 2020 10:12
Show Gist options
  • Save bachue/599a849d381448df2fd80282ce1db2b0 to your computer and use it in GitHub Desktop.
Save bachue/599a849d381448df2fd80282ce1db2b0 to your computer and use it in GitHub Desktop.
Qiniu MultiRange Download Example
package main
import (
"bytes"
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"os"
"strings"
)
// 注意:保证 Ranges 至少有两对,且 Ranges 最终不会覆盖整个文件
func MultiRangeGet(client *http.Client, url string, ranges [][2]int) ([]byte, error) {
rangeHeaderValue := fmt.Sprintf("bytes=%s", generateRangeHeader(ranges))
buffer := bytes.NewBuffer(make([]byte, 0, sumRangeSize(ranges)))
req, err := http.NewRequest("GET", url, http.NoBody)
if err != nil {
return nil, err
}
req.Header.Add("Range", rangeHeaderValue)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
return nil, errors.New("Status code is 200, we don't support it here")
case http.StatusPartialContent:
contentType := resp.Header.Get("Content-Type")
_, params, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
multipartReader := multipart.NewReader(resp.Body, params["boundary"])
for {
part, err := multipartReader.NextPart()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
_, partSize, err := parseContentRange(part.Header.Get("Content-Range"))
if err != nil {
return nil, err
}
copied, err := io.Copy(buffer, part)
if err != nil {
return nil, err
}
if copied != int64(partSize) {
return nil, io.ErrUnexpectedEOF
}
}
default:
return nil, errors.New("Status code is neither 200 nor 206")
}
return buffer.Bytes(), nil
}
func generateRangeHeader(ranges [][2]int) string {
parts := make([]string, 0, len(ranges))
for _, rg := range ranges {
parts = append(parts, fmt.Sprintf("%d-%d", rg[0], rg[0]+rg[1]-1))
}
return strings.Join(parts, ",")
}
func sumRangeSize(ranges [][2]int) int {
sum := 0
for _, rg := range ranges {
sum += rg[1]
}
return sum
}
func parseContentRange(contentRange string) (int, int, error) {
var from, to, total int
parsed, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &from, &to, &total)
if err != nil || parsed != 3 {
return 0, 0, fmt.Errorf("Content-Range is invalid: %s", err)
}
return from, to - from + 1, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment