Fix issues with headers
This commit is contained in:
@@ -106,7 +106,9 @@ func (f *File) getDownloadByteRange() (*[2]int64, error) {
|
|||||||
return byteRange, nil
|
return byteRange, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) servePreloadedContent(w http.ResponseWriter, r *http.Request) error {
|
// setVideoStreamingHeaders sets the necessary headers for video streaming
|
||||||
|
// It returns error and a boolean indicating if the request is a range request
|
||||||
|
func (f *File) servePreloadedContent(w http.ResponseWriter, r *http.Request) (error, bool) {
|
||||||
content := f.content
|
content := f.content
|
||||||
size := int64(len(content))
|
size := int64(len(content))
|
||||||
|
|
||||||
@@ -115,29 +117,27 @@ func (f *File) servePreloadedContent(w http.ResponseWriter, r *http.Request) err
|
|||||||
ranges, err := parseRange(rangeHeader, size)
|
ranges, err := parseRange(rangeHeader, size)
|
||||||
if err != nil || len(ranges) != 1 {
|
if err != nil || len(ranges) != 1 {
|
||||||
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
|
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
|
||||||
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}
|
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
start, end := ranges[0].start, ranges[0].end
|
start, end := ranges[0].start, ranges[0].end
|
||||||
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, size))
|
w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, size))
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", end-start+1))
|
||||||
w.Header().Set("Accept-Ranges", "bytes")
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
w.WriteHeader(http.StatusPartialContent)
|
|
||||||
|
|
||||||
_, err = w.Write(content[start : end+1])
|
_, err = w.Write(content[start : end+1])
|
||||||
return err
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full content
|
// Full content
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", size))
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", size))
|
||||||
w.Header().Set("Accept-Ranges", "bytes")
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
_, err := w.Write(content)
|
_, err := w.Write(content)
|
||||||
return err
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) StreamResponse(w http.ResponseWriter, r *http.Request) error {
|
func (f *File) StreamResponse(w http.ResponseWriter, r *http.Request) (error, bool) {
|
||||||
// Handle preloaded content files
|
// Handle preloaded content files
|
||||||
if f.content != nil {
|
if f.content != nil {
|
||||||
return f.servePreloadedContent(w, r)
|
return f.servePreloadedContent(w, r)
|
||||||
@@ -147,24 +147,24 @@ func (f *File) StreamResponse(w http.ResponseWriter, r *http.Request) error {
|
|||||||
return f.streamWithRetry(w, r, 0)
|
return f.streamWithRetry(w, r, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCount int) error {
|
func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCount int) (error, bool) {
|
||||||
const maxRetries = 3
|
const maxRetries = 3
|
||||||
_log := f.cache.Logger()
|
_log := f.cache.Logger()
|
||||||
|
|
||||||
// Get download link (with caching optimization)
|
// Get download link (with caching optimization)
|
||||||
downloadLink, err := f.getDownloadLink()
|
downloadLink, err := f.getDownloadLink()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &streamError{Err: err, StatusCode: http.StatusPreconditionFailed}
|
return &streamError{Err: err, StatusCode: http.StatusPreconditionFailed}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if downloadLink == "" {
|
if downloadLink == "" {
|
||||||
return &streamError{Err: fmt.Errorf("empty download link"), StatusCode: http.StatusNotFound}
|
return &streamError{Err: fmt.Errorf("empty download link"), StatusCode: http.StatusNotFound}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create upstream request with streaming optimizations
|
// Create upstream request with streaming optimizations
|
||||||
upstreamReq, err := http.NewRequest("GET", downloadLink, nil)
|
upstreamReq, err := http.NewRequest("GET", downloadLink, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &streamError{Err: err, StatusCode: http.StatusInternalServerError}
|
return &streamError{Err: err, StatusCode: http.StatusInternalServerError}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoStreamingHeaders(upstreamReq)
|
setVideoStreamingHeaders(upstreamReq)
|
||||||
@@ -172,12 +172,12 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
|
|||||||
// Handle range requests (critical for video seeking)
|
// Handle range requests (critical for video seeking)
|
||||||
isRangeRequest := f.handleRangeRequest(upstreamReq, r, w)
|
isRangeRequest := f.handleRangeRequest(upstreamReq, r, w)
|
||||||
if isRangeRequest == -1 {
|
if isRangeRequest == -1 {
|
||||||
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}
|
return &streamError{Err: fmt.Errorf("invalid range"), StatusCode: http.StatusRequestedRangeNotSatisfiable}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := sharedClient.Do(upstreamReq)
|
resp, err := sharedClient.Do(upstreamReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &streamError{Err: err, StatusCode: http.StatusServiceUnavailable}
|
return &streamError{Err: err, StatusCode: http.StatusServiceUnavailable}, false
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -192,14 +192,20 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
|
|||||||
return f.streamWithRetry(w, r, retryCount+1)
|
return f.streamWithRetry(w, r, retryCount+1)
|
||||||
}
|
}
|
||||||
if retryErr != nil {
|
if retryErr != nil {
|
||||||
return retryErr
|
return retryErr, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.streamBuffer(w, resp.Body); err != nil {
|
if err := f.streamBuffer(w, resp.Body); err != nil {
|
||||||
return err
|
return err, false
|
||||||
}
|
}
|
||||||
setVideoResponseHeaders(w, resp, isRangeRequest == 1)
|
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" {
|
||||||
return nil
|
w.Header().Set("Content-Length", contentLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if contentRange := resp.Header.Get("Content-Range"); contentRange != "" && isRangeRequest == 1 {
|
||||||
|
w.Header().Set("Content-Range", contentRange)
|
||||||
|
}
|
||||||
|
return nil, isRangeRequest == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
|
func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
"github.com/sirrobot01/decypharr/pkg/debrid/types"
|
||||||
"golang.org/x/net/webdav"
|
"golang.org/x/net/webdav"
|
||||||
"io"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -464,47 +463,36 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := file.StreamResponse(w, r); err != nil {
|
err, ranged := file.StreamResponse(w, r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
var streamErr *streamError
|
var streamErr *streamError
|
||||||
if errors.As(err, &streamErr) {
|
if errors.As(err, &streamErr) {
|
||||||
// Handle client disconnections silently (just debug log)
|
// Handle client disconnections silently (just debug log)
|
||||||
if errors.Is(streamErr.Err, context.Canceled) || errors.Is(streamErr.Err, context.DeadlineExceeded) || streamErr.IsClientDisconnection {
|
if errors.Is(streamErr.Err, context.Canceled) || errors.Is(streamErr.Err, context.DeadlineExceeded) || streamErr.IsClientDisconnection {
|
||||||
return // Don't log as error or try to write response
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamErr.StatusCode > 0 && !hasHeadersWritten(w) {
|
if streamErr.StatusCode > 0 {
|
||||||
http.Error(w, streamErr.Error(), streamErr.StatusCode)
|
http.Error(w, streamErr.Error(), streamErr.StatusCode)
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
h.logger.Error().
|
|
||||||
Err(streamErr.Err).
|
|
||||||
Str("path", r.URL.Path).
|
|
||||||
Msg("Stream error")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Generic error
|
// Generic error
|
||||||
if !hasHeadersWritten(w) {
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
return
|
||||||
return
|
}
|
||||||
} else {
|
} else {
|
||||||
h.logger.Error().
|
if ranged {
|
||||||
Err(err).
|
// If the file was served as a ranged request, return a 206 status code
|
||||||
Str("path", r.URL.Path).
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
Msg("Stream error after headers written")
|
} else {
|
||||||
}
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
return
|
||||||
// Fallback to ServeContent for other webdav.File implementations
|
|
||||||
if rs, ok := fRaw.(io.ReadSeeker); ok {
|
|
||||||
http.ServeContent(w, r, fi.Name(), fi.ModTime(), rs)
|
|
||||||
} else {
|
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = io.Copy(w, fRaw)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleHead(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) handleHead(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -227,16 +227,3 @@ func setVideoStreamingHeaders(req *http.Request) {
|
|||||||
req.Header.Set("User-Agent", "VideoStream/1.0")
|
req.Header.Set("User-Agent", "VideoStream/1.0")
|
||||||
req.Header.Set("Priority", "u=1")
|
req.Header.Set("Priority", "u=1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setVideoResponseHeaders(w http.ResponseWriter, resp *http.Response, isRange bool) {
|
|
||||||
// Copy essential headers from upstream
|
|
||||||
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" {
|
|
||||||
w.Header().Set("Content-Length", contentLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentRange := resp.Header.Get("Content-Range"); contentRange != "" && isRange {
|
|
||||||
w.Header().Set("Content-Range", contentRange)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user