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  }