Send 'Retry-After' to comply with RFC6585.
This commit is contained in:
parent
027093a5a5
commit
8d75aba7eb
29 changed files with 435 additions and 172 deletions
40
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
40
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
|
@ -3,7 +3,7 @@
|
|||
// Vulcan circuit breaker watches the error condtion to match
|
||||
// after which it activates the fallback scenario, e.g. returns the response code
|
||||
// or redirects the request to another location
|
||||
|
||||
//
|
||||
// Circuit breakers start in the Standby state first, observing responses and watching location metrics.
|
||||
//
|
||||
// Once the Circuit breaker condition is met, it enters the "Tripped" state, where it activates fallback scenario
|
||||
|
@ -31,9 +31,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/mailgun/timetools"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vulcand/oxy/memmetrics"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
@ -63,6 +62,8 @@ type CircuitBreaker struct {
|
|||
next http.Handler
|
||||
|
||||
clock timetools.TimeProvider
|
||||
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
// New creates a new CircuitBreaker middleware
|
||||
|
@ -76,6 +77,7 @@ func New(next http.Handler, expression string, options ...CircuitBreakerOption)
|
|||
fallbackDuration: defaultFallbackDuration,
|
||||
recoveryDuration: defaultRecoveryDuration,
|
||||
fallback: defaultFallback,
|
||||
log: log.StandardLogger(),
|
||||
}
|
||||
|
||||
for _, s := range options {
|
||||
|
@ -99,9 +101,19 @@ func New(next http.Handler, expression string, options ...CircuitBreakerOption)
|
|||
return cb, nil
|
||||
}
|
||||
|
||||
// Logger defines the logger the circuit breaker will use.
|
||||
//
|
||||
// It defaults to logrus.StandardLogger(), the global logger used by logrus.
|
||||
func Logger(l *log.Logger) CircuitBreakerOption {
|
||||
return func(c *CircuitBreaker) error {
|
||||
c.log = l
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if log.GetLevel() >= log.DebugLevel {
|
||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
if c.log.Level >= log.DebugLevel {
|
||||
logEntry := c.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
logEntry.Debug("vulcand/oxy/circuitbreaker: begin ServeHttp on request")
|
||||
defer logEntry.Debug("vulcand/oxy/circuitbreaker: completed ServeHttp on request")
|
||||
}
|
||||
|
@ -112,6 +124,7 @@ func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
c.serve(w, req)
|
||||
}
|
||||
|
||||
// Wrap sets the next handler to be called by circuit breaker handler.
|
||||
func (c *CircuitBreaker) Wrap(next http.Handler) {
|
||||
c.next = next
|
||||
}
|
||||
|
@ -126,7 +139,7 @@ func (c *CircuitBreaker) activateFallback(w http.ResponseWriter, req *http.Reque
|
|||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
log.Warnf("%v is in error state", c)
|
||||
c.log.Warnf("%v is in error state", c)
|
||||
|
||||
switch c.state {
|
||||
case stateStandby:
|
||||
|
@ -156,7 +169,7 @@ func (c *CircuitBreaker) activateFallback(w http.ResponseWriter, req *http.Reque
|
|||
|
||||
func (c *CircuitBreaker) serve(w http.ResponseWriter, req *http.Request) {
|
||||
start := c.clock.UtcNow()
|
||||
p := utils.NewProxyWriter(w)
|
||||
p := utils.NewProxyWriterWithLogger(w, c.log)
|
||||
|
||||
c.next.ServeHTTP(p, req)
|
||||
|
||||
|
@ -191,13 +204,13 @@ func (c *CircuitBreaker) exec(s SideEffect) {
|
|||
}
|
||||
go func() {
|
||||
if err := s.Exec(); err != nil {
|
||||
log.Errorf("%v side effect failure: %v", c, err)
|
||||
c.log.Errorf("%v side effect failure: %v", c, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *CircuitBreaker) setState(new cbState, until time.Time) {
|
||||
log.Debugf("%v setting state to %v, until %v", c, new, until)
|
||||
c.log.Debugf("%v setting state to %v, until %v", c, new, until)
|
||||
c.state = new
|
||||
c.until = until
|
||||
switch new {
|
||||
|
@ -230,7 +243,7 @@ func (c *CircuitBreaker) checkAndSet() {
|
|||
c.lastCheck = c.clock.UtcNow().Add(c.checkPeriod)
|
||||
|
||||
if c.state == stateTripped {
|
||||
log.Debugf("%v skip set tripped", c)
|
||||
c.log.Debugf("%v skip set tripped", c)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -244,7 +257,7 @@ func (c *CircuitBreaker) checkAndSet() {
|
|||
|
||||
func (c *CircuitBreaker) setRecovering() {
|
||||
c.setState(stateRecovering, c.clock.UtcNow().Add(c.recoveryDuration))
|
||||
c.rc = newRatioController(c.clock, c.recoveryDuration)
|
||||
c.rc = newRatioController(c.clock, c.recoveryDuration, c.log)
|
||||
}
|
||||
|
||||
// CircuitBreakerOption represents an option you can pass to New.
|
||||
|
@ -296,7 +309,7 @@ func OnTripped(s SideEffect) CircuitBreakerOption {
|
|||
}
|
||||
}
|
||||
|
||||
// OnTripped sets a SideEffect to run when entering the Standby state.
|
||||
// OnStandby sets a SideEffect to run when entering the Standby state.
|
||||
// Only one SideEffect can be set for this hook.
|
||||
func OnStandby(s SideEffect) CircuitBreakerOption {
|
||||
return func(c *CircuitBreaker) error {
|
||||
|
@ -346,8 +359,7 @@ const (
|
|||
|
||||
var defaultFallback = &fallback{}
|
||||
|
||||
type fallback struct {
|
||||
}
|
||||
type fallback struct{}
|
||||
|
||||
func (f *fallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
|
18
vendor/github.com/vulcand/oxy/cbreaker/effect.go
generated
vendored
18
vendor/github.com/vulcand/oxy/cbreaker/effect.go
generated
vendored
|
@ -13,10 +13,12 @@ import (
|
|||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// SideEffect a side effect
|
||||
type SideEffect interface {
|
||||
Exec() error
|
||||
}
|
||||
|
||||
// Webhook Web hook
|
||||
type Webhook struct {
|
||||
URL string
|
||||
Method string
|
||||
|
@ -25,11 +27,15 @@ type Webhook struct {
|
|||
Body []byte
|
||||
}
|
||||
|
||||
// WebhookSideEffect a web hook side effect
|
||||
type WebhookSideEffect struct {
|
||||
w Webhook
|
||||
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func NewWebhookSideEffect(w Webhook) (*WebhookSideEffect, error) {
|
||||
// NewWebhookSideEffectsWithLogger creates a new WebhookSideEffect
|
||||
func NewWebhookSideEffectsWithLogger(w Webhook, l *log.Logger) (*WebhookSideEffect, error) {
|
||||
if w.Method == "" {
|
||||
return nil, fmt.Errorf("Supply method")
|
||||
}
|
||||
|
@ -38,7 +44,12 @@ func NewWebhookSideEffect(w Webhook) (*WebhookSideEffect, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &WebhookSideEffect{w: w}, nil
|
||||
return &WebhookSideEffect{w: w, log: l}, nil
|
||||
}
|
||||
|
||||
// NewWebhookSideEffect creates a new WebhookSideEffect
|
||||
func NewWebhookSideEffect(w Webhook) (*WebhookSideEffect, error) {
|
||||
return NewWebhookSideEffectsWithLogger(w, log.StandardLogger())
|
||||
}
|
||||
|
||||
func (w *WebhookSideEffect) getBody() io.Reader {
|
||||
|
@ -51,6 +62,7 @@ func (w *WebhookSideEffect) getBody() io.Reader {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Exec execute the side effect
|
||||
func (w *WebhookSideEffect) Exec() error {
|
||||
r, err := http.NewRequest(w.w.Method, w.w.URL, w.getBody())
|
||||
if err != nil {
|
||||
|
@ -73,6 +85,6 @@ func (w *WebhookSideEffect) Exec() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%v got response: (%s): %s", w, re.Status, string(body))
|
||||
w.log.Debugf("%v got response: (%s): %s", w, re.Status, string(body))
|
||||
return nil
|
||||
}
|
||||
|
|
39
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
39
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
|
@ -10,26 +10,36 @@ import (
|
|||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
// Response response model
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
ContentType string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// ResponseFallback fallback response handler
|
||||
type ResponseFallback struct {
|
||||
r Response
|
||||
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func NewResponseFallback(r Response) (*ResponseFallback, error) {
|
||||
// NewResponseFallbackWithLogger creates a new ResponseFallback
|
||||
func NewResponseFallbackWithLogger(r Response, l *log.Logger) (*ResponseFallback, error) {
|
||||
if r.StatusCode == 0 {
|
||||
return nil, fmt.Errorf("response code should not be 0")
|
||||
}
|
||||
return &ResponseFallback{r: r}, nil
|
||||
return &ResponseFallback{r: r, log: l}, nil
|
||||
}
|
||||
|
||||
// NewResponseFallback creates a new ResponseFallback
|
||||
func NewResponseFallback(r Response) (*ResponseFallback, error) {
|
||||
return NewResponseFallbackWithLogger(r, log.StandardLogger())
|
||||
}
|
||||
|
||||
func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if log.GetLevel() >= log.DebugLevel {
|
||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
if f.log.Level >= log.DebugLevel {
|
||||
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
logEntry.Debug("vulcand/oxy/fallback/response: begin ServeHttp on request")
|
||||
defer logEntry.Debug("vulcand/oxy/fallback/response: completed ServeHttp on request")
|
||||
}
|
||||
|
@ -45,27 +55,38 @@ func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// Redirect redirect model
|
||||
type Redirect struct {
|
||||
URL string
|
||||
PreservePath bool
|
||||
}
|
||||
|
||||
// RedirectFallback fallback redirect handler
|
||||
type RedirectFallback struct {
|
||||
u *url.URL
|
||||
r Redirect
|
||||
|
||||
u *url.URL
|
||||
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func NewRedirectFallback(r Redirect) (*RedirectFallback, error) {
|
||||
// NewRedirectFallbackWithLogger creates a new RedirectFallback
|
||||
func NewRedirectFallbackWithLogger(r Redirect, l *log.Logger) (*RedirectFallback, error) {
|
||||
u, err := url.ParseRequestURI(r.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RedirectFallback{u: u, r: r}, nil
|
||||
return &RedirectFallback{r: r, u: u, log: l}, nil
|
||||
}
|
||||
|
||||
// NewRedirectFallback creates a new RedirectFallback
|
||||
func NewRedirectFallback(r Redirect) (*RedirectFallback, error) {
|
||||
return NewRedirectFallbackWithLogger(r, log.StandardLogger())
|
||||
}
|
||||
|
||||
func (f *RedirectFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if log.GetLevel() >= log.DebugLevel {
|
||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
if f.log.Level >= log.DebugLevel {
|
||||
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||
logEntry.Debug("vulcand/oxy/fallback/redirect: begin ServeHttp on request")
|
||||
defer logEntry.Debug("vulcand/oxy/fallback/redirect: completed ServeHttp on request")
|
||||
}
|
||||
|
|
3
vendor/github.com/vulcand/oxy/cbreaker/predicates.go
generated
vendored
3
vendor/github.com/vulcand/oxy/cbreaker/predicates.go
generated
vendored
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/vulcand/predicate"
|
||||
)
|
||||
|
||||
|
@ -50,7 +49,7 @@ func latencyAtQuantile(quantile float64) toInt {
|
|||
return func(c *CircuitBreaker) int {
|
||||
h, err := c.metrics.LatencyHistogram()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get latency histogram, for %v error: %v", c, err)
|
||||
c.log.Errorf("Failed to get latency histogram, for %v error: %v", c, err)
|
||||
return 0
|
||||
}
|
||||
return int(h.LatencyAtQuantile(quantile) / time.Millisecond)
|
||||
|
|
12
vendor/github.com/vulcand/oxy/cbreaker/ratio.go
generated
vendored
12
vendor/github.com/vulcand/oxy/cbreaker/ratio.go
generated
vendored
|
@ -19,13 +19,17 @@ type ratioController struct {
|
|||
tm timetools.TimeProvider
|
||||
allowed int
|
||||
denied int
|
||||
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func newRatioController(tm timetools.TimeProvider, rampUp time.Duration) *ratioController {
|
||||
func newRatioController(tm timetools.TimeProvider, rampUp time.Duration, log *log.Logger) *ratioController {
|
||||
return &ratioController{
|
||||
duration: rampUp,
|
||||
tm: tm,
|
||||
start: tm.UtcNow(),
|
||||
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,17 +38,17 @@ func (r *ratioController) String() string {
|
|||
}
|
||||
|
||||
func (r *ratioController) allowRequest() bool {
|
||||
log.Debugf("%v", r)
|
||||
r.log.Debugf("%v", r)
|
||||
t := r.targetRatio()
|
||||
// This condition answers the question - would we satisfy the target ratio if we allow this request?
|
||||
e := r.computeRatio(r.allowed+1, r.denied)
|
||||
if e < t {
|
||||
r.allowed++
|
||||
log.Debugf("%v allowed", r)
|
||||
r.log.Debugf("%v allowed", r)
|
||||
return true
|
||||
}
|
||||
r.denied++
|
||||
log.Debugf("%v denied", r)
|
||||
r.log.Debugf("%v denied", r)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue