forked from rubyist/circuitbreaker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
170 lines (148 loc) · 4.52 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package circuit
import (
"io"
"net/http"
"net/url"
"time"
)
// HTTPClient is a wrapper around http.Client that provides circuit breaker capabilities.
//
// By default, the client will use its defaultBreaker. A BreakerLookup function may be
// provided to allow different breakers to be used based on the circumstance. See the
// implementation of NewHostBasedHTTPClient for an example of this.
type HTTPClient struct {
Client *http.Client
BreakerTripped func()
BreakerReset func()
BreakerLookup func(*HTTPClient, interface{}) *Breaker
Panel *Panel
timeout time.Duration
}
var defaultBreakerName = "_default"
// NewHTTPClient provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions. Specifying 0 for timeout will
// give a breaker that does not check for time outs.
func NewHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
breaker := NewThresholdBreaker(threshold)
return NewHTTPClientWithBreaker(breaker, timeout, client)
}
// NewHostBasedHTTPClient provides a circuit breaker wrapper around http.Client. This
// client will use one circuit breaker per host parsed from the request URL. This allows
// you to use a single HTTPClient for multiple hosts with one host's breaker not affecting
// the other hosts.
func NewHostBasedHTTPClient(timeout time.Duration, threshold int64, client *http.Client) *HTTPClient {
brclient := NewHTTPClient(timeout, threshold, client)
brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
rawURL := val.(string)
parsedURL, err := url.Parse(rawURL)
if err != nil {
breaker, _ := c.Panel.Get(defaultBreakerName)
return breaker
}
host := parsedURL.Host
cb, ok := c.Panel.Get(host)
if !ok {
cb = NewThresholdBreaker(threshold)
c.Panel.Add(host, cb)
}
return cb
}
return brclient
}
// NewHTTPClientWithBreaker provides a circuit breaker wrapper around http.Client.
// It wraps all of the regular http.Client functions using the provided Breaker.
func NewHTTPClientWithBreaker(breaker *Breaker, timeout time.Duration, client *http.Client) *HTTPClient {
if client == nil {
client = &http.Client{}
}
panel := NewPanel()
panel.Add(defaultBreakerName, breaker)
brclient := &HTTPClient{Client: client, Panel: panel, timeout: timeout}
brclient.BreakerLookup = func(c *HTTPClient, val interface{}) *Breaker {
cb, _ := c.Panel.Get(defaultBreakerName)
return cb
}
events := breaker.Subscribe()
go func() {
event := <-events
switch event {
case BreakerTripped:
brclient.runBreakerTripped()
case BreakerReset:
brclient.runBreakerReset()
}
}()
return brclient
}
// Do wraps http.Client Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
breaker := c.breakerLookup(req.URL.String())
err = breaker.Call(func() error {
resp, err = c.Client.Do(req)
return err
}, c.timeout)
return resp, err
}
// Get wraps http.Client Get()
func (c *HTTPClient) Get(url string) (*http.Response, error) {
var resp *http.Response
breaker := c.breakerLookup(url)
err := breaker.Call(func() error {
aresp, err := c.Client.Get(url)
resp = aresp
return err
}, c.timeout)
return resp, err
}
// Head wraps http.Client Head()
func (c *HTTPClient) Head(url string) (*http.Response, error) {
var resp *http.Response
breaker := c.breakerLookup(url)
err := breaker.Call(func() error {
aresp, err := c.Client.Head(url)
resp = aresp
return err
}, c.timeout)
return resp, err
}
// Post wraps http.Client Post()
func (c *HTTPClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
var resp *http.Response
breaker := c.breakerLookup(url)
err := breaker.Call(func() error {
aresp, err := c.Client.Post(url, bodyType, body)
resp = aresp
return err
}, c.timeout)
return resp, err
}
// PostForm wraps http.Client PostForm()
func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
var resp *http.Response
breaker := c.breakerLookup(url)
err := breaker.Call(func() error {
aresp, err := c.Client.PostForm(url, data)
resp = aresp
return err
}, c.timeout)
return resp, err
}
func (c *HTTPClient) breakerLookup(val interface{}) *Breaker {
if c.BreakerLookup != nil {
return c.BreakerLookup(c, val)
}
cb, _ := c.Panel.Get(defaultBreakerName)
return cb
}
func (c *HTTPClient) runBreakerTripped() {
if c.BreakerTripped != nil {
c.BreakerTripped()
}
}
func (c *HTTPClient) runBreakerReset() {
if c.BreakerReset != nil {
c.BreakerReset()
}
}