Last active
October 14, 2020 10:12
-
-
Save bachue/599a849d381448df2fd80282ce1db2b0 to your computer and use it in GitHub Desktop.
Qiniu MultiRange Download Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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