-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
182 lines (158 loc) · 5.17 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
171
172
173
174
175
176
177
178
179
180
181
182
package httpclient
import (
"context"
"errors"
"io"
"io/ioutil"
"log"
"net/http"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
const (
// DefaultDialTimeout for TCP dial
DefaultDialTimeout = 10 * time.Second
// DefaultTLSHandshakeTimeout for TLS handshake
DefaultTLSHandshakeTimeout = 10 * time.Second
// DefaultResponseHeaderTimeout for waiting to read a response header
DefaultResponseHeaderTimeout = 30 * time.Second
// DefaultMaxIdleConns to keep in pool
DefaultMaxIdleConns = 15
// DefaultKeepAliveTimeout - when socket keep alive check will be performed
DefaultKeepAliveTimeout = 90 * time.Second
// log-prefix
defaultLogPrefix = "[httpclient]: "
)
// ErrInvalidOptionValue is returned when an invalid value was given for some
// configuration option
var ErrInvalidOptionValue = errors.New("invalid value for option")
// Client is the primary Interface used to make HTTP requests. It implements
// common HTTP Verbs as methods and hides away the details used to configure
// the underlying *http.Client object
type Client interface {
// Client returns the underlying *http.Client
Client() *http.Client
// Close all Idle connections
Close()
// HTTP Request Methods //
// Do is the generic HTTP request method. The two string parameters in
// order are the HTTP Method, and the URL to request respectively
Do(ctx context.Context, rh ResponseHandler, method, url string, body io.Reader, opts ...RequestOption) error
// GET method
Get(ctx context.Context, rh ResponseHandler, url string, opts ...RequestOption) error
// POST method
Post(ctx context.Context, rh ResponseHandler, url string, body io.Reader, opts ...RequestOption) error
}
// ensure Client interface implementation
var _ Client = &client{}
// Client is our http client that can be used to make http requests efficiently
// for now we intend to support Get
// it is used rather than net/http package directly due to ability to configure
// timeouts and watch the resource use
// safe (and intended) to use from several go routines
type client struct {
client *http.Client
currentConnID int64
customRoundTripper http.RoundTripper
dialTimeout time.Duration
disableHTTP2 bool
disableKeepAlive bool
headers http.Header
idleConnTimeout time.Duration
keepAliveTimeout time.Duration
log *log.Logger
logPrefix string
logWriter io.Writer
maxIdleConns int
maxIdleConnsPerHost int
redirectFunc func(*http.Request, []*http.Request) error
responseHeaderTimeout time.Duration
tlsHandshakeTimeout time.Duration
transport *http.Transport
withTracing bool
}
// Client returns the *http.Client as it was initialized by the constructor
func (c *client) Client() *http.Client {
return c.client
}
// Close is a cleanup function that makes the client close any idle connections
func (c *client) Close() {
c.log.Printf("Close client %#v with all idle connections", c)
c.transport.CloseIdleConnections()
}
// New configures and returns a new instance of Client
func New(options ...Option) (Client, error) {
c := newDefaultClient()
if err := c.setOptions(options...); err != nil {
return nil, err
}
return c, c.init()
}
// return a new *client with default values
func newDefaultClient() *client {
c := new(client)
c.setDefaults()
return c
}
// set sensible default values on the *client
func (c *client) setDefaults() {
c.dialTimeout = DefaultDialTimeout
c.headers = make(http.Header)
c.keepAliveTimeout = DefaultKeepAliveTimeout
c.log = log.New(
ioutil.Discard,
c.logPrefix,
log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile|log.LUTC,
)
c.logPrefix = defaultLogPrefix
c.maxIdleConns = DefaultMaxIdleConns
c.maxIdleConnsPerHost = -1 //-1 means unset
c.redirectFunc = defaultRedirectPolicy
c.responseHeaderTimeout = DefaultResponseHeaderTimeout
c.tlsHandshakeTimeout = DefaultTLSHandshakeTimeout
}
// initialise the client
func (c *client) init() error {
// logger
if c.logWriter != nil {
c.log.SetOutput(c.logWriter)
}
if c.logPrefix != defaultLogPrefix {
c.log.SetPrefix(c.logPrefix)
}
// if per host is unset set it to same as maxIdleConns
if c.maxIdleConnsPerHost < 0 {
c.maxIdleConnsPerHost = c.maxIdleConns
}
// create transport
tr := &http.Transport{
DialContext: c.dialContext,
DisableCompression: false,
DisableKeepAlives: c.disableKeepAlive,
IdleConnTimeout: c.idleConnTimeout,
MaxIdleConns: c.maxIdleConns,
MaxIdleConnsPerHost: c.maxIdleConnsPerHost,
ResponseHeaderTimeout: c.responseHeaderTimeout,
TLSHandshakeTimeout: c.tlsHandshakeTimeout,
ForceAttemptHTTP2: !c.disableHTTP2,
}
c.transport = tr
if c.customRoundTripper == nil {
c.customRoundTripper = tr
}
if c.withTracing {
c.customRoundTripper = otelhttp.NewTransport(c.customRoundTripper)
}
c.log.Printf("initialized transport: %#v\n", tr)
// create client
client := &http.Client{
Transport: c.customRoundTripper,
}
// set redirect func
if c.redirectFunc != nil {
client.CheckRedirect = c.redirectFunc
}
c.client = client
c.log.Printf("initialized *http.Client: %#v\n", c.client)
return nil
}