[go-nuts] Re: Writing correct response writer wrappers
If you use struct embedding I think you may get what you are looking for: type retryResponseWriter struct { http.ResponseWriter attemptsExhausted bool } Now retryResponseWriter has all of the methods of the http.ResponseWriter you assign to it and can be cast to those http interfaces without having to define stub methods or separate types. Also since http.Handler is an interface I don’t think you need a pointer to it in type retry. Matt On Wednesday, January 17, 2018 at 2:57:22 AM UTC-6, Marco Jantke wrote: > > Thanks for the response Matt. Some examples to explain my use-case better. > First of all, it's important to know that the proxy I am working on uses > HTTP handler wrapper to provide additional functionality. The retry handler > + response writer wrapper look like the following (code examples are > simplified): > > > type retry struct { > next *http.Handler > } > > func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) { > attempts := 1 > for { > retryResponseWriter := newRetryResponseWriter(rw, attempts >= > retry.attempts, ...) > > retry.next.ServeHTTP(retryResponseWriter, r.WithContext(newCtx)) > if !retryResponseWriter.ShouldRetry() { > break > } > > attempts++ > log.Debugf("New attempt %d for request: %v", attempts, r.URL) > retry.listener.Retried(r, attempts) > } > } > > type retryResponseWriter struct { > responseWriterhttp.ResponseWriter > attemptsExhausted bool > ... > } > > // Only responses that should not be retried > // should be written into the original response writer. > func (rr *retryResponseWriter) ShouldRetry() bool { > return netErrorOccured() && !rr.attemptsExhausted > } > > func (rr *retryResponseWriter) Header() http.Header { > if rr.ShouldRetry() { > return make(http.Header) > } > return rr.responseWriter.Header() > } > > func (rr *retryResponseWriter) Write(buf []byte) (int, error) { > if rr.ShouldRetry() { > return 0, nil > } > return rr.responseWriter.Write(buf) > } > > func (rr *retryResponseWriter) WriteHeader(code int) { > if rr.ShouldRetry() { > return > } > rr.responseWriter.WriteHeader(code) > } > > func (rr *retryResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, > error) { > return rr.responseWriter.(http.Hijacker).Hijack() > } > > func (rr *retryResponseWriter) CloseNotify() <-chan bool { > return rr.responseWriter.(http.CloseNotifier).CloseNotify() > } > > func (rr *retryResponseWriter) Flush() { > if flusher, ok := rr.responseWriter.(http.Flusher); ok { > flusher.Flush() > } > -} > > My problem is now that the retryResponseWriter always implements the > interfaces like Flusher or Hijacker. But actually what I want is that it > has the same capabilities as the http.ResponseWriter I am wrapping. An > idiomatic usage, to my understanding, of a response writer would look like > the following: > > if hijacker, ok := responseWriter.(Hijacker); ok { > conn, rw, err := hijacker.Hijack() > if err != nil { > // error handling, I even saw sometimes panic() used in this cases > } > // handle connections > } > > So implementing the interface is definitely something different than > returning stub values. However, after having a closer look it looks like > that it's safe to implement stub methods for Flush() (as a no-op) and > CloseNotify() (returning a new closed channel). If that assumption is right > I would only have to create two different retryResponseWriter types, one > with Hijack and one without and do a type assertion on the > http.ResponseWriter to choose which one. I would favor a more generic way > in case someone has a better idea, though. > > > On Tuesday, 16 January 2018 16:10:16 UTC+1, matthe...@gmail.com wrote: >> >> The type switch may not work since these http types are interfaces that >> may all be satisfied by the ResponseWriter. Instead of a type switch you >> would need to do a type assertion for each possible http interface. >> >> Matt >> >> On Tuesday, January 16, 2018 at 9:01:28 AM UTC-6, matthe...@gmail.com >> wrote: >>> >>> Can you provide some example code showing the problem? >>> >>> I don’t understand why you are wrapping the ResponseWriter. If you need >>> to pass along metadata then wrapping makes sense, but why not just pass the >>> original interface and do the type switch in each handler? >>> >>> type RetryResponseWriter struct { >>> http.ResponseWriter >>> Count uint >>> … >>> } >>> >>> func handler1(w http.ResponseWriter, r *http.Request) { >>> rrw := w.(RetryResponseWriter) >>> switch v := rrw.ResponseWriter.(type) { >>> case http.Hijacker: >>> … >>> case http.Flusher: >>> … >>> case http.CloseNotifier: >>> … >>> } >>> … >>> >>> Matt >>> >>> On Tuesday, January 16, 2018 at 7:53:40 AM UTC-6, Marco Jantke wrote: Hi everyone, I am currently working on a retry middleware for an http proxy. It is wrapping the original response writer and only in case the forwarded
[go-nuts] Re: Writing correct response writer wrappers
Thanks for the response Matt. Some examples to explain my use-case better. First of all, it's important to know that the proxy I am working on uses HTTP handler wrapper to provide additional functionality. The retry handler + response writer wrapper look like the following (code examples are simplified): type retry struct { next *http.Handler } func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) { attempts := 1 for { retryResponseWriter := newRetryResponseWriter(rw, attempts >= retry.attempts, ...) retry.next.ServeHTTP(retryResponseWriter, r.WithContext(newCtx)) if !retryResponseWriter.ShouldRetry() { break } attempts++ log.Debugf("New attempt %d for request: %v", attempts, r.URL) retry.listener.Retried(r, attempts) } } type retryResponseWriter struct { responseWriterhttp.ResponseWriter attemptsExhausted bool ... } // Only responses that should not be retried // should be written into the original response writer. func (rr *retryResponseWriter) ShouldRetry() bool { return netErrorOccured() && !rr.attemptsExhausted } func (rr *retryResponseWriter) Header() http.Header { if rr.ShouldRetry() { return make(http.Header) } return rr.responseWriter.Header() } func (rr *retryResponseWriter) Write(buf []byte) (int, error) { if rr.ShouldRetry() { return 0, nil } return rr.responseWriter.Write(buf) } func (rr *retryResponseWriter) WriteHeader(code int) { if rr.ShouldRetry() { return } rr.responseWriter.WriteHeader(code) } func (rr *retryResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { return rr.responseWriter.(http.Hijacker).Hijack() } func (rr *retryResponseWriter) CloseNotify() <-chan bool { return rr.responseWriter.(http.CloseNotifier).CloseNotify() } func (rr *retryResponseWriter) Flush() { if flusher, ok := rr.responseWriter.(http.Flusher); ok { flusher.Flush() } -} My problem is now that the retryResponseWriter always implements the interfaces like Flusher or Hijacker. But actually what I want is that it has the same capabilities as the http.ResponseWriter I am wrapping. An idiomatic usage, to my understanding, of a response writer would look like the following: if hijacker, ok := responseWriter.(Hijacker); ok { conn, rw, err := hijacker.Hijack() if err != nil { // error handling, I even saw sometimes panic() used in this cases } // handle connections } So implementing the interface is definitely something different than returning stub values. However, after having a closer look it looks like that it's safe to implement stub methods for Flush() (as a no-op) and CloseNotify() (returning a new closed channel). If that assumption is right I would only have to create two different retryResponseWriter types, one with Hijack and one without and do a type assertion on the http.ResponseWriter to choose which one. I would favor a more generic way in case someone has a better idea, though. On Tuesday, 16 January 2018 16:10:16 UTC+1, matthe...@gmail.com wrote: > > The type switch may not work since these http types are interfaces that > may all be satisfied by the ResponseWriter. Instead of a type switch you > would need to do a type assertion for each possible http interface. > > Matt > > On Tuesday, January 16, 2018 at 9:01:28 AM UTC-6, matthe...@gmail.com > wrote: >> >> Can you provide some example code showing the problem? >> >> I don’t understand why you are wrapping the ResponseWriter. If you need >> to pass along metadata then wrapping makes sense, but why not just pass the >> original interface and do the type switch in each handler? >> >> type RetryResponseWriter struct { >> http.ResponseWriter >> Count uint >> … >> } >> >> func handler1(w http.ResponseWriter, r *http.Request) { >> rrw := w.(RetryResponseWriter) >> switch v := rrw.ResponseWriter.(type) { >> case http.Hijacker: >> … >> case http.Flusher: >> … >> case http.CloseNotifier: >> … >> } >> … >> >> Matt >> >> On Tuesday, January 16, 2018 at 7:53:40 AM UTC-6, Marco Jantke wrote: >>> >>> Hi everyone, >>> >>> I am currently working on a retry middleware for an http proxy. It is >>> wrapping the original response writer and only in case the forwarded >>> request should not be retried, it passes calls to `Write()`, >>> `WriteHeader()` etc directly through to the original response writer. Now I >>> am struggling with the fact, that response writers have different >>> capabilities dependent on their type. Namely they can be `Hijacker`, >>> `Flusher` or `CloseNotifier`. The problem is that my wrapper should have >>> the same capabilities as the wrapped one, but I don't see any other way >>> than creating all different types and using the proper one after doing type >>> assertions on the response writer I am wrapping. Those types would be for >>> example: `ResponseWriterWrapper`, `ResponseWriterWrapperWithFlush`, >>> `ResponseWriterWrapperWithHijack`, >>>
[go-nuts] Re: Writing correct response writer wrappers
The type switch may not work since these http types are interfaces that may all be satisfied by the ResponseWriter. Instead of a type switch you would need to do a type assertion for each possible http interface. Matt On Tuesday, January 16, 2018 at 9:01:28 AM UTC-6, matthe...@gmail.com wrote: > > Can you provide some example code showing the problem? > > I don’t understand why you are wrapping the ResponseWriter. If you need to > pass along metadata then wrapping makes sense, but why not just pass the > original interface and do the type switch in each handler? > > type RetryResponseWriter struct { > http.ResponseWriter > Count uint > … > } > > func handler1(w http.ResponseWriter, r *http.Request) { > rrw := w.(RetryResponseWriter) > switch v := rrw.ResponseWriter.(type) { > case http.Hijacker: > … > case http.Flusher: > … > case http.CloseNotifier: > … > } > … > > Matt > > On Tuesday, January 16, 2018 at 7:53:40 AM UTC-6, Marco Jantke wrote: >> >> Hi everyone, >> >> I am currently working on a retry middleware for an http proxy. It is >> wrapping the original response writer and only in case the forwarded >> request should not be retried, it passes calls to `Write()`, >> `WriteHeader()` etc directly through to the original response writer. Now I >> am struggling with the fact, that response writers have different >> capabilities dependent on their type. Namely they can be `Hijacker`, >> `Flusher` or `CloseNotifier`. The problem is that my wrapper should have >> the same capabilities as the wrapped one, but I don't see any other way >> than creating all different types and using the proper one after doing type >> assertions on the response writer I am wrapping. Those types would be for >> example: `ResponseWriterWrapper`, `ResponseWriterWrapperWithFlush`, >> `ResponseWriterWrapperWithHijack`, >> `ResponseWriterWrapperWithHijackAndFlush` >> >> >> Does anyone have an idea how to approach this problem in a more >> reasonable fashion? >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
[go-nuts] Re: Writing correct response writer wrappers
Can you provide some example code showing the problem? I don’t understand why you are wrapping the ResponseWriter. If you need to pass along metadata then wrapping makes sense, but why not just pass the original interface and do the type switch in each handler? type RetryResponseWriter struct { http.ResponseWriter Count uint … } func handler1(w http.ResponseWriter, r *http.Request) { rrw := w.(RetryResponseWriter) switch v := rrw.ResponseWriter.(type) { case http.Hijacker: … case http.Flusher: … case http.CloseNotifier: … } … Matt On Tuesday, January 16, 2018 at 7:53:40 AM UTC-6, Marco Jantke wrote: > > Hi everyone, > > I am currently working on a retry middleware for an http proxy. It is > wrapping the original response writer and only in case the forwarded > request should not be retried, it passes calls to `Write()`, > `WriteHeader()` etc directly through to the original response writer. Now I > am struggling with the fact, that response writers have different > capabilities dependent on their type. Namely they can be `Hijacker`, > `Flusher` or `CloseNotifier`. The problem is that my wrapper should have > the same capabilities as the wrapped one, but I don't see any other way > than creating all different types and using the proper one after doing type > assertions on the response writer I am wrapping. Those types would be for > example: `ResponseWriterWrapper`, `ResponseWriterWrapperWithFlush`, > `ResponseWriterWrapperWithHijack`, > `ResponseWriterWrapperWithHijackAndFlush` > > > Does anyone have an idea how to approach this problem in a more reasonable > fashion? > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.