@@ -16,14 +16,19 @@ package com
1616
1717import (
1818 "bytes"
19+ "context"
1920 "encoding/json"
21+ "errors"
2022 "fmt"
2123 "io"
2224 "io/ioutil"
25+ "log"
2326 "net"
2427 "net/http"
28+ "net/url"
2529 "os"
2630 "path/filepath"
31+ "strings"
2732 "time"
2833)
2934
@@ -296,3 +301,95 @@ func HTTPClientWithTimeout(timeout time.Duration, options ...HTTPClientOptions)
296301 }
297302 return client
298303}
304+
305+ // IsNetworkOrHostDown - if there was a network error or if the host is down.
306+ // expectTimeouts indicates that *context* timeouts are expected and does not
307+ // indicate a downed host. Other timeouts still returns down.
308+ func IsNetworkOrHostDown (err error , expectTimeouts bool ) bool {
309+ if err == nil {
310+ return false
311+ }
312+
313+ if errors .Is (err , context .Canceled ) {
314+ return false
315+ }
316+
317+ if expectTimeouts && errors .Is (err , context .DeadlineExceeded ) {
318+ return false
319+ }
320+
321+ if errors .Is (err , context .DeadlineExceeded ) {
322+ return true
323+ }
324+
325+ // We need to figure if the error either a timeout
326+ // or a non-temporary error.
327+ urlErr := & url.Error {}
328+ if errors .As (err , & urlErr ) {
329+ switch urlErr .Err .(type ) {
330+ case * net.DNSError , * net.OpError , net.UnknownNetworkError :
331+ return true
332+ }
333+ }
334+ var e net.Error
335+ if errors .As (err , & e ) {
336+ if e .Timeout () {
337+ return true
338+ }
339+ }
340+
341+ // Fallback to other mechanisms.
342+ switch {
343+ case strings .Contains (err .Error (), "Connection closed by foreign host" ):
344+ return true
345+ case strings .Contains (err .Error (), "TLS handshake timeout" ):
346+ // If error is - tlsHandshakeTimeoutError.
347+ return true
348+ case strings .Contains (err .Error (), "i/o timeout" ):
349+ // If error is - tcp timeoutError.
350+ return true
351+ case strings .Contains (err .Error (), "connection timed out" ):
352+ // If err is a net.Dial timeout.
353+ return true
354+ case strings .Contains (err .Error (), "connection refused" ):
355+ // If err is connection refused
356+ return true
357+
358+ case strings .Contains (strings .ToLower (err .Error ()), "503 service unavailable" ):
359+ // Denial errors
360+ return true
361+ }
362+ return false
363+ }
364+
365+ func HTTPCanRetry (code int ) bool {
366+ return code < 200 || (code > 299 && code < http .StatusInternalServerError )
367+ }
368+
369+ func ParseHTTPRetryAfter (res http.ResponseWriter ) time.Duration {
370+ r := res .Header ().Get (`Retry-After` )
371+ return ParseRetryAfter (r )
372+ }
373+
374+ func ParseRetryAfter (r string ) time.Duration {
375+ if len (r ) == 0 {
376+ return 0
377+ }
378+ if StrIsNumeric (r ) {
379+ i := Int64 (r )
380+ if i <= 0 {
381+ return 0
382+ }
383+ return time .Duration (i ) * time .Second
384+ }
385+ t , err := time .Parse (time .RFC1123 , r )
386+ if err != nil {
387+ log .Printf (`failed to ParseRetryAfter(%q): %v` , r , err )
388+ return 0
389+ }
390+ //fmt.Printf("%+v", t.String())
391+ if t .Before (time .Now ()) {
392+ return 0
393+ }
394+ return time .Until (t )
395+ }
0 commit comments