user_agent.go (6932B)
1 package middleware 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "runtime" 8 "strings" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 "github.com/aws/smithy-go/middleware" 12 smithyhttp "github.com/aws/smithy-go/transport/http" 13 ) 14 15 var languageVersion = strings.TrimPrefix(runtime.Version(), "go") 16 17 // SDKAgentKeyType is the metadata type to add to the SDK agent string 18 type SDKAgentKeyType int 19 20 // The set of valid SDKAgentKeyType constants. If an unknown value is assigned for SDKAgentKeyType it will 21 // be mapped to AdditionalMetadata. 22 const ( 23 _ SDKAgentKeyType = iota 24 APIMetadata 25 OperatingSystemMetadata 26 LanguageMetadata 27 EnvironmentMetadata 28 FeatureMetadata 29 ConfigMetadata 30 FrameworkMetadata 31 AdditionalMetadata 32 ApplicationIdentifier 33 ) 34 35 func (k SDKAgentKeyType) string() string { 36 switch k { 37 case APIMetadata: 38 return "api" 39 case OperatingSystemMetadata: 40 return "os" 41 case LanguageMetadata: 42 return "lang" 43 case EnvironmentMetadata: 44 return "exec-env" 45 case FeatureMetadata: 46 return "ft" 47 case ConfigMetadata: 48 return "cfg" 49 case FrameworkMetadata: 50 return "lib" 51 case ApplicationIdentifier: 52 return "app" 53 case AdditionalMetadata: 54 fallthrough 55 default: 56 return "md" 57 } 58 } 59 60 const execEnvVar = `AWS_EXECUTION_ENV` 61 62 // requestUserAgent is a build middleware that set the User-Agent for the request. 63 type requestUserAgent struct { 64 sdkAgent, userAgent *smithyhttp.UserAgentBuilder 65 } 66 67 // newRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the 68 // request. 69 // 70 // User-Agent example: 71 // 72 // aws-sdk-go-v2/1.2.3 73 // 74 // X-Amz-User-Agent example: 75 // 76 // aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15 77 func newRequestUserAgent() *requestUserAgent { 78 userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder() 79 addProductName(userAgent) 80 addProductName(sdkAgent) 81 82 r := &requestUserAgent{ 83 sdkAgent: sdkAgent, 84 userAgent: userAgent, 85 } 86 87 addSDKMetadata(r) 88 89 return r 90 } 91 92 func addSDKMetadata(r *requestUserAgent) { 93 r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName()) 94 r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion) 95 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS) 96 r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH) 97 if ev := os.Getenv(execEnvVar); len(ev) > 0 { 98 r.AddSDKAgentKey(EnvironmentMetadata, ev) 99 } 100 } 101 102 func addProductName(builder *smithyhttp.UserAgentBuilder) { 103 builder.AddKeyValue(aws.SDKName, aws.SDKVersion) 104 } 105 106 // AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 107 func AddUserAgentKey(key string) func(*middleware.Stack) error { 108 return func(stack *middleware.Stack) error { 109 requestUserAgent, err := getOrAddRequestUserAgent(stack) 110 if err != nil { 111 return err 112 } 113 requestUserAgent.AddUserAgentKey(key) 114 return nil 115 } 116 } 117 118 // AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 119 func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error { 120 return func(stack *middleware.Stack) error { 121 requestUserAgent, err := getOrAddRequestUserAgent(stack) 122 if err != nil { 123 return err 124 } 125 requestUserAgent.AddUserAgentKeyValue(key, value) 126 return nil 127 } 128 } 129 130 // AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one. 131 func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error { 132 return func(stack *middleware.Stack) error { 133 requestUserAgent, err := getOrAddRequestUserAgent(stack) 134 if err != nil { 135 return err 136 } 137 requestUserAgent.AddSDKAgentKey(keyType, key) 138 return nil 139 } 140 } 141 142 // AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one. 143 func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error { 144 return func(stack *middleware.Stack) error { 145 requestUserAgent, err := getOrAddRequestUserAgent(stack) 146 if err != nil { 147 return err 148 } 149 requestUserAgent.AddSDKAgentKeyValue(keyType, key, value) 150 return nil 151 } 152 } 153 154 // AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present. 155 func AddRequestUserAgentMiddleware(stack *middleware.Stack) error { 156 _, err := getOrAddRequestUserAgent(stack) 157 return err 158 } 159 160 func getOrAddRequestUserAgent(stack *middleware.Stack) (*requestUserAgent, error) { 161 id := (*requestUserAgent)(nil).ID() 162 bm, ok := stack.Build.Get(id) 163 if !ok { 164 bm = newRequestUserAgent() 165 err := stack.Build.Add(bm, middleware.After) 166 if err != nil { 167 return nil, err 168 } 169 } 170 171 requestUserAgent, ok := bm.(*requestUserAgent) 172 if !ok { 173 return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id) 174 } 175 176 return requestUserAgent, nil 177 } 178 179 // AddUserAgentKey adds the component identified by name to the User-Agent string. 180 func (u *requestUserAgent) AddUserAgentKey(key string) { 181 u.userAgent.AddKey(key) 182 } 183 184 // AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 185 func (u *requestUserAgent) AddUserAgentKeyValue(key, value string) { 186 u.userAgent.AddKeyValue(key, value) 187 } 188 189 // AddUserAgentKey adds the component identified by name to the User-Agent string. 190 func (u *requestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) { 191 // TODO: should target sdkAgent 192 u.userAgent.AddKey(keyType.string() + "/" + key) 193 } 194 195 // AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string. 196 func (u *requestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) { 197 // TODO: should target sdkAgent 198 u.userAgent.AddKeyValue(keyType.string()+"/"+key, value) 199 } 200 201 // ID the name of the middleware. 202 func (u *requestUserAgent) ID() string { 203 return "UserAgent" 204 } 205 206 // HandleBuild adds or appends the constructed user agent to the request. 207 func (u *requestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) ( 208 out middleware.BuildOutput, metadata middleware.Metadata, err error, 209 ) { 210 switch req := in.Request.(type) { 211 case *smithyhttp.Request: 212 u.addHTTPUserAgent(req) 213 // TODO: To be re-enabled 214 // u.addHTTPSDKAgent(req) 215 default: 216 return out, metadata, fmt.Errorf("unknown transport type %T", in) 217 } 218 219 return next.HandleBuild(ctx, in) 220 } 221 222 func (u *requestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) { 223 const userAgent = "User-Agent" 224 updateHTTPHeader(request, userAgent, u.userAgent.Build()) 225 } 226 227 func (u *requestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) { 228 const sdkAgent = "X-Amz-User-Agent" 229 updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build()) 230 } 231 232 func updateHTTPHeader(request *smithyhttp.Request, header string, value string) { 233 var current string 234 if v := request.Header[header]; len(v) > 0 { 235 current = v[0] 236 } 237 if len(current) > 0 { 238 current = value + " " + current 239 } else { 240 current = value 241 } 242 request.Header[header] = append(request.Header[header][:0], current) 243 }