Files
go-omni/resilience/http.go

68 lines
1.4 KiB
Go

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
}