@@ -5,11 +5,10 @@ package appctx
55
66import (
77 "context"
8- "net/http"
9- "strings"
10-
118 "go.amzn.com/lambda/fatalerror"
129 "go.amzn.com/lambda/interop"
10+ "net/http"
11+ "strings"
1312
1413 log "github.com/sirupsen/logrus"
1514)
@@ -24,6 +23,9 @@ type ReqCtxKey int
2423// context object into request context.
2524const ReqCtxApplicationContextKey ReqCtxKey = iota
2625
26+ // MaxRuntimeReleaseLength Max length for user agent string.
27+ const MaxRuntimeReleaseLength = 128
28+
2729// FromRequest retrieves application context from the request context.
2830func FromRequest (request * http.Request ) ApplicationContext {
2931 return request .Context ().Value (ReqCtxApplicationContextKey ).(ApplicationContext )
@@ -39,24 +41,78 @@ func GetRuntimeRelease(appCtx ApplicationContext) string {
3941 return appCtx .GetOrDefault (AppCtxRuntimeReleaseKey , "" ).(string )
4042}
4143
42- // UpdateAppCtxWithRuntimeRelease extracts runtime release info from user agent header and put it into appCtx.
44+ // GetUserAgentFromRequest Returns the first token -seperated by a space-
45+ // from request header 'User-Agent'.
46+ func GetUserAgentFromRequest (request * http.Request ) string {
47+ runtimeRelease := ""
48+ userAgent := request .Header .Get ("User-Agent" )
49+ // Split around spaces and use only the first token.
50+ if fields := strings .Fields (userAgent ); len (fields ) > 0 && len (fields [0 ]) > 0 {
51+ runtimeRelease = fields [0 ]
52+ }
53+ return runtimeRelease
54+ }
55+
56+ // CreateRuntimeReleaseFromRequest Gets runtime features from request header
57+ // 'Lambda-Runtime-Features', and append it to the given runtime release.
58+ func CreateRuntimeReleaseFromRequest (request * http.Request , runtimeRelease string ) string {
59+ lambdaRuntimeFeaturesHeader := request .Header .Get ("Lambda-Runtime-Features" )
60+
61+ // "(", ")" are not valid token characters, and potentially could invalidate runtime_release
62+ lambdaRuntimeFeaturesHeader = strings .ReplaceAll (lambdaRuntimeFeaturesHeader , "(" , "" )
63+ lambdaRuntimeFeaturesHeader = strings .ReplaceAll (lambdaRuntimeFeaturesHeader , ")" , "" )
64+
65+ numberOfAppendedFeatures := 0
66+ // Available length is a maximum length available for runtime features (including delimiters). From maximal runtime
67+ // release length we subtract what we already have plus 3 additional bytes for a space and a pair of brackets for
68+ // list of runtime features that is added later.
69+ runtimeReleaseLength := len (runtimeRelease )
70+ if runtimeReleaseLength == 0 {
71+ runtimeReleaseLength = len ("Unknown" )
72+ }
73+ availableLength := MaxRuntimeReleaseLength - runtimeReleaseLength - 3
74+ var lambdaRuntimeFeatures []string
75+
76+ for _ , feature := range strings .Fields (lambdaRuntimeFeaturesHeader ) {
77+ featureLength := len (feature )
78+ // If featureLength <= availableLength - numberOfAppendedFeatures
79+ // (where numberOfAppendedFeatures is equal to number of delimiters needed).
80+ if featureLength <= availableLength - numberOfAppendedFeatures {
81+ availableLength -= featureLength
82+ lambdaRuntimeFeatures = append (lambdaRuntimeFeatures , feature )
83+ numberOfAppendedFeatures ++
84+ }
85+ }
86+ // Append valid features to runtime release.
87+ if len (lambdaRuntimeFeatures ) > 0 {
88+ if runtimeRelease == "" {
89+ runtimeRelease = "Unknown"
90+ }
91+ runtimeRelease += " (" + strings .Join (lambdaRuntimeFeatures , " " ) + ")"
92+ }
93+
94+ return runtimeRelease
95+ }
96+
97+ // UpdateAppCtxWithRuntimeRelease extracts runtime release info from user agent & lambda runtime features
98+ // headers and update it into appCtx.
4399// Sample UA:
44100// Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0
45101func UpdateAppCtxWithRuntimeRelease (request * http.Request , appCtx ApplicationContext ) bool {
46- // If appCtx has runtime release value already, skip updating for consistency.
47- if len (GetRuntimeRelease (appCtx )) > 0 {
48- return false
49- }
50-
51- userAgent := request .Header .Get ("User-Agent" )
52- if len (userAgent ) == 0 {
102+ // If appCtx has runtime release value already, just append the runtime features.
103+ if appCtxRuntimeRelease := GetRuntimeRelease (appCtx ); len (appCtxRuntimeRelease ) > 0 {
104+ // if the runtime features are not appended before append them, otherwise ignore
105+ if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest (request , appCtxRuntimeRelease ); len (runtimeReleaseWithFeatures ) > len (appCtxRuntimeRelease ) &&
106+ appCtxRuntimeRelease [len (appCtxRuntimeRelease )- 1 ] != ')' {
107+ appCtx .Store (AppCtxRuntimeReleaseKey , runtimeReleaseWithFeatures )
108+ return true
109+ }
53110 return false
54111 }
55-
56- // Split around spaces and use only the first token.
57- if fields := strings .Fields (userAgent ); len (fields ) > 0 && len (fields [0 ]) > 0 {
58- appCtx .Store (AppCtxRuntimeReleaseKey ,
59- fields [0 ])
112+ // If appCtx doesn't have runtime release value, update it with user agent and runtime features.
113+ if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest (request ,
114+ GetUserAgentFromRequest (request )); runtimeReleaseWithFeatures != "" {
115+ appCtx .Store (AppCtxRuntimeReleaseKey , runtimeReleaseWithFeatures )
60116 return true
61117 }
62118 return false
0 commit comments