256 lines
8.2 KiB
Go
256 lines
8.2 KiB
Go
/*
|
|
* Copyright 2020 The Dragonfly Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
//go:generate mockgen -destination ./mock/mock_source_client.go -package mock d7y.io/dragonfly/v2/pkg/source ResourceClient
|
|
|
|
package source
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
rangers "d7y.io/dragonfly/v2/pkg/util/rangeutils"
|
|
"github.com/go-http-utils/headers"
|
|
|
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
|
)
|
|
|
|
var _ ResourceClient = (*ClientManagerImpl)(nil)
|
|
var _ ClientManager = (*ClientManagerImpl)(nil)
|
|
|
|
// ResourceClient supply apis that interact with the source.
|
|
type ResourceClient interface {
|
|
|
|
// GetContentLength get length of resource content
|
|
// return -l if request fail
|
|
// return task.IllegalSourceFileLen if response status is not StatusOK and StatusPartialContent
|
|
GetContentLength(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (int64, error)
|
|
|
|
// IsSupportRange checks if resource supports breakpoint continuation
|
|
IsSupportRange(ctx context.Context, url string, header RequestHeader) (bool, error)
|
|
|
|
// IsExpired checks if a resource received or stored is the same.
|
|
IsExpired(ctx context.Context, url string, header RequestHeader, expireInfo map[string]string) (bool, error)
|
|
|
|
// Download download from source
|
|
Download(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (io.ReadCloser, error)
|
|
|
|
// DownloadWithResponseHeader download from source with responseHeader
|
|
DownloadWithResponseHeader(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (io.ReadCloser, ResponseHeader, error)
|
|
|
|
// GetLastModified get lastModified timestamp milliseconds of resource
|
|
GetLastModifiedMillis(ctx context.Context, url string, header RequestHeader) (int64, error)
|
|
}
|
|
|
|
type ClientManager interface {
|
|
ResourceClient
|
|
Register(schema string, resourceClient ResourceClient)
|
|
UnRegister(schema string)
|
|
}
|
|
|
|
type ClientManagerImpl struct {
|
|
sync.RWMutex
|
|
clients map[string]ResourceClient
|
|
}
|
|
|
|
var _defaultMgr = &ClientManagerImpl{
|
|
clients: make(map[string]ResourceClient),
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) GetContentLength(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (int64, error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
}
|
|
return sourceClient.GetContentLength(ctx, url, header, rang)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) IsSupportRange(ctx context.Context, url string, header RequestHeader) (bool, error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
}
|
|
return sourceClient.IsSupportRange(ctx, url, header)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) IsExpired(ctx context.Context, url string, header RequestHeader, expireInfo map[string]string) (bool, error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
}
|
|
return sourceClient.IsExpired(ctx, url, header, expireInfo)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) Download(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (io.ReadCloser, error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sourceClient.Download(ctx, url, header, rang)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) DownloadWithResponseHeader(ctx context.Context, url string, header RequestHeader, rang *rangers.Range) (io.ReadCloser, ResponseHeader,
|
|
error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return sourceClient.DownloadWithResponseHeader(ctx, url, header, rang)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) GetLastModifiedMillis(ctx context.Context, url string, header RequestHeader) (int64, error) {
|
|
sourceClient, err := clientMgr.getSourceClient(url)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
if _, ok := ctx.Deadline(); !ok {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
}
|
|
return sourceClient.GetLastModifiedMillis(ctx, url, header)
|
|
}
|
|
|
|
func NewManager() ClientManager {
|
|
return &ClientManagerImpl{
|
|
clients: make(map[string]ResourceClient),
|
|
}
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) Register(schema string, resourceClient ResourceClient) {
|
|
if client, ok := clientMgr.clients[strings.ToLower(schema)]; ok {
|
|
logger.Infof("replace client %#v with %#v for schema %s", client, resourceClient, schema)
|
|
}
|
|
clientMgr.clients[strings.ToLower(schema)] = resourceClient
|
|
}
|
|
|
|
func Register(schema string, resourceClient ResourceClient) {
|
|
_defaultMgr.Register(schema, resourceClient)
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) UnRegister(schema string) {
|
|
if client, ok := clientMgr.clients[strings.ToLower(schema)]; ok {
|
|
logger.Infof("remove client %#v for schema %s", client, schema)
|
|
}
|
|
delete(clientMgr.clients, strings.ToLower(schema))
|
|
}
|
|
|
|
func UnRegister(schema string) {
|
|
_defaultMgr.UnRegister(schema)
|
|
}
|
|
|
|
func GetContentLength(ctx context.Context, url string, header RequestHeader) (int64, error) {
|
|
var httpRange *rangers.Range
|
|
if header.Get(headers.Range) != "" {
|
|
var err error
|
|
httpRange, err = rangers.ParseHTTPRange(header.Get(headers.Range))
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
}
|
|
return _defaultMgr.GetContentLength(ctx, url, header, httpRange)
|
|
}
|
|
|
|
func IsSupportRange(ctx context.Context, url string, header RequestHeader) (bool, error) {
|
|
return _defaultMgr.IsSupportRange(ctx, url, header)
|
|
}
|
|
|
|
func IsExpired(ctx context.Context, url string, header RequestHeader, expireInfo map[string]string) (bool, error) {
|
|
return _defaultMgr.IsExpired(ctx, url, header, expireInfo)
|
|
}
|
|
|
|
func Download(ctx context.Context, url string, header RequestHeader) (io.ReadCloser, error) {
|
|
var httpRange *rangers.Range
|
|
if header.Get(headers.Range) != "" {
|
|
var err error
|
|
httpRange, err = rangers.ParseHTTPRange(header.Get(headers.Range))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return _defaultMgr.Download(ctx, url, header, httpRange)
|
|
}
|
|
|
|
func DownloadWithResponseHeader(ctx context.Context, url string, header RequestHeader) (io.ReadCloser, ResponseHeader, error) {
|
|
|
|
var httpRange *rangers.Range
|
|
if header.Get(headers.Range) != "" {
|
|
var err error
|
|
httpRange, err = rangers.ParseHTTPRange(header.Get(headers.Range))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
return _defaultMgr.DownloadWithResponseHeader(ctx, url, header, httpRange)
|
|
}
|
|
|
|
func GetLastModifiedMillis(ctx context.Context, url string, header RequestHeader) (int64, error) {
|
|
return _defaultMgr.GetLastModifiedMillis(ctx, url, header)
|
|
}
|
|
|
|
// getSourceClient get a source client from source manager with specified schema.
|
|
func (clientMgr *ClientManagerImpl) getSourceClient(rawURL string) (ResourceClient, error) {
|
|
logger.Debugf("current clients: %v", clientMgr.clients)
|
|
parsedURL, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientMgr.RLock()
|
|
client, ok := clientMgr.clients[strings.ToLower(parsedURL.Scheme)]
|
|
clientMgr.RUnlock()
|
|
if !ok || client == nil {
|
|
return nil, errors.Errorf("can not find client for supporting url %s, clients:%v", rawURL, clientMgr.clients)
|
|
}
|
|
return client, nil
|
|
}
|
|
|
|
func (clientMgr *ClientManagerImpl) loadSourcePlugin(schema string) (ResourceClient, error) {
|
|
clientMgr.Lock()
|
|
defer clientMgr.Unlock()
|
|
// double check
|
|
client, ok := clientMgr.clients[schema]
|
|
if ok {
|
|
return client, nil
|
|
}
|
|
|
|
client, err := LoadPlugin(schema)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
clientMgr.clients[schema] = client
|
|
return client, nil
|
|
}
|