mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 08:30:29 +00:00
101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
package hls
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"time"
|
|
|
|
"github.com/GopeedLab/gopeed/internal/httpclient"
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
)
|
|
|
|
const defaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
|
|
|
const maxPlaylistSize = 10 << 20 // 10MB, playlists are small text files
|
|
|
|
func (f *Fetcher) timeout() time.Duration {
|
|
return time.Duration(f.config.TimeoutSeconds) * time.Second
|
|
}
|
|
|
|
// buildClient builds an HTTP client honoring the task proxy and TLS settings.
|
|
// The client has no total timeout on purpose: large segments over slow links
|
|
// must not be aborted; body stalls are handled by stallReader instead. It is
|
|
// only called while the fetcher is quiescent (Resolve/Start), never from
|
|
// inside running worker goroutines.
|
|
func (f *Fetcher) buildClient() *http.Client {
|
|
var proxy *base.RequestProxy
|
|
skipVerify := false
|
|
if req := f.meta.Req; req != nil {
|
|
proxy = req.Proxy
|
|
skipVerify = req.SkipVerifyCert
|
|
}
|
|
jar, _ := cookiejar.New(nil)
|
|
client, err := httpclient.NewClient(httpclient.Options{
|
|
Client: httpclient.ClientOptions{
|
|
Jar: jar,
|
|
},
|
|
Transport: httpclient.TransportOptions{
|
|
DialContext: (&net.Dialer{
|
|
Timeout: f.timeout(),
|
|
}).DialContext,
|
|
Proxy: f.Ctl.GetProxy(proxy),
|
|
TLSClientConfig: &tls.Config{
|
|
InsecureSkipVerify: skipVerify,
|
|
},
|
|
TLSHandshakeTimeout: f.timeout(),
|
|
},
|
|
Impersonation: httpclient.ImpersonationOptions{
|
|
Mode: httpclient.ImpersonationAuto,
|
|
Session: f.impSession,
|
|
},
|
|
})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("build HTTP client: %v", err))
|
|
}
|
|
return client
|
|
}
|
|
|
|
// buildRequest builds a request with task extra headers, the default user
|
|
// agent and an optional byte range. extra may be nil.
|
|
func buildRequest(ctx context.Context, extra *ReqExtra, method, u string, body io.Reader, byterange *ByteRange) (*http.Request, error) {
|
|
req, err := http.NewRequestWithContext(ctx, method, u, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if extra != nil {
|
|
for key, value := range extra.Header {
|
|
req.Header.Set(key, value)
|
|
}
|
|
}
|
|
if req.Header.Get("User-Agent") == "" {
|
|
req.Header.Set("User-Agent", defaultUserAgent)
|
|
}
|
|
if byterange != nil {
|
|
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", byterange.Offset, byterange.Offset+byterange.Length-1))
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
// stallReader aborts reads that see no data for the configured timeout.
|
|
type stallReader struct {
|
|
rc io.ReadCloser
|
|
timeout time.Duration
|
|
}
|
|
|
|
func (r *stallReader) Read(p []byte) (int, error) {
|
|
timer := time.AfterFunc(r.timeout, func() {
|
|
r.rc.Close()
|
|
})
|
|
defer timer.Stop()
|
|
return r.rc.Read(p)
|
|
}
|
|
|
|
func (r *stallReader) Close() error {
|
|
return r.rc.Close()
|
|
}
|