package main import ( "crypto/tls" "flag" "fmt" "io" "net" "net/http" "net/url" "os" "path" "time" ) var version string type client struct { User string Password string Insecure bool // disable certificate check ConnectTimeout time.Duration ReadTimeout time.Duration Retries int RetryWait time.Duration NoProxy bool } func main() { c := client{} var filename string var showVersion bool flag.StringVar(&c.User, "http-user", "", "HTTP auth username") flag.StringVar(&c.Password, "http-passwd", "", "HTTP auth password") flag.IntVar(&c.Retries, "retries", 1, "Number of retries in case of download errors.") flag.DurationVar(&c.RetryWait, "retry-wait", 2*time.Second, "Time to wait before retrying download.") flag.DurationVar(&c.ConnectTimeout, "connect-timeout", 5*time.Second, "TCP connections that take longer to establish will be aborted") flag.DurationVar(&c.ReadTimeout, "read-timeout", 10*time.Second, "TCP connections that that are longer idle will be aborted") flag.BoolVar(&c.Insecure, "insecure", false, "Disable TLS certificate verification.") flag.BoolVar(&showVersion, "version", false, "Show version and exit.") flag.BoolVar(&c.NoProxy, "no-proxy", false, "Ignore *_proxy environment variables.") flag.StringVar(&filename, "filename", "", "Filename / path where to write downloaded file to. Defaults to filename of the URL.") flag.Parse() if showVersion { println("version:", version) os.Exit(1) } downloadURI := flag.Args()[0] if filename == "" { uri, err := url.Parse(downloadURI) if err != nil { panic(err) } filename = path.Base(uri.Path) } var err error for i := 0; i < c.Retries; i++ { err = c.DownloadFile(filename, downloadURI) if err == nil { break } println(err.Error()) if c.RetryWait > 0 { time.Sleep(c.RetryWait) } } if err != nil { if err := os.Remove(filename); err != nil { println(err) } os.Exit(1) } } func (c *client) DownloadFile(filename string, url string) (err error) { fmt.Fprintf(os.Stderr, "downloading %s to file %s\n", url, filename) // Create the target file out, err := os.Create(filename) if err != nil { return err } defer out.Close() tr := &http.Transport{ MaxIdleConns: 10, IdleConnTimeout: c.ReadTimeout, DisableCompression: true, DialContext: (&net.Dialer{ Timeout: c.ConnectTimeout, // connect timeout }).DialContext, // TLSHandshakeTimeout: 5 * time.Second, TLSClientConfig: &tls.Config{ InsecureSkipVerify: c.Insecure, }, } if c.NoProxy { tr.Proxy = nil } else { tr.Proxy = http.ProxyFromEnvironment } client := &http.Client{Transport: tr} // Get the data req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return err } req.SetBasicAuth(c.User, c.Password) resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() // Check server response if resp.StatusCode != http.StatusOK { return fmt.Errorf("bad status: %s", resp.Status) } // Writer the body to file _, err = io.Copy(out, resp.Body) if err != nil { return err } return nil }