summaryrefslogtreecommitdiff
path: root/cautious_http_client.go
diff options
context:
space:
mode:
Diffstat (limited to 'cautious_http_client.go')
-rw-r--r--cautious_http_client.go87
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
3import ( 3import (
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
12type CautiousHTTPClient interface { 14type 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
17type cautiousHttpClient struct { 20type cautiousHttpClient struct {
18 client *http.Client 21 allowHttp bool
22 client *http.Client
19} 23}
20 24
21func 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
27func 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
46func (c *cautiousHttpClient) Get(gurl string) (*http.Response, error) { 52func (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
65func (c *cautiousHttpClient) GetJSON(url string, rv interface{}) error { 71func (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
87func (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
105type JSONURL struct {
106 *url.URL
107}
108
109func (u *JSONURL) AsURL() *url.URL {
110 return u.URL
111}
112
113func (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
124func 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}