package resilience import ( "errors" "io" "net/http" ) // Transport isolates one logical upstream. Create separate Breakers for // different dependencies. Slots remain occupied until body Close/EOF. type Transport struct { Base http.RoundTripper Breaker *Breaker } func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { if t.Breaker == nil { return nil, errors.New("resilience: breaker required") } done, err := t.Breaker.Acquire(r.Context()) if err != nil { return nil, err } defer func() { if v := recover(); v != nil { done(false) panic(v) } }() base := t.Base if base == nil { base = http.DefaultTransport } response, err := base.RoundTrip(r) if err != nil { done(!t.Breaker.config.IsFailure(err)) if response != nil && response.Body != nil { response.Body.Close() } return nil, err } if response == nil || response.Body == nil { done(false) return nil, errors.New("resilience: invalid transport response") } response.Body = &breakerBody{ReadCloser: response.Body, done: done, success: response.StatusCode < 500} return response, nil } type breakerBody struct { io.ReadCloser done func(bool) success bool } func (b *breakerBody) Read(p []byte) (int, error) { n, err := b.ReadCloser.Read(p) if err != nil { b.done(b.success && err == io.EOF) } return n, err } func (b *breakerBody) Close() error { err := b.ReadCloser.Close() b.done(b.success && err == nil) return err }