github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/httpvar/httpvar.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package httpvar provides a runtimevar implementation with variables 16 // backed by http endpoint. Use OpenVariable to construct a *runtimevar.Variable. 17 // 18 // URLs 19 // 20 // For runtimevar.OpenVariable, httpvar registers for the schemes "http" and 21 // "https". The default URL opener will use http.DefaultClient. 22 // To customize the URL opener, or for more details on the URL format, 23 // see URLOpener. 24 // See https://gocloud.dev/concepts/urls/ for background information. 25 // 26 // As 27 // 28 // httpvar exposes the following types for As: 29 // - Snapshot: *http.Response 30 // - Error: httpvar.RequestError, url.Error 31 package httpvar // import "gocloud.dev/runtimevar/httpvar" 32 33 import ( 34 "bytes" 35 "context" 36 "fmt" 37 "io/ioutil" 38 "net/http" 39 "net/url" 40 "time" 41 42 "gocloud.dev/gcerrors" 43 "gocloud.dev/internal/gcerr" 44 "gocloud.dev/runtimevar" 45 "gocloud.dev/runtimevar/driver" 46 ) 47 48 func init() { 49 o := &URLOpener{Client: http.DefaultClient} 50 for _, scheme := range Schemes { 51 runtimevar.DefaultURLMux().RegisterVariable(scheme, o) 52 } 53 } 54 55 // Schemes are the URL schemes httpvar registers its URLOpener under on runtimevar.DefaultMux. 56 var Schemes = []string{"http", "https"} 57 58 // URLOpener opens HTTP URLs like "http://myserver.com/foo.txt". 59 // 60 // The full URL, including scheme, is used as the endpoint, except that the 61 // the following URL parameters are removed if present: 62 // - decoder: The decoder to use. Defaults to runtimevar.BytesDecoder. 63 // See runtimevar.DecoderByName for supported values. 64 type URLOpener struct { 65 // The Client to use; required. 66 Client *http.Client 67 68 // Decoder specifies the decoder to use if one is not specified in the URL. 69 // Defaults to runtimevar.BytesDecoder. 70 Decoder *runtimevar.Decoder 71 72 // Options specifies the options to pass to OpenVariable. 73 Options Options 74 } 75 76 // OpenVariableURL opens a httpvar Variable for u. 77 func (o *URLOpener) OpenVariableURL(ctx context.Context, u *url.URL) (*runtimevar.Variable, error) { 78 // Clone u because we may strip some query parameters. 79 u2 := *u 80 q := u2.Query() 81 82 decoderName := q.Get("decoder") 83 q.Del("decoder") 84 decoder, err := runtimevar.DecoderByName(ctx, decoderName, o.Decoder) 85 if err != nil { 86 return nil, fmt.Errorf("open variable %v: invalid decoder: %v", u, err) 87 } 88 // See if we changed the query parameters. 89 if rawq := q.Encode(); rawq != u.Query().Encode() { 90 u2.RawQuery = rawq 91 } 92 return OpenVariable(o.Client, u2.String(), decoder, &o.Options) 93 } 94 95 // Options sets options. 96 type Options struct { 97 // WaitDuration controls the rate at which the HTTP endpoint is called to check for changes. 98 // Defaults to 30 seconds. 99 WaitDuration time.Duration 100 } 101 102 // RequestError represents an HTTP error that occurred during endpoint call. 103 type RequestError struct { 104 Response *http.Response 105 } 106 107 func (e *RequestError) Error() string { 108 return fmt.Sprintf("httpvar: received status code %d", e.Response.StatusCode) 109 } 110 111 func newRequestError(response *http.Response) *RequestError { 112 return &RequestError{Response: response} 113 } 114 115 // OpenVariable constructs a *runtimevar.Variable that uses client 116 // to retrieve the variable contents from the URL urlStr. 117 func OpenVariable(client *http.Client, urlStr string, decoder *runtimevar.Decoder, opts *Options) (*runtimevar.Variable, error) { 118 endpointURL, err := url.Parse(urlStr) 119 if err != nil { 120 return nil, fmt.Errorf("httpvar: failed to parse url %q: %v", urlStr, err) 121 } 122 123 return runtimevar.New(newWatcher(client, endpointURL, decoder, opts)), nil 124 } 125 126 type state struct { 127 val interface{} 128 raw *http.Response 129 rawBytes []byte 130 updateTime time.Time 131 err error 132 } 133 134 // Value implements driver.State.Value. 135 func (s *state) Value() (interface{}, error) { 136 return s.val, s.err 137 } 138 139 // UpdateTime implements driver.State.UpdateTime. 140 func (s *state) UpdateTime() time.Time { 141 return s.updateTime 142 } 143 144 // As implements driver.State.As. 145 func (s *state) As(i interface{}) bool { 146 if s.raw == nil { 147 return false 148 } 149 p, ok := i.(**http.Response) 150 if !ok { 151 return false 152 } 153 *p = s.raw 154 return true 155 } 156 157 // errorState returns a new State with err, unless prevS also represents 158 // the same error, in which case it returns nil. 159 func errorState(err error, prevS driver.State) driver.State { 160 s := &state{err: err} 161 if prevS == nil { 162 return s 163 } 164 prev := prevS.(*state) 165 if prev.err == nil { 166 // New error. 167 return s 168 } 169 if equivalentError(err, prev.err) { 170 // Same error, return nil to indicate no change. 171 return nil 172 } 173 return s 174 } 175 176 // equivalentError returns true if err1 and err2 represent an equivalent error; 177 // i.e., we don't want to return it to the user as a different error. 178 func equivalentError(err1, err2 error) bool { 179 if err1 == err2 || err1.Error() == err2.Error() { 180 return true 181 } 182 var code1, code2 int 183 if e, ok := err1.(*RequestError); ok { 184 code1 = e.Response.StatusCode 185 } 186 if e, ok := err2.(*RequestError); ok { 187 code2 = e.Response.StatusCode 188 } 189 return code1 != 0 && code1 == code2 190 } 191 192 // watcher implements driver.Watcher for configurations provided by the Runtime Configurator 193 // service. 194 type watcher struct { 195 client *http.Client 196 endpoint *url.URL 197 decoder *runtimevar.Decoder 198 wait time.Duration 199 } 200 201 // WatchVariable implements driver.WatchVariable. 202 func (w *watcher) WatchVariable(ctx context.Context, prev driver.State) (driver.State, time.Duration) { 203 req, err := http.NewRequestWithContext(ctx, http.MethodGet, w.endpoint.String(), nil) 204 if err != nil { 205 return errorState(err, prev), w.wait 206 } 207 resp, err := w.client.Do(req) 208 if err != nil { 209 return errorState(err, prev), w.wait 210 } 211 defer resp.Body.Close() 212 213 if resp.StatusCode != http.StatusOK { 214 err := newRequestError(resp) 215 return errorState(err, prev), w.wait 216 } 217 218 respBodyBytes, err := ioutil.ReadAll(resp.Body) 219 if err != nil { 220 return errorState(err, prev), w.wait 221 } 222 223 // When endpoint returns the same response again, we return nil as state to not trigger variable update. 224 if prev != nil && bytes.Equal(respBodyBytes, prev.(*state).rawBytes) { 225 return nil, w.wait 226 } 227 228 val, err := w.decoder.Decode(ctx, respBodyBytes) 229 if err != nil { 230 return errorState(err, prev), w.wait 231 } 232 233 return &state{ 234 val: val, 235 raw: resp, 236 rawBytes: respBodyBytes, 237 updateTime: time.Now(), 238 }, w.wait 239 } 240 241 // Close implements driver.Close. 242 func (w *watcher) Close() error { 243 return nil 244 } 245 246 // ErrorAs implements driver.ErrorAs. 247 func (w *watcher) ErrorAs(err error, i interface{}) bool { 248 switch v := err.(type) { 249 case *url.Error: 250 if p, ok := i.(*url.Error); ok { 251 *p = *v 252 return true 253 } 254 case *RequestError: 255 if p, ok := i.(*RequestError); ok { 256 *p = *v 257 return true 258 } 259 } 260 return false 261 } 262 263 // ErrorCode implements driver.ErrorCode. 264 func (*watcher) ErrorCode(err error) gcerrors.ErrorCode { 265 if requestErr, ok := err.(*RequestError); ok { 266 switch requestErr.Response.StatusCode { 267 case http.StatusBadRequest: 268 return gcerr.InvalidArgument 269 case http.StatusNotFound: 270 return gcerr.NotFound 271 case http.StatusUnauthorized: 272 return gcerr.PermissionDenied 273 case http.StatusGatewayTimeout, http.StatusRequestTimeout: 274 return gcerr.DeadlineExceeded 275 case http.StatusInternalServerError, http.StatusServiceUnavailable, http.StatusBadGateway: 276 return gcerr.Internal 277 } 278 } 279 return gcerr.Unknown 280 } 281 282 func newWatcher(client *http.Client, endpoint *url.URL, decoder *runtimevar.Decoder, opts *Options) driver.Watcher { 283 if opts == nil { 284 opts = &Options{} 285 } 286 return &watcher{ 287 client: client, 288 endpoint: endpoint, 289 decoder: decoder, 290 wait: driver.WaitDuration(opts.WaitDuration), 291 } 292 }