Final fix for writeheader

This commit is contained in:
Mukhtar Akere
2025-08-05 05:01:34 +01:00
parent 40755fbdde
commit cb63fc69f5
2 changed files with 34 additions and 21 deletions

View File

@@ -124,6 +124,7 @@ func (f *File) servePreloadedContent(w http.ResponseWriter, r *http.Request) err
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("Accept-Ranges", "bytes")
w.WriteHeader(http.StatusPartialContent)
_, err = w.Write(content[start : end+1])
return err
@@ -132,6 +133,7 @@ func (f *File) servePreloadedContent(w http.ResponseWriter, r *http.Request) err
// Full content
w.Header().Set("Content-Length", fmt.Sprintf("%d", size))
w.Header().Set("Accept-Ranges", "bytes")
w.WriteHeader(http.StatusOK)
_, err := w.Write(content)
return err
@@ -195,9 +197,13 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
return retryErr
}
if err := f.streamBuffer(w, resp.Body); err != nil {
return err
// Determine status code based on range request
statusCode := http.StatusOK
if isRangeRequest == 1 {
statusCode = http.StatusPartialContent
}
// Set headers before streaming
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" {
w.Header().Set("Content-Length", contentLength)
}
@@ -205,10 +211,14 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
if contentRange := resp.Header.Get("Content-Range"); contentRange != "" && isRangeRequest == 1 {
w.Header().Set("Content-Range", contentRange)
}
if err := f.streamBuffer(w, resp.Body, statusCode); err != nil {
return err
}
return nil
}
func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader, statusCode int) error {
flusher, ok := w.(http.Flusher)
if !ok {
return fmt.Errorf("response does not support flushing")
@@ -216,12 +226,19 @@ func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
smallBuf := make([]byte, 64*1024) // 64 KB
if n, err := src.Read(smallBuf); n > 0 {
// Write status code just before first successful write
w.WriteHeader(statusCode)
if _, werr := w.Write(smallBuf[:n]); werr != nil {
return werr
if isClientDisconnection(werr) {
return &streamError{Err: werr, StatusCode: 0, IsClientDisconnection: true}
}
// Headers already sent, can't send HTTP error response
return &streamError{Err: werr, StatusCode: 0, IsClientDisconnection: false}
}
flusher.Flush()
} else if err != nil && err != io.EOF {
return err
return &streamError{Err: err, StatusCode: http.StatusInternalServerError}
}
buf := make([]byte, 256*1024) // 256 KB
@@ -232,7 +249,8 @@ func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
if isClientDisconnection(writeErr) {
return &streamError{Err: writeErr, StatusCode: 0, IsClientDisconnection: true}
}
return writeErr
// Headers already sent, can't send HTTP error response
return &streamError{Err: writeErr, StatusCode: 0, IsClientDisconnection: false}
}
flusher.Flush()
}

View File

@@ -463,16 +463,6 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
return
}
// Check if this will be a range request before streaming
isRangeRequest := r.Header.Get("Range") != ""
// Write status headers before streaming starts
if isRangeRequest {
w.WriteHeader(http.StatusPartialContent)
} else {
w.WriteHeader(http.StatusOK)
}
if err := file.StreamResponse(w, r); err != nil {
var streamErr *streamError
if errors.As(err, &streamErr) {
@@ -480,12 +470,17 @@ func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
if errors.Is(streamErr.Err, context.Canceled) || errors.Is(streamErr.Err, context.DeadlineExceeded) || streamErr.IsClientDisconnection {
return
}
// For other errors, we can't send HTTP error response since headers are already written
h.logger.Error().Err(streamErr.Err).Str("file", file.name).Msg("Stream error after headers written")
return
if streamErr.StatusCode > 0 {
http.Error(w, streamErr.Error(), streamErr.StatusCode)
return
} else {
// We've already written a status code, just log the error
h.logger.Error().Err(streamErr.Err).Msg("Streaming error")
return
}
} else {
// Generic error - can't send HTTP error response since headers are already written
h.logger.Error().Err(err).Str("file", file.name).Msg("Generic stream error after headers written")
// Generic error
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}