From 387f39cbcc563004bd6338bd4f193a848049bdc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sun, 25 Jan 2026 15:40:04 +0100 Subject: [PATCH] Fix public file streaming with async download - Use io.Pipe for immediate response headers - Start WebDAV download in goroutine to avoid blocking - Stream content as it becomes available - Prevents client timeouts on slow downloads - Maintains CORS and MIME type headers --- go_cloud/internal/http/routes.go | 36 +++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 5abbcbd..0c22456 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -3178,14 +3178,20 @@ func publicFileViewHandler(w http.ResponseWriter, r *http.Request, db *database. downloadCtx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) defer cancel() - // Stream file - resp, err := client.Download(downloadCtx, file.Path, r.Header.Get("Range")) - if err != nil { - errors.LogError(r, err, "Failed to download file") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) - return - } - defer resp.Body.Close() + // Create a pipe for streaming + pipeReader, pipeWriter := io.Pipe() + + // Start download in goroutine + go func() { + defer pipeWriter.Close() + resp, err := client.Download(downloadCtx, file.Path, r.Header.Get("Range")) + if err != nil { + pipeWriter.CloseWithError(err) + return + } + defer resp.Body.Close() + io.Copy(pipeWriter, resp.Body) + }() // Add CORS headers for public access w.Header().Set("Access-Control-Allow-Origin", "*") @@ -3193,24 +3199,16 @@ func publicFileViewHandler(w http.ResponseWriter, r *http.Request, db *database. w.Header().Set("Access-Control-Allow-Headers", "Range") // Set status code (200 or 206 for partial) - w.WriteHeader(resp.StatusCode) - - // Copy headers from Nextcloud response, but skip Content-Type to ensure correct MIME type - for k, v := range resp.Header { - if k != "Content-Type" { - w.Header()[k] = v - } - } + w.WriteHeader(200) // Assume 200 for now, or check if Range was requested // Set correct Content-Type based on file extension w.Header().Set("Content-Type", mimeType) // Ensure inline viewing behavior (no Content-Disposition attachment) - w.Header().Del("Content-Disposition") w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", file.Name)) - // Copy body - io.Copy(w, resp.Body) + // Copy body from pipe + io.Copy(w, pipeReader) } func getUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {