fix: 200 cache

pull/250/head
zijiren233 4 months ago
parent 91a8779aa1
commit 5559570d3b

@ -600,7 +600,7 @@ func ProxyMovie(ctx *gin.Context) {
// TODO: cache mpd file
fallthrough
default:
err = proxy.AutoProxyURL(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Type, m.Movie.MovieBase.Headers, ctx.GetString("token"), room.ID, m.ID)
err = proxy.AutoProxyURL(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Type, m.Movie.MovieBase.Headers, true, ctx.GetString("token"), room.ID, m.ID)
if err != nil {
log.Errorf("proxy movie error: %v", err)
return
@ -654,7 +654,7 @@ func ServeM3u8(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("invalid token"))
return
}
err = proxy.ProxyM3u8(ctx, claims.TargetUrl, m.Movie.MovieBase.Headers, claims.IsM3u8File, ctx.GetString("token"), room.ID, m.ID)
err = proxy.ProxyM3u8(ctx, claims.TargetUrl, m.Movie.MovieBase.Headers, true, claims.IsM3u8File, ctx.GetString("token"), room.ID, m.ID)
if err != nil {
log.Errorf("proxy m3u8 error: %v", err)
}
@ -785,7 +785,7 @@ func JoinHlsLive(ctx *gin.Context) {
}
if utils.IsM3u8Url(m.Movie.MovieBase.Url) {
err = proxy.ProxyM3u8(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Headers, true, ctx.GetString("token"), room.ID, m.ID)
err = proxy.ProxyM3u8(ctx, m.Movie.MovieBase.Url, m.Movie.MovieBase.Headers, true, true, ctx.GetString("token"), room.ID, m.ID)
if err != nil {
log.Errorf("proxy m3u8 hls live error: %v", err)
}

@ -28,20 +28,20 @@ func ParseByteRange(r string) (*ByteRange, error) {
}
if !strings.HasPrefix(r, "bytes=") {
return nil, fmt.Errorf("invalid range prefix: %s", r)
return nil, fmt.Errorf("range header must start with 'bytes=', got: %s", r)
}
r = strings.TrimPrefix(r, "bytes=")
parts := strings.Split(r, "-")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid range format: %s", r)
return nil, fmt.Errorf("range header must contain exactly one hyphen (-) separator, got: %s", r)
}
parts[0] = strings.TrimSpace(parts[0])
parts[1] = strings.TrimSpace(parts[1])
if parts[0] == "" && parts[1] == "" {
return nil, fmt.Errorf("empty range values")
return nil, fmt.Errorf("range header cannot have empty start and end values: %s", r)
}
var start, end int64 = 0, -1
@ -50,23 +50,23 @@ func ParseByteRange(r string) (*ByteRange, error) {
if parts[0] != "" {
start, err = strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid range start: %s", parts[0])
return nil, fmt.Errorf("failed to parse range start value '%s': %v", parts[0], err)
}
if start < 0 {
return nil, fmt.Errorf("negative range start: %d", start)
return nil, fmt.Errorf("range start value must be non-negative, got: %d", start)
}
}
if parts[1] != "" {
end, err = strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid range end: %s", parts[1])
return nil, fmt.Errorf("failed to parse range end value '%s': %v", parts[1], err)
}
if end < 0 {
return nil, fmt.Errorf("negative range end: %d", end)
return nil, fmt.Errorf("range end value must be non-negative, got: %d", end)
}
if start > end {
return nil, fmt.Errorf("invalid range: start (%d) greater than end (%d)", start, end)
return nil, fmt.Errorf("range start value (%d) cannot be greater than end value (%d)", start, end)
}
}
@ -81,9 +81,6 @@ type CacheMetadata struct {
}
func (m *CacheMetadata) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, fmt.Errorf("nil metadata")
}
return json.Marshal(m)
}
@ -95,47 +92,43 @@ type CacheItem struct {
// WriteTo implements io.WriterTo to serialize the cache item
func (i *CacheItem) WriteTo(w io.Writer) (int64, error) {
if i == nil {
return 0, fmt.Errorf("nil cache item")
if w == nil {
return 0, fmt.Errorf("cannot write to nil io.Writer")
}
if i.Metadata == nil {
return 0, fmt.Errorf("nil metadata")
}
if w == nil {
return 0, fmt.Errorf("nil writer")
return 0, fmt.Errorf("CacheItem contains nil Metadata")
}
metadata, err := i.Metadata.MarshalBinary()
if err != nil {
return 0, fmt.Errorf("marshal metadata: %w", err)
return 0, fmt.Errorf("failed to marshal metadata: %w", err)
}
var written int64
// Write metadata length and metadata
if err := binary.Write(w, binary.BigEndian, int64(len(metadata))); err != nil {
return written, fmt.Errorf("write metadata length: %w", err)
return written, fmt.Errorf("failed to write metadata length: %w", err)
}
written += 8
n, err := w.Write(metadata)
written += int64(n)
if err != nil {
return written, fmt.Errorf("write metadata: %w", err)
return written, fmt.Errorf("failed to write metadata bytes: %w", err)
}
// Write data length and data
if err := binary.Write(w, binary.BigEndian, int64(len(i.Data))); err != nil {
return written, fmt.Errorf("write data length: %w", err)
return written, fmt.Errorf("failed to write data length: %w", err)
}
written += 8
n, err = w.Write(i.Data)
written += int64(n)
if err != nil {
return written, fmt.Errorf("write data: %w", err)
return written, fmt.Errorf("failed to write data bytes: %w", err)
}
return written, nil
@ -143,12 +136,8 @@ func (i *CacheItem) WriteTo(w io.Writer) (int64, error) {
// ReadFrom implements io.ReaderFrom to deserialize the cache item
func (i *CacheItem) ReadFrom(r io.Reader) (int64, error) {
if i == nil {
return 0, fmt.Errorf("nil cache item")
}
if r == nil {
return 0, fmt.Errorf("nil reader")
return 0, fmt.Errorf("cannot read from nil io.Reader")
}
var read int64
@ -156,42 +145,42 @@ func (i *CacheItem) ReadFrom(r io.Reader) (int64, error) {
// Read metadata length and metadata
var metadataLen int64
if err := binary.Read(r, binary.BigEndian, &metadataLen); err != nil {
return read, fmt.Errorf("read metadata length: %w", err)
return read, fmt.Errorf("failed to read metadata length: %w", err)
}
read += 8
if metadataLen <= 0 {
return read, fmt.Errorf("invalid metadata length: %d", metadataLen)
return read, fmt.Errorf("metadata length must be positive, got: %d", metadataLen)
}
metadata := make([]byte, metadataLen)
n, err := io.ReadFull(r, metadata)
read += int64(n)
if err != nil {
return read, fmt.Errorf("read metadata: %w", err)
return read, fmt.Errorf("failed to read metadata bytes: %w", err)
}
i.Metadata = new(CacheMetadata)
if err := json.Unmarshal(metadata, i.Metadata); err != nil {
return read, fmt.Errorf("unmarshal metadata: %w", err)
return read, fmt.Errorf("failed to unmarshal metadata: %w", err)
}
// Read data length and data
var dataLen int64
if err := binary.Read(r, binary.BigEndian, &dataLen); err != nil {
return read, fmt.Errorf("read data length: %w", err)
return read, fmt.Errorf("failed to read data length: %w", err)
}
read += 8
if dataLen < 0 {
return read, fmt.Errorf("invalid data length: %d", dataLen)
return read, fmt.Errorf("data length cannot be negative, got: %d", dataLen)
}
i.Data = make([]byte, dataLen)
n, err = io.ReadFull(r, i.Data)
read += int64(n)
if err != nil {
return read, fmt.Errorf("read data: %w", err)
return read, fmt.Errorf("failed to read data bytes: %w", err)
}
return read, nil
@ -219,7 +208,7 @@ func NewMemoryCache() *MemoryCache {
func (c *MemoryCache) Get(key string) (*CacheItem, bool, error) {
if key == "" {
return nil, false, fmt.Errorf("empty key")
return nil, false, fmt.Errorf("cache key cannot be empty")
}
c.mu.RLock()
@ -233,10 +222,10 @@ func (c *MemoryCache) Get(key string) (*CacheItem, bool, error) {
func (c *MemoryCache) Set(key string, data *CacheItem) error {
if key == "" {
return fmt.Errorf("empty key")
return fmt.Errorf("cache key cannot be empty")
}
if data == nil {
return fmt.Errorf("nil cache item")
return fmt.Errorf("cannot cache nil CacheItem")
}
c.mu.Lock()
@ -327,28 +316,27 @@ func (c *SliceCacheProxy) fmtContentLength(start, end, total int64) string {
func (c *SliceCacheProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
byteRange, err := ParseByteRange(r.Header.Get("Range"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
http.Error(w, fmt.Sprintf("Failed to parse Range header: %v", err), http.StatusBadRequest)
return
}
alignedOffset := c.alignedOffset(byteRange.Start)
cacheItem, err := c.getCacheItem(alignedOffset)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
http.Error(w, fmt.Sprintf("Failed to get cache item: %v", err), http.StatusInternalServerError)
return
}
c.setResponseHeaders(w, byteRange, cacheItem)
c.setResponseHeaders(w, byteRange, cacheItem, r.Header.Get("Range") != "")
if err := c.writeResponse(w, byteRange, alignedOffset, cacheItem); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
fmt.Printf("Failed to write response: %v\n", err)
fmt.Printf("Failed to write response: %v\n", err)
fmt.Printf("Failed to write response: %v\n", err)
return
}
}
func (c *SliceCacheProxy) setResponseHeaders(w http.ResponseWriter, byteRange *ByteRange, cacheItem *CacheItem) {
contentRange := c.fmtContentRange(byteRange.Start, byteRange.End, cacheItem.Metadata.ContentTotalLength)
w.Header().Set("Content-Type", cacheItem.Metadata.ContentType)
func (c *SliceCacheProxy) setResponseHeaders(w http.ResponseWriter, byteRange *ByteRange, cacheItem *CacheItem, hasRange bool) {
// Copy headers excluding special ones
for k, v := range cacheItem.Metadata.Headers {
switch k {
@ -359,21 +347,21 @@ func (c *SliceCacheProxy) setResponseHeaders(w http.ResponseWriter, byteRange *B
}
}
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Length", c.fmtContentLength(byteRange.Start, byteRange.End, cacheItem.Metadata.ContentTotalLength))
w.Header().Set("Content-Range", contentRange)
w.WriteHeader(http.StatusPartialContent)
w.Header().Set("Content-Type", cacheItem.Metadata.ContentType)
if hasRange {
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("Content-Range", c.fmtContentRange(byteRange.Start, byteRange.End, cacheItem.Metadata.ContentTotalLength))
w.WriteHeader(http.StatusPartialContent)
} else {
w.WriteHeader(http.StatusOK)
}
}
func (c *SliceCacheProxy) writeResponse(w http.ResponseWriter, byteRange *ByteRange, alignedOffset int64, cacheItem *CacheItem) error {
if w == nil || byteRange == nil || cacheItem == nil {
return fmt.Errorf("nil parameters")
}
sliceOffset := byteRange.Start - alignedOffset
if sliceOffset < 0 {
return fmt.Errorf("negative slice offset")
return fmt.Errorf("slice offset cannot be negative, got: %d", sliceOffset)
}
remainingLength := c.contentLength(byteRange.Start, byteRange.End, cacheItem.Metadata.ContentTotalLength)
@ -389,7 +377,7 @@ func (c *SliceCacheProxy) writeResponse(w http.ResponseWriter, byteRange *ByteRa
}
if n > 0 {
if _, err := w.Write(cacheItem.Data[sliceOffset : sliceOffset+n]); err != nil {
return fmt.Errorf("write initial slice: %w", err)
return fmt.Errorf("failed to write initial data slice: %w", err)
}
remainingLength -= n
}
@ -400,7 +388,7 @@ func (c *SliceCacheProxy) writeResponse(w http.ResponseWriter, byteRange *ByteRa
for remainingLength > 0 {
cacheItem, err := c.getCacheItem(currentOffset)
if err != nil {
return fmt.Errorf("get cache item: %w", err)
return fmt.Errorf("failed to get cache item at offset %d: %w", currentOffset, err)
}
n := int64(len(cacheItem.Data))
@ -409,7 +397,7 @@ func (c *SliceCacheProxy) writeResponse(w http.ResponseWriter, byteRange *ByteRa
}
if n > 0 {
if _, err := w.Write(cacheItem.Data[:n]); err != nil {
return fmt.Errorf("write slice: %w", err)
return fmt.Errorf("failed to write data slice at offset %d: %w", currentOffset, err)
}
remainingLength -= n
}
@ -421,7 +409,7 @@ func (c *SliceCacheProxy) writeResponse(w http.ResponseWriter, byteRange *ByteRa
func (c *SliceCacheProxy) getCacheItem(alignedOffset int64) (*CacheItem, error) {
if alignedOffset < 0 {
return nil, fmt.Errorf("negative offset")
return nil, fmt.Errorf("cache item offset cannot be negative, got: %d", alignedOffset)
}
cacheKey := c.cacheKey(alignedOffset)
@ -431,7 +419,7 @@ func (c *SliceCacheProxy) getCacheItem(alignedOffset int64) (*CacheItem, error)
// Try to get from cache first
slice, ok, err := c.cache.Get(cacheKey)
if err != nil {
return nil, fmt.Errorf("get from cache: %w", err)
return nil, fmt.Errorf("failed to get item from cache: %w", err)
}
if ok {
return slice, nil
@ -440,12 +428,12 @@ func (c *SliceCacheProxy) getCacheItem(alignedOffset int64) (*CacheItem, error)
// Fetch from source if not in cache
slice, err = c.fetchFromSource(alignedOffset)
if err != nil {
return nil, fmt.Errorf("fetch from source: %w", err)
return nil, fmt.Errorf("failed to fetch item from source: %w", err)
}
// Store in cache
if err = c.cache.Set(cacheKey, slice); err != nil {
return nil, fmt.Errorf("set cache: %w", err)
return nil, fmt.Errorf("failed to store item in cache: %w", err)
}
return slice, nil
@ -453,16 +441,16 @@ func (c *SliceCacheProxy) getCacheItem(alignedOffset int64) (*CacheItem, error)
func (c *SliceCacheProxy) fetchFromSource(offset int64) (*CacheItem, error) {
if offset < 0 {
return nil, fmt.Errorf("negative offset")
return nil, fmt.Errorf("source offset cannot be negative, got: %d", offset)
}
if _, err := c.r.Seek(offset, io.SeekStart); err != nil {
return nil, fmt.Errorf("seek source: %w", err)
return nil, fmt.Errorf("failed to seek to offset %d in source: %w", offset, err)
}
buf := make([]byte, c.sliceSize)
n, err := io.ReadFull(c.r, buf)
if err != nil && err != io.ErrUnexpectedEOF {
return nil, fmt.Errorf("read source: %w", err)
return nil, fmt.Errorf("failed to read %d bytes from source at offset %d: %w", c.sliceSize, offset, err)
}
var headers http.Header
@ -474,12 +462,12 @@ func (c *SliceCacheProxy) fetchFromSource(offset int64) (*CacheItem, error) {
contentTotalLength, err := c.r.ContentTotalLength()
if err != nil {
return nil, fmt.Errorf("get content total length: %w", err)
return nil, fmt.Errorf("failed to get content total length from source: %w", err)
}
contentType, err := c.r.ContentType()
if err != nil {
return nil, fmt.Errorf("get content type: %w", err)
return nil, fmt.Errorf("failed to get content type from source: %w", err)
}
return &CacheItem{

@ -55,9 +55,10 @@ func NewM3u8TargetToken(targetUrl, roomId, movieId string, isM3u8File bool) (str
const maxM3u8FileSize = 3 * 1024 * 1024 //
func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, isM3u8File bool, token, roomId, movieId string) error {
// only cache non-m3u8 files
func ProxyM3u8(ctx *gin.Context, u string, headers map[string]string, cache bool, isM3u8File bool, token, roomId, movieId string) error {
if !isM3u8File {
return ProxyURL(ctx, u, headers)
return ProxyURL(ctx, u, headers, cache)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)

@ -15,7 +15,7 @@ import (
"github.com/zijiren233/go-uhc"
)
func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error {
func ProxyURL(ctx *gin.Context, u string, headers map[string]string, cache bool) error {
if !settings.AllowProxyToLocal.Get() {
if l, err := utils.ParseURLIsLocalIP(u); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest,
@ -33,8 +33,12 @@ func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error {
return errors.New("not allow proxy to local")
}
}
if r := ctx.GetHeader("Range"); r != "" {
rsc := NewHttpReadSeekCloser(u, WithHeadersMap(headers))
if cache {
rsc := NewHttpReadSeekCloser(u,
WithHeadersMap(headers),
WithNotSupportRange(ctx.GetHeader("Range") == ""),
)
defer rsc.Close()
NewSliceCacheProxy(u, 1024*512, rsc, defaultCache).ServeHTTP(ctx.Writer, ctx.Request)
return nil
@ -109,9 +113,9 @@ func ProxyURL(ctx *gin.Context, u string, headers map[string]string) error {
return nil
}
func AutoProxyURL(ctx *gin.Context, u, t string, headers map[string]string, token, roomId, movieId string) error {
func AutoProxyURL(ctx *gin.Context, u, t string, headers map[string]string, cache bool, token, roomId, movieId string) error {
if strings.HasPrefix(t, "m3u") || utils.IsM3u8Url(u) {
return ProxyM3u8(ctx, u, headers, true, token, roomId, movieId)
return ProxyM3u8(ctx, u, headers, cache, true, token, roomId, movieId)
}
return ProxyURL(ctx, u, headers)
return ProxyURL(ctx, u, headers, cache)
}

@ -2,7 +2,6 @@ package proxy
import (
"context"
"errors"
"fmt"
"io"
"net/http"
@ -128,6 +127,12 @@ func WithLength(length int64) HttpReadSeekerConf {
}
}
func WithNotSupportRange(notSupportRange bool) HttpReadSeekerConf {
return func(h *HttpReadSeekCloser) {
h.notSupportRange = notSupportRange
}
}
func NewHttpReadSeekCloser(url string, conf ...HttpReadSeekerConf) *HttpReadSeekCloser {
rs := &HttpReadSeekCloser{
url: url,
@ -183,7 +188,7 @@ func (h *HttpReadSeekCloser) Read(p []byte) (n int, err error) {
if err == io.EOF {
return n, io.EOF
}
return 0, fmt.Errorf("fetch next chunk: %w", err)
return 0, fmt.Errorf("failed to fetch next chunk: %w", err)
}
}
@ -204,7 +209,7 @@ func (h *HttpReadSeekCloser) Read(p []byte) (n int, err error) {
if n > 0 {
return n, nil
}
return 0, fmt.Errorf("read response body: %w", err)
return 0, fmt.Errorf("error reading response body: %w", err)
}
}
@ -220,37 +225,55 @@ func (h *HttpReadSeekCloser) FetchNextChunk() error {
req, err := h.createRequest()
if err != nil {
return fmt.Errorf("create request: %w", err)
return fmt.Errorf("failed to create request: %w", err)
}
resp, err := h.client.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
return fmt.Errorf("failed to execute HTTP request: %w", err)
}
h.contentType = resp.Header.Get("Content-Type")
if resp.StatusCode == http.StatusOK {
// if the maximum offset of the current response is less than the content length minus one, it means that the server does not support range requests
if h.currentRespMaxOffset < resp.ContentLength-1 || h.notSupportRange {
// if the offset is not 0, it means that the seek method is incorrectly used
if h.offset != 0 {
resp.Body.Close()
return fmt.Errorf("server does not support range requests, cannot seek to non-zero offset")
}
h.notSupportRange = true
h.contentLength = resp.ContentLength
h.currentRespMaxOffset = h.contentLength - 1
h.currentResp = resp
return nil
}
// if the content length is not known, it may be because the requested length is too long, and a new request is needed
if h.contentLength < 0 {
h.contentLength = resp.ContentLength
resp.Body.Close()
return h.FetchNextChunk()
}
// if the offset is greater than 0, it means that the seek method is incorrectly used
if h.offset > 0 {
resp.Body.Close()
return fmt.Errorf("not support range")
return fmt.Errorf("server does not support range requests, cannot seek to offset %d", h.offset)
}
h.notSupportRange = true
h.currentRespMaxOffset = h.contentLength - 1
h.currentResp = resp
return nil
}
if resp.StatusCode != http.StatusPartialContent {
resp.Body.Close()
return fmt.Errorf("status code: %d", resp.StatusCode)
return fmt.Errorf("unexpected HTTP status code: %d (expected 206 Partial Content)", resp.StatusCode)
}
if err := h.checkResponse(resp); err != nil {
resp.Body.Close()
return fmt.Errorf("check response: %w", err)
return fmt.Errorf("response validation failed: %w", err)
}
contentTotalLength, err := ParseContentRangeTotalLength(resp.Header.Get("Content-Range"))
@ -262,13 +285,18 @@ func (h *HttpReadSeekCloser) FetchNextChunk() error {
h.currentRespMaxOffset = end
}
h.contentType = resp.Header.Get("Content-Type")
h.currentResp = resp
return nil
}
func (h *HttpReadSeekCloser) createRequest() (*http.Request, error) {
if h.notSupportRange {
if h.contentLength != -1 {
h.currentRespMaxOffset = h.contentLength - 1
}
return h.createRequestWithoutRange()
}
req, err := h.createRequestWithoutRange()
if err != nil {
return nil, err
@ -288,7 +316,7 @@ func (h *HttpReadSeekCloser) createRequest() (*http.Request, error) {
func (h *HttpReadSeekCloser) createRequestWithoutRange() (*http.Request, error) {
req, err := http.NewRequestWithContext(h.ctx, h.method, h.url, nil)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header = h.headers.Clone()
req.Header.Del("Range")
@ -315,7 +343,7 @@ func (h *HttpReadSeekCloser) closeCurrentResp() {
func (h *HttpReadSeekCloser) checkContentType(ct string) error {
if len(h.allowedContentTypes) != 0 {
if ct == "" || slices.Index(h.allowedContentTypes, ct) == -1 {
return fmt.Errorf("content type `%s` not allowed", ct)
return fmt.Errorf("content type '%s' is not in the list of allowed content types: %v", ct, h.allowedContentTypes)
}
}
return nil
@ -324,13 +352,13 @@ func (h *HttpReadSeekCloser) checkContentType(ct string) error {
func (h *HttpReadSeekCloser) checkStatusCode(code int) error {
if len(h.allowedStatusCodes) != 0 {
if slices.Index(h.allowedStatusCodes, code) == -1 {
return fmt.Errorf("status code `%d` not allowed", code)
return fmt.Errorf("HTTP status code %d is not in the list of allowed status codes: %v", code, h.allowedStatusCodes)
}
return nil
}
if len(h.notAllowedStatusCodes) != 0 {
if slices.Index(h.notAllowedStatusCodes, code) != -1 {
return fmt.Errorf("status code `%d` not allowed", code)
return fmt.Errorf("HTTP status code %d is in the list of not allowed status codes: %v", code, h.notAllowedStatusCodes)
}
}
return nil
@ -339,11 +367,11 @@ func (h *HttpReadSeekCloser) checkStatusCode(code int) error {
func (h *HttpReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
newOffset, err := h.calculateNewOffset(offset, whence)
if err != nil {
return 0, fmt.Errorf("calculate new offset: %w", err)
return 0, fmt.Errorf("failed to calculate new offset: %w", err)
}
if newOffset < 0 {
return 0, fmt.Errorf("negative offset: %d", newOffset)
return 0, fmt.Errorf("cannot seek to negative offset: %d", newOffset)
}
if newOffset != h.offset {
@ -355,23 +383,30 @@ func (h *HttpReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
}
func (h *HttpReadSeekCloser) calculateNewOffset(offset int64, whence int) (int64, error) {
if offset != 0 && h.notSupportRange {
return 0, fmt.Errorf("not support range")
}
switch whence {
case io.SeekStart:
if h.notSupportRange && offset != 0 && offset != h.offset {
return 0, fmt.Errorf("server does not support range requests, cannot seek to non-zero offset")
}
return offset, nil
case io.SeekCurrent:
if h.notSupportRange && offset != 0 {
return 0, fmt.Errorf("server does not support range requests, cannot seek to non-zero offset")
}
return h.offset + offset, nil
case io.SeekEnd:
if h.contentLength < 0 {
if err := h.fetchContentLength(); err != nil {
return 0, fmt.Errorf("fetch content length: %w", err)
return 0, fmt.Errorf("failed to fetch content length: %w", err)
}
}
return h.contentLength - offset, nil
newOffset := h.contentLength - offset
if h.notSupportRange && newOffset != h.offset {
return 0, fmt.Errorf("server does not support range requests, cannot seek to non-zero offset")
}
return newOffset, nil
default:
return 0, fmt.Errorf("invalid whence: %d", whence)
return 0, fmt.Errorf("invalid seek whence value: %d (must be 0, 1, or 2)", whence)
}
}
@ -384,20 +419,20 @@ func (h *HttpReadSeekCloser) fetchContentLength() error {
resp, err := h.client.Do(req)
if err != nil {
return err
return fmt.Errorf("failed to execute HEAD request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status code: %d", resp.StatusCode)
return fmt.Errorf("unexpected HTTP status code in HEAD request: %d (expected 200 OK)", resp.StatusCode)
}
if err := h.checkResponse(resp); err != nil {
return err
return fmt.Errorf("HEAD response validation failed: %w", err)
}
if resp.ContentLength < 0 {
return errors.New("invalid content length")
return fmt.Errorf("server returned invalid content length: %d", resp.ContentLength)
}
h.contentType = resp.Header.Get("Content-Type")
@ -426,38 +461,38 @@ func (h *HttpReadSeekCloser) ContentType() (string, error) {
if h.contentType != "" {
return h.contentType, nil
}
return "", fmt.Errorf("content type not available")
return "", fmt.Errorf("content type is not available - no successful response received yet")
}
func (h *HttpReadSeekCloser) ContentTotalLength() (int64, error) {
if h.contentLength > 0 {
return h.contentLength, nil
}
return 0, fmt.Errorf("content total length not available")
return 0, fmt.Errorf("content total length is not available - no successful response received yet")
}
func ParseContentRangeStartAndEnd(contentRange string) (int64, int64, error) {
if contentRange == "" {
return 0, 0, fmt.Errorf("empty content range")
return 0, 0, fmt.Errorf("Content-Range header is empty")
}
if !strings.HasPrefix(contentRange, "bytes ") {
return 0, 0, fmt.Errorf("invalid content range format: %s", contentRange)
return 0, 0, fmt.Errorf("invalid Content-Range header format (expected 'bytes ' prefix): %s", contentRange)
}
parts := strings.Split(strings.TrimPrefix(contentRange, "bytes "), "/")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid content range parts: %s", contentRange)
return 0, 0, fmt.Errorf("invalid Content-Range header format (expected 2 parts separated by '/'): %s", contentRange)
}
rangeParts := strings.Split(strings.TrimSpace(parts[0]), "-")
if len(rangeParts) != 2 {
return 0, 0, fmt.Errorf("invalid content range range parts: %s", contentRange)
return 0, 0, fmt.Errorf("invalid Content-Range range format (expected start-end): %s", contentRange)
}
start, err := strconv.ParseInt(strings.TrimSpace(rangeParts[0]), 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("invalid content range start: %w", err)
return 0, 0, fmt.Errorf("invalid Content-Range start value '%s': %w", rangeParts[0], err)
}
rangeParts[1] = strings.TrimSpace(rangeParts[1])
@ -467,7 +502,7 @@ func ParseContentRangeStartAndEnd(contentRange string) (int64, int64, error) {
} else {
end, err = strconv.ParseInt(rangeParts[1], 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("invalid content range end: %w", err)
return 0, 0, fmt.Errorf("invalid Content-Range end value '%s': %w", rangeParts[1], err)
}
}
@ -477,16 +512,16 @@ func ParseContentRangeStartAndEnd(contentRange string) (int64, int64, error) {
// ParseContentRangeTotalLength parses a Content-Range header value and returns the total length
func ParseContentRangeTotalLength(contentRange string) (int64, error) {
if contentRange == "" {
return 0, fmt.Errorf("empty content range")
return 0, fmt.Errorf("Content-Range header is empty")
}
if !strings.HasPrefix(contentRange, "bytes ") {
return 0, fmt.Errorf("invalid content range format: %s", contentRange)
return 0, fmt.Errorf("invalid Content-Range header format (expected 'bytes ' prefix): %s", contentRange)
}
parts := strings.Split(strings.TrimPrefix(contentRange, "bytes "), "/")
if len(parts) != 2 {
return 0, fmt.Errorf("invalid content range parts: %s", contentRange)
return 0, fmt.Errorf("invalid Content-Range header format (expected 2 parts separated by '/'): %s", contentRange)
}
if parts[1] == "" || parts[1] == "*" {
@ -495,11 +530,11 @@ func ParseContentRangeTotalLength(contentRange string) (int64, error) {
length, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid content range length: %w", err)
return 0, fmt.Errorf("invalid Content-Range total length value '%s': %w", parts[1], err)
}
if length < 0 {
return 0, fmt.Errorf("negative content range length: %d", length)
return 0, fmt.Errorf("Content-Range total length cannot be negative: %d", length)
}
return length, nil

@ -134,7 +134,7 @@ func (s *alistVendorService) ProxyMovie(ctx *gin.Context) {
ctx.Data(http.StatusOK, "audio/mpegurl", data.Ali.M3U8ListFile)
return
case "raw":
err := proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
err := proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, true, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}
@ -173,7 +173,7 @@ func (s *alistVendorService) ProxyMovie(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("proxy is not enabled"))
return
}
err = proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
err = proxy.AutoProxyURL(ctx, data.URL, s.movie.MovieBase.Type, nil, true, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}

@ -127,7 +127,7 @@ func (s *bilibiliVendorService) ProxyMovie(ctx *gin.Context) {
headers["Referer"] = "https://www.bilibili.com"
headers["User-Agent"] = utils.UA
}
err = proxy.ProxyURL(ctx, mpdC.Urls[streamId], headers)
err = proxy.ProxyURL(ctx, mpdC.Urls[streamId], headers, true)
if err != nil {
log.Errorf("proxy vendor movie [%s] error: %v", mpdC.Urls[streamId], err)
}

@ -151,7 +151,7 @@ func (s *embyVendorService) ProxyMovie(ctx *gin.Context) {
ctx.Redirect(http.StatusFound, embyC.Sources[source].URL)
return
}
err = proxy.AutoProxyURL(ctx, embyC.Sources[source].URL, "", nil, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
err = proxy.AutoProxyURL(ctx, embyC.Sources[source].URL, "", nil, true, ctx.GetString("token"), s.movie.RoomID, s.movie.ID)
if err != nil {
log.Errorf("proxy vendor movie error: %v", err)
}

Loading…
Cancel
Save