Replies: 1 comment
-
I encountered the same issue lately. I needed some sort of retry mechanism in which I can delay the writing of the reponse back to the calling client until I have a satisfactory answer from a downstream server. Here is what I am coming up with to handle this situation. type delayedResponseWriter struct {
originalResponse http.ResponseWriter
statusCode int
buf *bytes.Buffer
}
// Header returns the map of header fields.
func (d delayedResponseWriter) Header() http.Header {
return d.originalResponse.Header()
}
// Write records the body content to send when Commit is called.
func (d delayedResponseWriter) Write(bytes []byte) (int, error) {
return d.buf.Write(bytes)
}
// WriteHeader records the statusCode to send when Commit is called.
func (d delayedResponseWriter) WriteHeader(statusCode int) {
d.statusCode = statusCode
}
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
// This is required for some content types such as octet-stream (for files download)
func (d delayedResponseWriter) Flush() {
d.originalResponse.(http.Flusher).Flush()
}
// Reset resets the internal buffer and status code.
func (d delayedResponseWriter) Reset() {
d.buf.Reset()
d.statusCode = 0
}
// Commit sends the header and body content to the original response writer.
func (d delayedResponseWriter) Commit() (err error) {
if d.statusCode != 0 {
d.originalResponse.WriteHeader(d.statusCode)
}
_, err = d.originalResponse.Write(d.buf.Bytes())
return
}
func newDelayedResponseWriter(c echo.Context) delayedResponseWriter {
return delayedResponseWriter{
originalResponse: c.Response(),
buf: new(bytes.Buffer)}
} Now in some middleware of my own I give this response writer to Echo: writer := newDelayedResponseWriter(c)
delayedResponse := echo.NewResponse(writer, c.Echo())
c.SetResponse(delayedResponse)
err = next(c) After calling writer.Reset()
delayedResponse.Committed = false
delayedResponse.Size = 0
delayedResponse.Status = 0
// This _error key comes from Echo (referenced in ProxyWithConfig middleware)
// so we need to reset this as well.
c.Set("_error", nil)
// ...and retry request
err = next(c) Now, if delayedWriter, ok := c.Response().Writer.(delayedResponseWriter); ok {
if commitErr := delayedWriter.Commit(); commitErr != nil {
panic(commitErr) // TODO: panic or c.Error() ?
}
} I am still testing this solution. So far it seems to work, but I am not sure if some mysterious intricacies are happening in the Echo framework or the Go stdlib that could backfire on me some weirdo bugs. Also, this solution uses a temporary buffer to witheld the response content. On large response (Megabytes+) this means an additional copy of such large buffer, which can hampers performances somewhat. Let me know if you try to implement something like this and how it goes for you. Cheers, |
Beta Was this translation helpful? Give feedback.
-
I'm trying to write an echo middleware that should act as a HTTP forward proxy. For this I'm leveraging some ideas of the
echo.Proxy()
middleware.It will be called somehow like:
However, I'm struggling to implement a retry mechanism. Let's say I have multiple upstreams and I wish to fail-over the request from one upstream to the other if the response is bad. Now I tried to somehow modify the context
Response()
but it seems to not be passed to the client. E.g.:In case the request to
upstreams[0].Host
is responded with a HTTP 404 and the request toupstreams[1].Host
with a HTTP 200, then the echo logger middleware would log the 200 response but the client still receives the HTTP 404.For those interested you can find the full code at https://github.com/ganto/pkgproxy/tree/feature/echo-2375.
Is there a way to "recreate" a
c.Response()
within anecho.HandlerFunc
? One would need to somehow "reset" theResponseWriter()
so that it allows to write another response.I already implemented some functionality to follow redirects which I did by implementing a custom
http.RoundTripper
in theproxyRequest
middleware that would send a new request if a redirect response is received. However, this is a layer below the echo framework and is super generic compared with the use case above.I personally would prefer if the request could actually be re-initiated in both cases (redirect and retry) by an echo middleware so there is the entire context available and it can also benefit from the middleware chaining. Is this possible?
Any ideas or hints?
Beta Was this translation helpful? Give feedback.
All reactions