| package client |
| |
| import ( |
| "fmt" |
| "io" |
| "net/http" |
| "net/url" |
| "path" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| type HTTPRemoteOptions struct { |
| MetadataPath string |
| TargetsPath string |
| UserAgent string |
| Retries *HTTPRemoteRetries |
| } |
| |
| type HTTPRemoteRetries struct { |
| Delay time.Duration |
| Total time.Duration |
| } |
| |
| var DefaultHTTPRetries = &HTTPRemoteRetries{ |
| Delay: time.Second, |
| Total: 10 * time.Second, |
| } |
| |
| func HTTPRemoteStore(baseURL string, opts *HTTPRemoteOptions, client *http.Client) (RemoteStore, error) { |
| if !strings.HasPrefix(baseURL, "http") { |
| return nil, ErrInvalidURL{baseURL} |
| } |
| if opts == nil { |
| opts = &HTTPRemoteOptions{} |
| } |
| if opts.TargetsPath == "" { |
| opts.TargetsPath = "targets" |
| } |
| if client == nil { |
| client = http.DefaultClient |
| } |
| return &httpRemoteStore{baseURL, opts, client}, nil |
| } |
| |
| type httpRemoteStore struct { |
| baseURL string |
| opts *HTTPRemoteOptions |
| cli *http.Client |
| } |
| |
| func (h *httpRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) { |
| return h.get(path.Join(h.opts.MetadataPath, name)) |
| } |
| |
| func (h *httpRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) { |
| return h.get(path.Join(h.opts.TargetsPath, name)) |
| } |
| |
| func (h *httpRemoteStore) get(s string) (io.ReadCloser, int64, error) { |
| u := h.url(s) |
| req, err := http.NewRequest("GET", u, nil) |
| if err != nil { |
| return nil, 0, err |
| } |
| if h.opts.UserAgent != "" { |
| req.Header.Set("User-Agent", h.opts.UserAgent) |
| } |
| var res *http.Response |
| if r := h.opts.Retries; r != nil { |
| for start := time.Now(); time.Since(start) < r.Total; time.Sleep(r.Delay) { |
| res, err = h.cli.Do(req) |
| if err == nil && (res.StatusCode < 500 || res.StatusCode > 599) { |
| break |
| } |
| } |
| } else { |
| res, err = h.cli.Do(req) |
| } |
| if err != nil { |
| return nil, 0, err |
| } |
| |
| if res.StatusCode == http.StatusNotFound { |
| res.Body.Close() |
| return nil, 0, ErrNotFound{s} |
| } else if res.StatusCode != http.StatusOK { |
| res.Body.Close() |
| return nil, 0, &url.Error{ |
| Op: "GET", |
| URL: u, |
| Err: fmt.Errorf("unexpected HTTP status %d", res.StatusCode), |
| } |
| } |
| |
| size, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 0) |
| if err != nil { |
| return res.Body, -1, nil |
| } |
| return res.Body, size, nil |
| } |
| |
| func (h *httpRemoteStore) url(path string) string { |
| if !strings.HasPrefix(path, "/") { |
| path = "/" + path |
| } |
| return h.baseURL + path |
| } |