From d0b684a26643404a9efeb0ae90069d6facc8a467 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 17 Jan 2024 11:53:22 +0100 Subject: [PATCH] Fixes and tests --- pkg/proxy/middleware.go | 138 +++++++++++++++++--------- pkg/proxy/middleware_test.go | 115 ++++++++++++++++++++- tests/integration/integration_test.go | 2 +- 3 files changed, 204 insertions(+), 51 deletions(-) diff --git a/pkg/proxy/middleware.go b/pkg/proxy/middleware.go index e8a5820f..6964a84a 100644 --- a/pkg/proxy/middleware.go +++ b/pkg/proxy/middleware.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "io" - "log" "math/rand" "net/http" "time" @@ -44,35 +43,35 @@ func NewRetryMiddleware(maxRetries int, other http.Handler, optRetryStatusCodes } } +type xResponseWriter interface { + http.ResponseWriter + discardedResponse() bool + capturedStatusCode() int +} +type xBodyCapturer interface { + io.ReadCloser + Capture() +} + func (r RetryMiddleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - lazyBody := &lazyBodyCapturer{ - reader: request.Body, - buf: bytes.NewBuffer([]byte{}), - } + lazyBody := newLazyBodyCapturer(request.Body) request.Body = lazyBody for i := 0; ; i++ { - capturedResp := &responseWriterDelegator{ - isRetryableStatusCode: r.isRetryableStatusCode, - ResponseWriter: writer, - headerBuf: make(http.Header), - discardErrResp: i < r.maxRetries && - request.Context().Err() == nil, // abort early on timeout, context cancel - } + discardErrResp := i < r.maxRetries && request.Context().Err() == nil + capturedResp := newResponseWriterDelegator(writer, r.isRetryableStatusCode, discardErrResp) // call next handler in chain - req, err := http.NewRequestWithContext(request.Context(), request.Method, request.URL.String(), lazyBody) - if err != nil { - log.Printf("clone request: %v", err) - writer.WriteHeader(http.StatusInternalServerError) - return - } - r.nextHandler.ServeHTTP(capturedResp, req) - lazyBody.Capture() - if !capturedResp.discardErrResp || // max retries reached - !r.isRetryableStatusCode(capturedResp.statusCode) { + reqClone := request.Clone(request.Context()) // also copies the reference to the lazy body capturer + r.nextHandler.ServeHTTP(capturedResp, reqClone) + + if !capturedResp.discardedResponse() || // max retries reached or context error + !r.isRetryableStatusCode(capturedResp.capturedStatusCode()) { break } + // setup for retry + lazyBody.Capture() + totalRetries.Inc() - // Exponential backoff + // exponential backoff jitter := time.Duration(r.rSource.Intn(50)) time.Sleep(time.Millisecond*time.Duration(1<