...
1 package client
2
3 import (
4 "fmt"
5 "io"
6 "net/http"
7 "net/url"
8 "path"
9 "strconv"
10 "strings"
11 "time"
12 )
13
14 type HTTPRemoteOptions struct {
15 MetadataPath string
16 TargetsPath string
17 UserAgent string
18 Retries *HTTPRemoteRetries
19 }
20
21 type HTTPRemoteRetries struct {
22 Delay time.Duration
23 Total time.Duration
24 }
25
26 var DefaultHTTPRetries = &HTTPRemoteRetries{
27 Delay: time.Second,
28 Total: 10 * time.Second,
29 }
30
31 func HTTPRemoteStore(baseURL string, opts *HTTPRemoteOptions, client *http.Client) (RemoteStore, error) {
32 if !strings.HasPrefix(baseURL, "http") {
33 return nil, ErrInvalidURL{baseURL}
34 }
35 if opts == nil {
36 opts = &HTTPRemoteOptions{}
37 }
38 if opts.TargetsPath == "" {
39 opts.TargetsPath = "targets"
40 }
41 if client == nil {
42 client = http.DefaultClient
43 }
44 return &httpRemoteStore{baseURL, opts, client}, nil
45 }
46
47 type httpRemoteStore struct {
48 baseURL string
49 opts *HTTPRemoteOptions
50 cli *http.Client
51 }
52
53 func (h *httpRemoteStore) GetMeta(name string) (io.ReadCloser, int64, error) {
54 return h.get(path.Join(h.opts.MetadataPath, name))
55 }
56
57 func (h *httpRemoteStore) GetTarget(name string) (io.ReadCloser, int64, error) {
58 return h.get(path.Join(h.opts.TargetsPath, name))
59 }
60
61 func (h *httpRemoteStore) get(s string) (io.ReadCloser, int64, error) {
62 u := h.url(s)
63 req, err := http.NewRequest("GET", u, nil)
64 if err != nil {
65 return nil, 0, err
66 }
67 if h.opts.UserAgent != "" {
68 req.Header.Set("User-Agent", h.opts.UserAgent)
69 }
70 var res *http.Response
71 if r := h.opts.Retries; r != nil {
72 for start := time.Now(); time.Since(start) < r.Total; time.Sleep(r.Delay) {
73 res, err = h.cli.Do(req)
74 if err == nil && (res.StatusCode < 500 || res.StatusCode > 599) {
75 break
76 }
77 }
78 } else {
79 res, err = h.cli.Do(req)
80 }
81 if err != nil {
82 return nil, 0, err
83 }
84
85 if res.StatusCode == http.StatusNotFound {
86 res.Body.Close()
87 return nil, 0, ErrNotFound{s}
88 } else if res.StatusCode != http.StatusOK {
89 res.Body.Close()
90 return nil, 0, &url.Error{
91 Op: "GET",
92 URL: u,
93 Err: fmt.Errorf("unexpected HTTP status %d", res.StatusCode),
94 }
95 }
96
97 size, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 0)
98 if err != nil {
99 return res.Body, -1, nil
100 }
101 return res.Body, size, nil
102 }
103
104 func (h *httpRemoteStore) url(path string) string {
105 if !strings.HasPrefix(path, "/") {
106 path = "/" + path
107 }
108 return h.baseURL + path
109 }
110
View as plain text