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