diff options
Diffstat (limited to 'cautious_http_client.go')
-rw-r--r-- | cautious_http_client.go | 87 |
1 files changed, 74 insertions, 13 deletions
diff --git a/cautious_http_client.go b/cautious_http_client.go index 2f33ae0..34b736f 100644 --- a/cautious_http_client.go +++ b/cautious_http_client.go | |||
@@ -2,24 +2,29 @@ package main | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "fmt" | 5 | "github.com/lox/httpcache" |
6 | "github.com/pkg/errors" | ||
6 | "net" | 7 | "net" |
7 | "net/http" | 8 | "net/http" |
8 | "net/url" | 9 | "net/url" |
10 | "strings" | ||
9 | "time" | 11 | "time" |
10 | ) | 12 | ) |
11 | 13 | ||
12 | type CautiousHTTPClient interface { | 14 | type CautiousHTTPClient interface { |
13 | Get(string) (*http.Response, error) | 15 | Get(string) (*http.Response, error) |
14 | GetJSON(string, interface{}) error | 16 | GetJSON(string, interface{}) error |
17 | GetJSONExpires(string, interface{}) (time.Duration, error) | ||
15 | } | 18 | } |
16 | 19 | ||
17 | type cautiousHttpClient struct { | 20 | type cautiousHttpClient struct { |
18 | client *http.Client | 21 | allowHttp bool |
22 | client *http.Client | ||
19 | } | 23 | } |
20 | 24 | ||
21 | func NewCautiousHTTPClient() CautiousHTTPClient { | 25 | // allowHttp is UNSAFE and technically validates the spec but it does make it |
22 | // May Need: TLSClientConfig *tls.Config | 26 | // easier to work in dev so leaving it in for now |
27 | func NewCautiousHTTPClient(allowHttp bool) (CautiousHTTPClient, error) { | ||
23 | CautiousTransport := &http.Transport{ | 28 | CautiousTransport := &http.Transport{ |
24 | Proxy: http.ProxyFromEnvironment, | 29 | Proxy: http.ProxyFromEnvironment, |
25 | DialContext: (&net.Dialer{ | 30 | DialContext: (&net.Dialer{ |
@@ -36,44 +41,100 @@ func NewCautiousHTTPClient() CautiousHTTPClient { | |||
36 | } | 41 | } |
37 | 42 | ||
38 | return &cautiousHttpClient{ | 43 | return &cautiousHttpClient{ |
44 | allowHttp: allowHttp, | ||
39 | client: &http.Client{ | 45 | client: &http.Client{ |
40 | Transport: CautiousTransport, | 46 | Transport: CautiousTransport, |
41 | Timeout: 30 * time.Second, | 47 | Timeout: 30 * time.Second, |
42 | }, | 48 | }, |
43 | } | 49 | }, nil |
44 | } | 50 | } |
45 | 51 | ||
46 | func (c *cautiousHttpClient) Get(gurl string) (*http.Response, error) { | 52 | func (c *cautiousHttpClient) Get(gurl string) (*http.Response, error) { |
47 | u, err := url.Parse(gurl) | 53 | u, err := url.Parse(gurl) |
48 | if err != nil { | 54 | if err != nil { |
49 | return nil, err | 55 | return nil, errors.WithStack(err) |
50 | } | 56 | } |
51 | 57 | ||
52 | // TODO | 58 | if u.Scheme != "https" && !c.allowHttp { |
53 | if u.Scheme != "https" && false { | 59 | return nil, errors.Errorf("URL for GET must be secure") |
54 | return nil, fmt.Errorf("URL for GET must be secure") | ||
55 | } | 60 | } |
56 | 61 | ||
57 | r, err := c.client.Get(u.String()) | 62 | r, err := c.client.Get(u.String()) |
58 | if err != nil { | 63 | if err != nil { |
59 | return nil, err | 64 | return nil, errors.WithStack(err) |
60 | } | 65 | } |
61 | r.Body = http.MaxBytesReader(nil, r.Body, 1000000) | 66 | r.Body = http.MaxBytesReader(nil, r.Body, 1000000) |
62 | return r, err | 67 | |
68 | return r, nil | ||
63 | } | 69 | } |
64 | 70 | ||
65 | func (c *cautiousHttpClient) GetJSON(url string, rv interface{}) error { | 71 | func (c *cautiousHttpClient) GetJSON(url string, rv interface{}) error { |
66 | r, err := c.Get(url) | 72 | r, err := c.Get(url) |
67 | if err != nil { | 73 | if err != nil { |
68 | return err | 74 | return errors.WithStack(err) |
69 | } | 75 | } |
70 | defer r.Body.Close() | 76 | defer r.Body.Close() |
71 | 77 | ||
72 | d := json.NewDecoder(r.Body) | 78 | d := json.NewDecoder(r.Body) |
73 | err = d.Decode(rv) | 79 | err = d.Decode(rv) |
74 | if err != nil { | 80 | if err != nil { |
75 | return err | 81 | return errors.WithStack(err) |
76 | } | 82 | } |
77 | 83 | ||
78 | return nil | 84 | return nil |
79 | } | 85 | } |
86 | |||
87 | func (c *cautiousHttpClient) GetJSONExpires(url string, rv interface{}) (time.Duration, error) { | ||
88 | r, err := c.Get(url) | ||
89 | if err != nil { | ||
90 | return time.Duration(0), errors.WithStack(err) | ||
91 | } | ||
92 | defer r.Body.Close() | ||
93 | |||
94 | res := httpcache.NewResource(r.StatusCode, nil, r.Header) | ||
95 | |||
96 | d := json.NewDecoder(r.Body) | ||
97 | err = d.Decode(rv) | ||
98 | if err != nil { | ||
99 | return time.Duration(0), errors.WithStack(err) | ||
100 | } | ||
101 | |||
102 | return refreshAfter(res), nil | ||
103 | } | ||
104 | |||
105 | type JSONURL struct { | ||
106 | *url.URL | ||
107 | } | ||
108 | |||
109 | func (u *JSONURL) AsURL() *url.URL { | ||
110 | return u.URL | ||
111 | } | ||
112 | |||
113 | func (u *JSONURL) UnmarshalJSON(data []byte) error { | ||
114 | d := strings.Trim(string(data), "\"") | ||
115 | pu, err := url.Parse(d) | ||
116 | if err != nil { | ||
117 | return errors.WithStack(err) | ||
118 | } | ||
119 | |||
120 | u.URL = pu | ||
121 | return nil | ||
122 | } | ||
123 | |||
124 | func refreshAfter(res *httpcache.Resource) time.Duration { | ||
125 | maxAge, err := res.MaxAge(false) | ||
126 | if err != nil { | ||
127 | return time.Duration(0) | ||
128 | } | ||
129 | |||
130 | age, err := res.Age() | ||
131 | if err != nil { | ||
132 | return time.Duration(0) | ||
133 | } | ||
134 | |||
135 | if hFresh := res.HeuristicFreshness(); hFresh > maxAge { | ||
136 | maxAge = hFresh | ||
137 | } | ||
138 | |||
139 | return maxAge - age | ||
140 | } | ||