source: code/trunk/vendor/github.com/valyala/fasthttp/tcpdialer.go@ 145

Last change on this file since 145 was 145, checked in by Izuru Yakumo, 22 months ago

Updated the Makefile and vendored depedencies

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 13.3 KB
Line 
1package fasthttp
2
3import (
4 "context"
5 "errors"
6 "net"
7 "strconv"
8 "sync"
9 "sync/atomic"
10 "time"
11)
12
13// Dial dials the given TCP addr using tcp4.
14//
15// This function has the following additional features comparing to net.Dial:
16//
17// * It reduces load on DNS resolver by caching resolved TCP addressed
18// for DNSCacheDuration.
19// * It dials all the resolved TCP addresses in round-robin manner until
20// connection is established. This may be useful if certain addresses
21// are temporarily unreachable.
22// * It returns ErrDialTimeout if connection cannot be established during
23// DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
24//
25// This dialer is intended for custom code wrapping before passing
26// to Client.Dial or HostClient.Dial.
27//
28// For instance, per-host counters and/or limits may be implemented
29// by such wrappers.
30//
31// The addr passed to the function must contain port. Example addr values:
32//
33// * foobar.baz:443
34// * foo.bar:80
35// * aaa.com:8080
36func Dial(addr string) (net.Conn, error) {
37 return defaultDialer.Dial(addr)
38}
39
40// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
41//
42// This function has the following additional features comparing to net.Dial:
43//
44// * It reduces load on DNS resolver by caching resolved TCP addressed
45// for DNSCacheDuration.
46// * It dials all the resolved TCP addresses in round-robin manner until
47// connection is established. This may be useful if certain addresses
48// are temporarily unreachable.
49//
50// This dialer is intended for custom code wrapping before passing
51// to Client.Dial or HostClient.Dial.
52//
53// For instance, per-host counters and/or limits may be implemented
54// by such wrappers.
55//
56// The addr passed to the function must contain port. Example addr values:
57//
58// * foobar.baz:443
59// * foo.bar:80
60// * aaa.com:8080
61func DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
62 return defaultDialer.DialTimeout(addr, timeout)
63}
64
65// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
66//
67// This function has the following additional features comparing to net.Dial:
68//
69// * It reduces load on DNS resolver by caching resolved TCP addressed
70// for DNSCacheDuration.
71// * It dials all the resolved TCP addresses in round-robin manner until
72// connection is established. This may be useful if certain addresses
73// are temporarily unreachable.
74// * It returns ErrDialTimeout if connection cannot be established during
75// DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
76// timeout.
77//
78// This dialer is intended for custom code wrapping before passing
79// to Client.Dial or HostClient.Dial.
80//
81// For instance, per-host counters and/or limits may be implemented
82// by such wrappers.
83//
84// The addr passed to the function must contain port. Example addr values:
85//
86// * foobar.baz:443
87// * foo.bar:80
88// * aaa.com:8080
89func DialDualStack(addr string) (net.Conn, error) {
90 return defaultDialer.DialDualStack(addr)
91}
92
93// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
94// using the given timeout.
95//
96// This function has the following additional features comparing to net.Dial:
97//
98// * It reduces load on DNS resolver by caching resolved TCP addressed
99// for DNSCacheDuration.
100// * It dials all the resolved TCP addresses in round-robin manner until
101// connection is established. This may be useful if certain addresses
102// are temporarily unreachable.
103//
104// This dialer is intended for custom code wrapping before passing
105// to Client.Dial or HostClient.Dial.
106//
107// For instance, per-host counters and/or limits may be implemented
108// by such wrappers.
109//
110// The addr passed to the function must contain port. Example addr values:
111//
112// * foobar.baz:443
113// * foo.bar:80
114// * aaa.com:8080
115func DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {
116 return defaultDialer.DialDualStackTimeout(addr, timeout)
117}
118
119var (
120 defaultDialer = &TCPDialer{Concurrency: 1000}
121)
122
123// Resolver represents interface of the tcp resolver.
124type Resolver interface {
125 LookupIPAddr(context.Context, string) (names []net.IPAddr, err error)
126}
127
128// TCPDialer contains options to control a group of Dial calls.
129type TCPDialer struct {
130 // Concurrency controls the maximum number of concurrent Dials
131 // that can be performed using this object.
132 // Setting this to 0 means unlimited.
133 //
134 // WARNING: This can only be changed before the first Dial.
135 // Changes made after the first Dial will not affect anything.
136 Concurrency int
137
138 // LocalAddr is the local address to use when dialing an
139 // address.
140 // If nil, a local address is automatically chosen.
141 LocalAddr *net.TCPAddr
142
143 // This may be used to override DNS resolving policy, like this:
144 // var dialer = &fasthttp.TCPDialer{
145 // Resolver: &net.Resolver{
146 // PreferGo: true,
147 // StrictErrors: false,
148 // Dial: func (ctx context.Context, network, address string) (net.Conn, error) {
149 // d := net.Dialer{}
150 // return d.DialContext(ctx, "udp", "8.8.8.8:53")
151 // },
152 // },
153 // }
154 Resolver Resolver
155
156 // DNSCacheDuration may be used to override the default DNS cache duration (DefaultDNSCacheDuration)
157 DNSCacheDuration time.Duration
158
159 tcpAddrsMap sync.Map
160
161 concurrencyCh chan struct{}
162
163 once sync.Once
164}
165
166// Dial dials the given TCP addr using tcp4.
167//
168// This function has the following additional features comparing to net.Dial:
169//
170// * It reduces load on DNS resolver by caching resolved TCP addressed
171// for DNSCacheDuration.
172// * It dials all the resolved TCP addresses in round-robin manner until
173// connection is established. This may be useful if certain addresses
174// are temporarily unreachable.
175// * It returns ErrDialTimeout if connection cannot be established during
176// DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
177//
178// This dialer is intended for custom code wrapping before passing
179// to Client.Dial or HostClient.Dial.
180//
181// For instance, per-host counters and/or limits may be implemented
182// by such wrappers.
183//
184// The addr passed to the function must contain port. Example addr values:
185//
186// * foobar.baz:443
187// * foo.bar:80
188// * aaa.com:8080
189func (d *TCPDialer) Dial(addr string) (net.Conn, error) {
190 return d.dial(addr, false, DefaultDialTimeout)
191}
192
193// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
194//
195// This function has the following additional features comparing to net.Dial:
196//
197// * It reduces load on DNS resolver by caching resolved TCP addressed
198// for DNSCacheDuration.
199// * It dials all the resolved TCP addresses in round-robin manner until
200// connection is established. This may be useful if certain addresses
201// are temporarily unreachable.
202//
203// This dialer is intended for custom code wrapping before passing
204// to Client.Dial or HostClient.Dial.
205//
206// For instance, per-host counters and/or limits may be implemented
207// by such wrappers.
208//
209// The addr passed to the function must contain port. Example addr values:
210//
211// * foobar.baz:443
212// * foo.bar:80
213// * aaa.com:8080
214func (d *TCPDialer) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
215 return d.dial(addr, false, timeout)
216}
217
218// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
219//
220// This function has the following additional features comparing to net.Dial:
221//
222// * It reduces load on DNS resolver by caching resolved TCP addressed
223// for DNSCacheDuration.
224// * It dials all the resolved TCP addresses in round-robin manner until
225// connection is established. This may be useful if certain addresses
226// are temporarily unreachable.
227// * It returns ErrDialTimeout if connection cannot be established during
228// DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
229// timeout.
230//
231// This dialer is intended for custom code wrapping before passing
232// to Client.Dial or HostClient.Dial.
233//
234// For instance, per-host counters and/or limits may be implemented
235// by such wrappers.
236//
237// The addr passed to the function must contain port. Example addr values:
238//
239// * foobar.baz:443
240// * foo.bar:80
241// * aaa.com:8080
242func (d *TCPDialer) DialDualStack(addr string) (net.Conn, error) {
243 return d.dial(addr, true, DefaultDialTimeout)
244}
245
246// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
247// using the given timeout.
248//
249// This function has the following additional features comparing to net.Dial:
250//
251// * It reduces load on DNS resolver by caching resolved TCP addressed
252// for DNSCacheDuration.
253// * It dials all the resolved TCP addresses in round-robin manner until
254// connection is established. This may be useful if certain addresses
255// are temporarily unreachable.
256//
257// This dialer is intended for custom code wrapping before passing
258// to Client.Dial or HostClient.Dial.
259//
260// For instance, per-host counters and/or limits may be implemented
261// by such wrappers.
262//
263// The addr passed to the function must contain port. Example addr values:
264//
265// * foobar.baz:443
266// * foo.bar:80
267// * aaa.com:8080
268func (d *TCPDialer) DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {
269 return d.dial(addr, true, timeout)
270}
271
272func (d *TCPDialer) dial(addr string, dualStack bool, timeout time.Duration) (net.Conn, error) {
273 d.once.Do(func() {
274 if d.Concurrency > 0 {
275 d.concurrencyCh = make(chan struct{}, d.Concurrency)
276 }
277
278 if d.DNSCacheDuration == 0 {
279 d.DNSCacheDuration = DefaultDNSCacheDuration
280 }
281
282 go d.tcpAddrsClean()
283 })
284
285 addrs, idx, err := d.getTCPAddrs(addr, dualStack)
286 if err != nil {
287 return nil, err
288 }
289 network := "tcp4"
290 if dualStack {
291 network = "tcp"
292 }
293
294 var conn net.Conn
295 n := uint32(len(addrs))
296 deadline := time.Now().Add(timeout)
297 for n > 0 {
298 conn, err = d.tryDial(network, &addrs[idx%n], deadline, d.concurrencyCh)
299 if err == nil {
300 return conn, nil
301 }
302 if err == ErrDialTimeout {
303 return nil, err
304 }
305 idx++
306 n--
307 }
308 return nil, err
309}
310
311func (d *TCPDialer) tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyCh chan struct{}) (net.Conn, error) {
312 timeout := -time.Since(deadline)
313 if timeout <= 0 {
314 return nil, ErrDialTimeout
315 }
316
317 if concurrencyCh != nil {
318 select {
319 case concurrencyCh <- struct{}{}:
320 default:
321 tc := AcquireTimer(timeout)
322 isTimeout := false
323 select {
324 case concurrencyCh <- struct{}{}:
325 case <-tc.C:
326 isTimeout = true
327 }
328 ReleaseTimer(tc)
329 if isTimeout {
330 return nil, ErrDialTimeout
331 }
332 }
333 defer func() { <-concurrencyCh }()
334 }
335
336 dialer := net.Dialer{}
337 if d.LocalAddr != nil {
338 dialer.LocalAddr = d.LocalAddr
339 }
340
341 ctx, cancel_ctx := context.WithDeadline(context.Background(), deadline)
342 defer cancel_ctx()
343 conn, err := dialer.DialContext(ctx, network, addr.String())
344 if err != nil && ctx.Err() == context.DeadlineExceeded {
345 return nil, ErrDialTimeout
346 }
347 return conn, err
348}
349
350// ErrDialTimeout is returned when TCP dialing is timed out.
351var ErrDialTimeout = errors.New("dialing to the given TCP address timed out")
352
353// DefaultDialTimeout is timeout used by Dial and DialDualStack
354// for establishing TCP connections.
355const DefaultDialTimeout = 3 * time.Second
356
357type tcpAddrEntry struct {
358 addrs []net.TCPAddr
359 addrsIdx uint32
360
361 pending int32
362 resolveTime time.Time
363}
364
365// DefaultDNSCacheDuration is the duration for caching resolved TCP addresses
366// by Dial* functions.
367const DefaultDNSCacheDuration = time.Minute
368
369func (d *TCPDialer) tcpAddrsClean() {
370 expireDuration := 2 * d.DNSCacheDuration
371 for {
372 time.Sleep(time.Second)
373 t := time.Now()
374 d.tcpAddrsMap.Range(func(k, v interface{}) bool {
375 if e, ok := v.(*tcpAddrEntry); ok && t.Sub(e.resolveTime) > expireDuration {
376 d.tcpAddrsMap.Delete(k)
377 }
378 return true
379 })
380
381 }
382}
383
384func (d *TCPDialer) getTCPAddrs(addr string, dualStack bool) ([]net.TCPAddr, uint32, error) {
385 item, exist := d.tcpAddrsMap.Load(addr)
386 e, ok := item.(*tcpAddrEntry)
387 if exist && ok && e != nil && time.Since(e.resolveTime) > d.DNSCacheDuration {
388 // Only let one goroutine re-resolve at a time.
389 if atomic.SwapInt32(&e.pending, 1) == 0 {
390 e = nil
391 }
392 }
393
394 if e == nil {
395 addrs, err := resolveTCPAddrs(addr, dualStack, d.Resolver)
396 if err != nil {
397 item, exist := d.tcpAddrsMap.Load(addr)
398 e, ok = item.(*tcpAddrEntry)
399 if exist && ok && e != nil {
400 // Set pending to 0 so another goroutine can retry.
401 atomic.StoreInt32(&e.pending, 0)
402 }
403 return nil, 0, err
404 }
405
406 e = &tcpAddrEntry{
407 addrs: addrs,
408 resolveTime: time.Now(),
409 }
410 d.tcpAddrsMap.Store(addr, e)
411 }
412
413 idx := atomic.AddUint32(&e.addrsIdx, 1)
414 return e.addrs, idx, nil
415}
416
417func resolveTCPAddrs(addr string, dualStack bool, resolver Resolver) ([]net.TCPAddr, error) {
418 host, portS, err := net.SplitHostPort(addr)
419 if err != nil {
420 return nil, err
421 }
422 port, err := strconv.Atoi(portS)
423 if err != nil {
424 return nil, err
425 }
426
427 if resolver == nil {
428 resolver = net.DefaultResolver
429 }
430
431 ctx := context.Background()
432 ipaddrs, err := resolver.LookupIPAddr(ctx, host)
433 if err != nil {
434 return nil, err
435 }
436
437 n := len(ipaddrs)
438 addrs := make([]net.TCPAddr, 0, n)
439 for i := 0; i < n; i++ {
440 ip := ipaddrs[i]
441 if !dualStack && ip.IP.To4() == nil {
442 continue
443 }
444 addrs = append(addrs, net.TCPAddr{
445 IP: ip.IP,
446 Port: port,
447 Zone: ip.Zone,
448 })
449 }
450 if len(addrs) == 0 {
451 return nil, errNoDNSEntries
452 }
453 return addrs, nil
454}
455
456var errNoDNSEntries = errors.New("couldn't find DNS entries for the given domain. Try using DialDualStack")
Note: See TracBrowser for help on using the repository browser.