1 Commits

Author SHA1 Message Date
c16e9e292f Avoid truncating large uploads with body timeouts
http.Server.ReadTimeout and WriteTimeout cover the full request/
response lifetime including streaming bodies, so finite values truncate
large object PUTs and GETs mid-flight. Disable both and rely on
ReadHeaderTimeout (header phase) and IdleTimeout (between keepalive
requests), consistent with how S3-style streaming servers behave.
serverMaxConnections still bounds concurrent load.

Also log io.Copy errors in handleGetObject so a client disconnect or
mid-stream chunk failure is no longer invisible.
2026-07-26 12:28:13 +02:00

View File

@@ -43,11 +43,17 @@ const (
maxObjectKeyBytes = 1024 maxObjectKeyBytes = 1024
maxAWSChunkedLineBytes = 8 << 10 maxAWSChunkedLineBytes = 8 << 10
serverReadHeaderTimeout = 5 * time.Second serverReadHeaderTimeout = 5 * time.Second
serverReadTimeout = 60 * time.Second // serverReadTimeout and serverWriteTimeout are disabled on purpose: both
serverWriteTimeout = 120 * time.Second // cover the full request/response lifetime including streaming bodies, so a
serverIdleTimeout = 120 * time.Second // finite value truncates large uploads and downloads mid-flight. Read and
serverMaxHeaderBytes = 1 << 20 // idle liveness are still enforced by serverReadHeaderTimeout (header
serverMaxConnections = 1024 // phase) and serverIdleTimeout (between keepalive requests), and concurrent
// load is bounded by serverMaxConnections.
serverReadTimeout = 0
serverWriteTimeout = 0
serverIdleTimeout = 120 * time.Second
serverMaxHeaderBytes = 1 << 20
serverMaxConnections = 1024
) )
func NewHandler(svc *service.ObjectService, logger *slog.Logger, logConfig logging.Config, authSvc *auth.Service, adminAPI bool) *Handler { func NewHandler(svc *service.ObjectService, logger *slog.Logger, logConfig logging.Config, authSvc *auth.Service, adminAPI bool) *Handler {
@@ -271,8 +277,14 @@ func (h *Handler) handleGetObject(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Last-Modified", time.Unix(manifest.CreatedAt, 0).UTC().Format(http.TimeFormat)) w.Header().Set("Last-Modified", time.Unix(manifest.CreatedAt, 0).UTC().Format(http.TimeFormat))
w.Header().Set("Accept-Ranges", "bytes") w.Header().Set("Accept-Ranges", "bytes")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, stream) if _, err = io.Copy(w, stream); err != nil && !errors.Is(err, context.Canceled) {
h.logger.Warn("get_object_stream_failed",
"bucket", bucket,
"key", key,
"etag", manifest.ETag,
"error", err,
)
}
} }
func (h *Handler) handlePostObject(w http.ResponseWriter, r *http.Request) { func (h *Handler) handlePostObject(w http.ResponseWriter, r *http.Request) {