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