sq/libsq/source/fetcher/fetcher.go

111 lines
2.4 KiB
Go
Raw Normal View History

2020-08-06 20:58:47 +03:00
// Package fetcher provides a mechanism for fetching files
// from URLs.
package fetcher
import (
"context"
"crypto/tls"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/net/context/ctxhttp"
"github.com/neilotoole/sq/libsq/core/errz"
"github.com/neilotoole/sq/libsq/core/stringz"
2020-08-06 20:58:47 +03:00
)
// Config parameterizes Fetcher behavior.
type Config struct {
// Timeout is the request timeout.
Timeout time.Duration
// Skip verification of insecure transports.
InsecureSkipVerify bool
}
// Fetcher can fetch files from URLs. If field Config is nil,
2021-01-02 07:10:02 +03:00
// defaults are used. At this time, only HTTP/HTTPS is supported,
// but it's possible other schemes (such as FTP) will be
// supported in future.
2020-08-06 20:58:47 +03:00
type Fetcher struct {
Config *Config
}
// Fetch writes the body of the document at fileURL to w.
func (f *Fetcher) Fetch(ctx context.Context, fileURL string, w io.Writer) error {
return fetchHTTP(ctx, f.Config, fileURL, w)
2020-08-06 20:58:47 +03:00
}
func httpClient(cfg *Config) *http.Client {
var client = *http.DefaultClient
var tr *http.Transport
if client.Transport == nil {
tr = (http.DefaultTransport.(*http.Transport)).Clone()
} else {
tr = (client.Transport.(*http.Transport)).Clone()
}
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
2020-08-06 20:58:47 +03:00
} else {
tr.TLSClientConfig = tr.TLSClientConfig.Clone()
}
if cfg != nil {
tr.TLSClientConfig.InsecureSkipVerify = cfg.InsecureSkipVerify
client.Timeout = cfg.Timeout
}
client.Transport = tr
return &client
}
func fetchHTTP(ctx context.Context, cfg *Config, fileURL string, w io.Writer) error {
2020-08-06 20:58:47 +03:00
c := httpClient(cfg)
req, err := http.NewRequest("GET", fileURL, nil)
2020-08-06 20:58:47 +03:00
if err != nil {
return err
}
resp, err := ctxhttp.Do(ctx, c, req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
_ = resp.Body.Close()
return errz.Errorf("http: returned non-200 status code (%s) from: %s", resp.Status, fileURL)
2020-08-06 20:58:47 +03:00
}
_, err = io.Copy(w, resp.Body)
if err != nil {
_ = resp.Body.Close()
return errz.Wrapf(err, "http: failed to read body from: %s", fileURL)
2020-08-06 20:58:47 +03:00
}
return errz.Err(resp.Body.Close())
}
// Schemes is the set of supported schemes.
func (f *Fetcher) Schemes() []string {
return []string{"http", "https"}
}
// Supported returns true if loc is a supported URL.
func (f *Fetcher) Supported(loc string) bool {
u, err := url.ParseRequestURI(loc)
if err != nil {
return false
}
if stringz.InSlice(f.Schemes(), u.Scheme) {
return true
}
return false
}