|
package stream |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
|
|
"github.com/alist-org/alist/v3/internal/errs" |
|
"github.com/alist-org/alist/v3/internal/model" |
|
"github.com/alist-org/alist/v3/internal/net" |
|
"github.com/alist-org/alist/v3/pkg/http_range" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
func GetRangeReadCloserFromLink(size int64, link *model.Link) (model.RangeReadCloserIF, error) { |
|
if len(link.URL) == 0 { |
|
return nil, fmt.Errorf("can't create RangeReadCloser since URL is empty in link") |
|
} |
|
|
|
rangeReaderFunc := func(ctx context.Context, r http_range.Range) (io.ReadCloser, error) { |
|
if link.Concurrency != 0 || link.PartSize != 0 { |
|
header := net.ProcessHeader(http.Header{}, link.Header) |
|
down := net.NewDownloader(func(d *net.Downloader) { |
|
d.Concurrency = link.Concurrency |
|
d.PartSize = link.PartSize |
|
}) |
|
req := &net.HttpRequestParams{ |
|
URL: link.URL, |
|
Range: r, |
|
Size: size, |
|
HeaderRef: header, |
|
} |
|
rc, err := down.Download(ctx, req) |
|
if err != nil { |
|
return nil, errs.NewErr(err, "GetReadCloserFromLink failed") |
|
} |
|
return rc, nil |
|
|
|
} |
|
if len(link.URL) > 0 { |
|
response, err := RequestRangedHttp(ctx, link, r.Start, r.Length) |
|
if err != nil { |
|
if response == nil { |
|
return nil, fmt.Errorf("http request failure, err:%s", err) |
|
} |
|
return nil, fmt.Errorf("http request failure,status: %d err:%s", response.StatusCode, err) |
|
} |
|
if r.Start == 0 && (r.Length == -1 || r.Length == size) || response.StatusCode == http.StatusPartialContent || |
|
checkContentRange(&response.Header, r.Start) { |
|
return response.Body, nil |
|
} else if response.StatusCode == http.StatusOK { |
|
log.Warnf("remote http server not supporting range request, expect low perfromace!") |
|
readCloser, err := net.GetRangedHttpReader(response.Body, r.Start, r.Length) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return readCloser, nil |
|
|
|
} |
|
|
|
return response.Body, nil |
|
} |
|
|
|
return nil, errs.NotSupport |
|
} |
|
resultRangeReadCloser := model.RangeReadCloser{RangeReader: rangeReaderFunc} |
|
return &resultRangeReadCloser, nil |
|
} |
|
|
|
func RequestRangedHttp(ctx context.Context, link *model.Link, offset, length int64) (*http.Response, error) { |
|
header := net.ProcessHeader(http.Header{}, link.Header) |
|
header = http_range.ApplyRangeToHttpHeader(http_range.Range{Start: offset, Length: length}, header) |
|
|
|
return net.RequestHttp(ctx, "GET", header, link.URL) |
|
} |
|
|
|
|
|
func checkContentRange(header *http.Header, offset int64) bool { |
|
start, _, err := http_range.ParseContentRange(header.Get("Content-Range")) |
|
if err != nil { |
|
log.Warnf("exception trying to parse Content-Range, will ignore,err=%s", err) |
|
} |
|
if start == offset { |
|
return true |
|
} |
|
return false |
|
} |
|
|