]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package client |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "net/http/httputil" | |
6 | ||
7 | "github.com/aws/aws-sdk-go/aws" | |
8 | "github.com/aws/aws-sdk-go/aws/awserr" | |
9 | "github.com/aws/aws-sdk-go/aws/client/metadata" | |
10 | "github.com/aws/aws-sdk-go/aws/request" | |
11 | ) | |
12 | ||
13 | // A Config provides configuration to a service client instance. | |
14 | type Config struct { | |
15 | Config *aws.Config | |
16 | Handlers request.Handlers | |
17 | Endpoint string | |
18 | SigningRegion string | |
19 | SigningName string | |
20 | } | |
21 | ||
22 | // ConfigProvider provides a generic way for a service client to receive | |
23 | // the ClientConfig without circular dependencies. | |
24 | type ConfigProvider interface { | |
25 | ClientConfig(serviceName string, cfgs ...*aws.Config) Config | |
26 | } | |
27 | ||
28 | // ConfigNoResolveEndpointProvider same as ConfigProvider except it will not | |
29 | // resolve the endpoint automatically. The service client's endpoint must be | |
30 | // provided via the aws.Config.Endpoint field. | |
31 | type ConfigNoResolveEndpointProvider interface { | |
32 | ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) Config | |
33 | } | |
34 | ||
35 | // A Client implements the base client request and response handling | |
36 | // used by all service clients. | |
37 | type Client struct { | |
38 | request.Retryer | |
39 | metadata.ClientInfo | |
40 | ||
41 | Config aws.Config | |
42 | Handlers request.Handlers | |
43 | } | |
44 | ||
45 | // New will return a pointer to a new initialized service client. | |
46 | func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, options ...func(*Client)) *Client { | |
47 | svc := &Client{ | |
48 | Config: cfg, | |
49 | ClientInfo: info, | |
50 | Handlers: handlers.Copy(), | |
51 | } | |
52 | ||
53 | switch retryer, ok := cfg.Retryer.(request.Retryer); { | |
54 | case ok: | |
55 | svc.Retryer = retryer | |
56 | case cfg.Retryer != nil && cfg.Logger != nil: | |
57 | s := fmt.Sprintf("WARNING: %T does not implement request.Retryer; using DefaultRetryer instead", cfg.Retryer) | |
58 | cfg.Logger.Log(s) | |
59 | fallthrough | |
60 | default: | |
61 | maxRetries := aws.IntValue(cfg.MaxRetries) | |
62 | if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries { | |
63 | maxRetries = 3 | |
64 | } | |
65 | svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries} | |
66 | } | |
67 | ||
68 | svc.AddDebugHandlers() | |
69 | ||
70 | for _, option := range options { | |
71 | option(svc) | |
72 | } | |
73 | ||
74 | return svc | |
75 | } | |
76 | ||
77 | // NewRequest returns a new Request pointer for the service API | |
78 | // operation and parameters. | |
79 | func (c *Client) NewRequest(operation *request.Operation, params interface{}, data interface{}) *request.Request { | |
80 | return request.New(c.Config, c.ClientInfo, c.Handlers, c.Retryer, operation, params, data) | |
81 | } | |
82 | ||
83 | // AddDebugHandlers injects debug logging handlers into the service to log request | |
84 | // debug information. | |
85 | func (c *Client) AddDebugHandlers() { | |
86 | if !c.Config.LogLevel.AtLeast(aws.LogDebug) { | |
87 | return | |
88 | } | |
89 | ||
90 | c.Handlers.Send.PushFrontNamed(request.NamedHandler{Name: "awssdk.client.LogRequest", Fn: logRequest}) | |
91 | c.Handlers.Send.PushBackNamed(request.NamedHandler{Name: "awssdk.client.LogResponse", Fn: logResponse}) | |
92 | } | |
93 | ||
94 | const logReqMsg = `DEBUG: Request %s/%s Details: | |
95 | ---[ REQUEST POST-SIGN ]----------------------------- | |
96 | %s | |
97 | -----------------------------------------------------` | |
98 | ||
99 | const logReqErrMsg = `DEBUG ERROR: Request %s/%s: | |
100 | ---[ REQUEST DUMP ERROR ]----------------------------- | |
101 | %s | |
102 | -----------------------------------------------------` | |
103 | ||
104 | func logRequest(r *request.Request) { | |
105 | logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) | |
106 | dumpedBody, err := httputil.DumpRequestOut(r.HTTPRequest, logBody) | |
107 | if err != nil { | |
108 | r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg, r.ClientInfo.ServiceName, r.Operation.Name, err)) | |
109 | r.Error = awserr.New(request.ErrCodeRead, "an error occurred during request body reading", err) | |
110 | return | |
111 | } | |
112 | ||
113 | if logBody { | |
114 | // Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's | |
115 | // Body as a NoOpCloser and will not be reset after read by the HTTP | |
116 | // client reader. | |
117 | r.ResetBody() | |
118 | } | |
119 | ||
120 | r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody))) | |
121 | } | |
122 | ||
123 | const logRespMsg = `DEBUG: Response %s/%s Details: | |
124 | ---[ RESPONSE ]-------------------------------------- | |
125 | %s | |
126 | -----------------------------------------------------` | |
127 | ||
128 | const logRespErrMsg = `DEBUG ERROR: Response %s/%s: | |
129 | ---[ RESPONSE DUMP ERROR ]----------------------------- | |
130 | %s | |
131 | -----------------------------------------------------` | |
132 | ||
133 | func logResponse(r *request.Request) { | |
134 | var msg = "no response data" | |
135 | if r.HTTPResponse != nil { | |
136 | logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody) | |
137 | dumpedBody, err := httputil.DumpResponse(r.HTTPResponse, logBody) | |
138 | if err != nil { | |
139 | r.Config.Logger.Log(fmt.Sprintf(logRespErrMsg, r.ClientInfo.ServiceName, r.Operation.Name, err)) | |
140 | r.Error = awserr.New(request.ErrCodeRead, "an error occurred during response body reading", err) | |
141 | return | |
142 | } | |
143 | ||
144 | msg = string(dumpedBody) | |
145 | } else if r.Error != nil { | |
146 | msg = r.Error.Error() | |
147 | } | |
148 | r.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.ClientInfo.ServiceName, r.Operation.Name, msg)) | |
149 | } |