forked from aws/aws-lambda-runtime-interface-emulator
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathappctxutil.go
More file actions
166 lines (142 loc) · 6.44 KB
/
appctxutil.go
File metadata and controls
166 lines (142 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package appctx
import (
"context"
"go.amzn.com/lambda/fatalerror"
"go.amzn.com/lambda/interop"
"net/http"
"strings"
log "github.com/sirupsen/logrus"
)
// This package contains a set of utility methods for accessing application
// context and application context data.
// A ReqCtxKey type is used as a key for storing values in the request context.
type ReqCtxKey int
// ReqCtxApplicationContextKey is used for injecting application
// context object into request context.
const ReqCtxApplicationContextKey ReqCtxKey = iota
// MaxRuntimeReleaseLength Max length for user agent string.
const MaxRuntimeReleaseLength = 128
// FromRequest retrieves application context from the request context.
func FromRequest(request *http.Request) ApplicationContext {
return request.Context().Value(ReqCtxApplicationContextKey).(ApplicationContext)
}
// RequestWithAppCtx places application context into request context.
func RequestWithAppCtx(request *http.Request, appCtx ApplicationContext) *http.Request {
return request.WithContext(context.WithValue(request.Context(), ReqCtxApplicationContextKey, appCtx))
}
// GetRuntimeRelease returns runtime_release str extracted from app context.
func GetRuntimeRelease(appCtx ApplicationContext) string {
return appCtx.GetOrDefault(AppCtxRuntimeReleaseKey, "").(string)
}
// GetUserAgentFromRequest Returns the first token -seperated by a space-
// from request header 'User-Agent'.
func GetUserAgentFromRequest(request *http.Request) string {
runtimeRelease := ""
userAgent := request.Header.Get("User-Agent")
// Split around spaces and use only the first token.
if fields := strings.Fields(userAgent); len(fields) > 0 && len(fields[0]) > 0 {
runtimeRelease = fields[0]
}
return runtimeRelease
}
// CreateRuntimeReleaseFromRequest Gets runtime features from request header
// 'Lambda-Runtime-Features', and append it to the given runtime release.
func CreateRuntimeReleaseFromRequest(request *http.Request, runtimeRelease string) string {
lambdaRuntimeFeaturesHeader := request.Header.Get("Lambda-Runtime-Features")
// "(", ")" are not valid token characters, and potentially could invalidate runtime_release
lambdaRuntimeFeaturesHeader = strings.ReplaceAll(lambdaRuntimeFeaturesHeader, "(", "")
lambdaRuntimeFeaturesHeader = strings.ReplaceAll(lambdaRuntimeFeaturesHeader, ")", "")
numberOfAppendedFeatures := 0
// Available length is a maximum length available for runtime features (including delimiters). From maximal runtime
// release length we subtract what we already have plus 3 additional bytes for a space and a pair of brackets for
// list of runtime features that is added later.
runtimeReleaseLength := len(runtimeRelease)
if runtimeReleaseLength == 0 {
runtimeReleaseLength = len("Unknown")
}
availableLength := MaxRuntimeReleaseLength - runtimeReleaseLength - 3
var lambdaRuntimeFeatures []string
for _, feature := range strings.Fields(lambdaRuntimeFeaturesHeader) {
featureLength := len(feature)
// If featureLength <= availableLength - numberOfAppendedFeatures
// (where numberOfAppendedFeatures is equal to number of delimiters needed).
if featureLength <= availableLength-numberOfAppendedFeatures {
availableLength -= featureLength
lambdaRuntimeFeatures = append(lambdaRuntimeFeatures, feature)
numberOfAppendedFeatures++
}
}
// Append valid features to runtime release.
if len(lambdaRuntimeFeatures) > 0 {
if runtimeRelease == "" {
runtimeRelease = "Unknown"
}
runtimeRelease += " (" + strings.Join(lambdaRuntimeFeatures, " ") + ")"
}
return runtimeRelease
}
// UpdateAppCtxWithRuntimeRelease extracts runtime release info from user agent & lambda runtime features
// headers and update it into appCtx.
// Sample UA:
// Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0
func UpdateAppCtxWithRuntimeRelease(request *http.Request, appCtx ApplicationContext) bool {
// If appCtx has runtime release value already, just append the runtime features.
if appCtxRuntimeRelease := GetRuntimeRelease(appCtx); len(appCtxRuntimeRelease) > 0 {
// if the runtime features are not appended before append them, otherwise ignore
if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest(request, appCtxRuntimeRelease); len(runtimeReleaseWithFeatures) > len(appCtxRuntimeRelease) &&
appCtxRuntimeRelease[len(appCtxRuntimeRelease)-1] != ')' {
appCtx.Store(AppCtxRuntimeReleaseKey, runtimeReleaseWithFeatures)
return true
}
return false
}
// If appCtx doesn't have runtime release value, update it with user agent and runtime features.
if runtimeReleaseWithFeatures := CreateRuntimeReleaseFromRequest(request,
GetUserAgentFromRequest(request)); runtimeReleaseWithFeatures != "" {
appCtx.Store(AppCtxRuntimeReleaseKey, runtimeReleaseWithFeatures)
return true
}
return false
}
// StoreErrorResponse stores response in the applicaton context.
func StoreErrorResponse(appCtx ApplicationContext, errorResponse *interop.ErrorResponse) {
appCtx.Store(AppCtxInvokeErrorResponseKey, errorResponse)
}
// LoadErrorResponse retrieves response from the application context.
func LoadErrorResponse(appCtx ApplicationContext) *interop.ErrorResponse {
v, ok := appCtx.Load(AppCtxInvokeErrorResponseKey)
if ok {
return v.(*interop.ErrorResponse)
}
return nil
}
// StoreInteropServer stores a reference to the interop server.
func StoreInteropServer(appCtx ApplicationContext, server interop.Server) {
appCtx.Store(AppCtxInteropServerKey, server)
}
// LoadInteropServer retrieves the interop server.
func LoadInteropServer(appCtx ApplicationContext) interop.Server {
v, ok := appCtx.Load(AppCtxInteropServerKey)
if ok {
return v.(interop.Server)
}
return nil
}
// StoreFirstFatalError stores unrecoverable error code in appctx once. This error is considered to be the rootcause of failure
func StoreFirstFatalError(appCtx ApplicationContext, err fatalerror.ErrorType) {
if existing := appCtx.StoreIfNotExists(AppCtxFirstFatalErrorKey, err); existing != nil {
log.Warnf("Omitting fatal error %s: %s already stored", err, existing.(fatalerror.ErrorType))
return
}
log.Warnf("First fatal error stored in appctx: %s", err)
}
// LoadFirstFatalError returns stored error if found
func LoadFirstFatalError(appCtx ApplicationContext) (errorType fatalerror.ErrorType, found bool) {
v, found := appCtx.Load(AppCtxFirstFatalErrorKey)
if !found {
return "", false
}
return v.(fatalerror.ErrorType), true
}