From 3515a799214894318f06159369dcfa2eb1ff5215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 7 Dec 2023 15:22:11 -0500 Subject: [PATCH 1/2] internal/server/response: Don't re-send headers when streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber --- internal/server/response/response.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/server/response/response.go b/internal/server/response/response.go index 65cb02f99ed..cd9b06db5fc 100644 --- a/internal/server/response/response.go +++ b/internal/server/response/response.go @@ -187,7 +187,9 @@ func (r *syncResponse) Render(w http.ResponseWriter) error { code = http.StatusOK } - w.WriteHeader(code) + if w.Header().Get("Connection") != "keep-alive" { + w.WriteHeader(code) + } // Handle plain text responses. if r.plaintext { @@ -347,7 +349,9 @@ func (r *errorResponse) Render(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.Header().Set("X-Content-Type-Options", "nosniff") - w.WriteHeader(r.code) // Set the error code in the HTTP header response. + if w.Header().Get("Connection") != "keep-alive" { + w.WriteHeader(r.code) // Set the error code in the HTTP header response. + } _, err = fmt.Fprintln(w, buf.String()) @@ -524,7 +528,10 @@ func (r *forwardedResponse) Render(w http.ResponseWriter) error { w.Header().Set(key, response.Header.Get(key)) } - w.WriteHeader(response.StatusCode) + if w.Header().Get("Connection") != "keep-alive" { + w.WriteHeader(response.StatusCode) + } + _, err = io.Copy(w, response.Body) return err } From e5a0594c5f4c5995e7a52bdba70976caf5743201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= Date: Thu, 7 Dec 2023 15:22:26 -0500 Subject: [PATCH 2/2] incusd/operations: Use ManualResponse to send headers early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This avoids hitting a timeout waiting for headers to arrive and properly marks the connection for keep-alive which should avoid other timeouts when going through proxies. Signed-off-by: Stéphane Graber --- cmd/incusd/operations.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/cmd/incusd/operations.go b/cmd/incusd/operations.go index 42e0f5f6e4e..5c5f140026d 100644 --- a/cmd/incusd/operations.go +++ b/cmd/incusd/operations.go @@ -947,19 +947,37 @@ func operationWaitGet(d *Daemon, r *http.Request) response.Response { ctx, cancel = context.WithCancel(r.Context()) } - defer cancel() + waitResponse := func(w http.ResponseWriter) error { + defer cancel() + + // Write header to avoid client side timeouts. + w.Header().Set("Connection", "keep-alive") + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(http.StatusOK) + f, ok := w.(http.Flusher) + if ok { + f.Flush() + } - err = op.Wait(ctx) - if err != nil { - return response.SmartError(err) - } + // Wait for the operation. + err = op.Wait(ctx) + if err != nil { + _ = response.SmartError(err).Render(w) + return nil + } - _, body, err := op.Render() - if err != nil { - return response.SmartError(err) + _, body, err := op.Render() + if err != nil { + _ = response.SmartError(err).Render(w) + return nil + } + + _ = response.SyncResponse(true, body).Render(w) + return nil } - return response.SyncResponse(true, body) + return response.ManualResponse(waitResponse) } // Then check if the query is from an operation on another node, and, if so, forward it