session.go raw

   1  // Package session provides the base secure http client and request management for akamai apis
   2  package session
   3  
   4  import (
   5  	"context"
   6  	"errors"
   7  	"fmt"
   8  	"net/http"
   9  	"runtime"
  10  	"strings"
  11  
  12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegrid"
  13  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/log"
  14  )
  15  
  16  type (
  17  	// Session is the interface that is used by the pa
  18  	// This allows the client itself to be more extensible and readily testable, ets.
  19  	Session interface {
  20  		// Exec will sign and execute a request returning the response
  21  		// The response body will be unmarshaled in to out
  22  		// Optionally the in value will be marshaled into the body
  23  		Exec(r *http.Request, out interface{}, in ...interface{}) (*http.Response, error)
  24  
  25  		// Sign will only sign a request, this is useful for circumstances
  26  		// when the caller wishes to manage the http client
  27  		Sign(r *http.Request) error
  28  
  29  		// Log returns the logging interface for the session
  30  		// If provided all debugging will output to this log interface
  31  		Log(ctx context.Context) log.Interface
  32  
  33  		// Client return the session http client
  34  		Client() *http.Client
  35  	}
  36  
  37  	// session is the base akamai http client
  38  	session struct {
  39  		client       *http.Client
  40  		signer       edgegrid.Signer
  41  		log          log.Interface
  42  		trace        bool
  43  		userAgent    string
  44  		requestLimit int
  45  	}
  46  
  47  	contextOptions struct {
  48  		log    log.Interface
  49  		header http.Header
  50  	}
  51  
  52  	// Option defines a client option
  53  	Option func(*session) error
  54  
  55  	contextKey string
  56  
  57  	// ContextOption are options on the context
  58  	ContextOption func(*contextOptions)
  59  )
  60  
  61  var (
  62  	contextOptionKey = contextKey("sessionContext")
  63  )
  64  
  65  const (
  66  	// Version is the client version
  67  	Version = "11.0.0"
  68  )
  69  
  70  // New returns a new session
  71  func New(opts ...Option) (Session, error) {
  72  	var (
  73  		defaultUserAgent = "Akamai-Open-Edgegrid-golang/" + Version + " golang/" + strings.TrimPrefix(runtime.Version(), "go")
  74  	)
  75  
  76  	s := &session{
  77  		client:    http.DefaultClient,
  78  		log:       log.Default(),
  79  		userAgent: defaultUserAgent,
  80  		trace:     false,
  81  	}
  82  
  83  	for _, opt := range opts {
  84  		err := opt(s)
  85  		if err != nil {
  86  			return nil, err
  87  		}
  88  	}
  89  
  90  	if s.signer == nil {
  91  		config, err := edgegrid.New()
  92  		if err != nil {
  93  			return nil, err
  94  		}
  95  		s.signer = config
  96  	}
  97  
  98  	return s, nil
  99  }
 100  
 101  // Must is a helper that will result in a panic if an error is returned
 102  // ex. sess := Must(New())
 103  func Must(sess Session, err error) Session {
 104  	if err != nil {
 105  		panic(err)
 106  	}
 107  
 108  	return sess
 109  }
 110  
 111  // WithClient creates a client using the specified http.Client
 112  func WithClient(client *http.Client) Option {
 113  	return func(s *session) error {
 114  		if client == nil {
 115  			return errors.New("client should not be nil")
 116  		}
 117  		s.client = client
 118  		return nil
 119  	}
 120  }
 121  
 122  // WithRetries configures the HTTP client to automatically retry failed GET requests
 123  func WithRetries(conf RetryConfig) Option {
 124  	return func(s *session) error {
 125  		retryClient, err := configureRetryClient(conf, s.Sign, s.log)
 126  		if err != nil {
 127  			return fmt.Errorf("retry configuration failed: %w", err)
 128  		}
 129  		s.client = retryClient.StandardClient()
 130  		return nil
 131  	}
 132  }
 133  
 134  // WithLog sets the log interface for the client
 135  func WithLog(l log.Interface) Option {
 136  	return func(s *session) error {
 137  		if l == nil {
 138  			return errors.New("logger should not be nil")
 139  		}
 140  		s.log = l
 141  		return nil
 142  	}
 143  }
 144  
 145  // WithUserAgent sets the user agent string for the client
 146  func WithUserAgent(u string) Option {
 147  	return func(s *session) error {
 148  		if u == "" {
 149  			return errors.New("user agent should not be empty")
 150  		}
 151  		s.userAgent = u
 152  		return nil
 153  	}
 154  }
 155  
 156  // WithSigner sets the request signer for the session
 157  func WithSigner(signer edgegrid.Signer) Option {
 158  	return func(s *session) error {
 159  		if signer == nil {
 160  			return errors.New("signer should not be nil")
 161  		}
 162  		s.signer = signer
 163  		return nil
 164  	}
 165  }
 166  
 167  // WithRequestLimit sets the maximum number of API calls that the provider will make per second.
 168  func WithRequestLimit(requestLimit int) Option {
 169  	return func(s *session) error {
 170  		s.requestLimit = requestLimit
 171  		return nil
 172  	}
 173  }
 174  
 175  // WithHTTPTracing sets the request and response dump for debugging
 176  func WithHTTPTracing(trace bool) Option {
 177  	return func(s *session) error {
 178  		s.trace = trace
 179  		return nil
 180  	}
 181  }
 182  
 183  // Log returns the context logger, or the session log
 184  func (s *session) Log(ctx context.Context) log.Interface {
 185  	if o := ctx.Value(contextOptionKey); o != nil {
 186  		if ops, ok := o.(*contextOptions); ok && ops.log != nil {
 187  			return ops.log
 188  		}
 189  	}
 190  	if s.log != nil {
 191  		return s.log
 192  	}
 193  
 194  	// if context/session logs were not set, it will return default logger
 195  	return log.Default()
 196  }
 197  
 198  // Client returns the http client interface
 199  func (s *session) Client() *http.Client {
 200  	return s.client
 201  }
 202  
 203  // ContextWithOptions adds request-specific options to the context
 204  // This log debugs the request using only the provided log
 205  func ContextWithOptions(ctx context.Context, opts ...ContextOption) context.Context {
 206  	o := new(contextOptions)
 207  	for _, opt := range opts {
 208  		opt(o)
 209  	}
 210  
 211  	return context.WithValue(ctx, contextOptionKey, o)
 212  }
 213  
 214  // WithContextLog provides a context specific logger
 215  func WithContextLog(l log.Interface) ContextOption {
 216  	return func(o *contextOptions) {
 217  		o.log = l
 218  	}
 219  }
 220  
 221  // WithContextHeaders sets the context headers
 222  func WithContextHeaders(h http.Header) ContextOption {
 223  	return func(o *contextOptions) {
 224  		o.header = h
 225  	}
 226  }
 227  
 228  // CloseResponseBody closes response body
 229  func CloseResponseBody(resp *http.Response) {
 230  	_ = resp.Body.Close()
 231  }
 232