-
Notifications
You must be signed in to change notification settings - Fork 404
/
Copy pathapi-gateway.js
159 lines (139 loc) · 4.41 KB
/
api-gateway.js
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
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
'use strict'
/**
* This class captures data needed to construct a web transaction from
* an API Gateway Lambda proxy request. This is to be used with the setWebRequest
* method.
*/
class LambdaProxyWebRequest {
constructor(event) {
const lowerCaseHeaders = normalizeHeaders(event, true)
this.headers = normalizeHeaders(event)
this.url = {
path: '',
port: lowerCaseHeaders['x-forwarded-port'],
requestParameters: normalizeQueryStringParameters(event)
}
this.method = ''
if (isGatewayV1Event(event) === true) {
this.url.path = event.path
this.method = event.httpMethod
} else if (isGatewayV2Event(event) === true) {
this.url.path = event.requestContext.http.path
this.method = event.requestContext.http.method
}
this.transportType = lowerCaseHeaders['x-forwarded-proto']
}
}
/**
* This class captures data necessary to create a web transaction from the lambda's web
* response to API Gateway when used with API Gateway Lambda proxy. This is to be used
* with the setWebResponse method.
*/
class LambdaProxyWebResponse {
constructor(lambdaResponse) {
this.headers = normalizeHeaders(lambdaResponse)
this.statusCode = lambdaResponse.statusCode
}
}
/**
* normalizes query string parameters either from multi value query string parameters or normal query string parameters to a
* key map with comma separated strings
*
* @param {object} event The event with query string to normalize
* @returns {Object<string, string>} The normalized query string map
*/
function normalizeQueryStringParameters(event) {
if (!event.multiValueQueryStringParameters) {
return event.queryStringParameters
}
return Object.fromEntries(
Object.entries(event.multiValueQueryStringParameters).map(([param, value]) => {
if (Array.isArray(value)) {
return [param, value.join(',')]
}
return [param, value]
})
)
}
/**
* Normalizes both request and response headers,
* either from Multi Value headers or "normal" headers to a
* lowercase key map with comma separated string
*
* @param {object} event The event with headers to normalize
* @param {boolean} lowerCaseKey Whether to lowercase the header names or not
* @returns {Object<string, string>} The normalized headers map
*/
function normalizeHeaders(event, lowerCaseKey = false) {
const headers = event.multiValueHeaders ?? event.headers
if (!headers) {
return {}
}
return Object.fromEntries(
Object.entries(headers).map(([headerKey, headerValue]) => {
const newKey = lowerCaseKey ? headerKey.toLowerCase() : headerKey
if (Array.isArray(headerValue)) {
return [newKey, headerValue.join(',')]
}
return [newKey, headerValue]
})
)
}
/**
* Determines if Lambda event appears to be a valid Lambda Proxy event.
*
* @param {object} event The event to inspect.
* @returns {boolean} Whether the given object contains fields necessary
* to create a web transaction.
*/
function isLambdaProxyEvent(event) {
return isGatewayV1Event(event) || isGatewayV2Event(event)
}
function isGatewayV1Event(event) {
let result = false
if (event?.version === '1.0') {
result = true
} else if (
typeof event?.path === 'string' &&
(event.headers ?? event.multiValueHeaders) &&
typeof event?.httpMethod === 'string'
// eslint-disable-next-line sonarjs/no-duplicated-branches
) {
result = true
}
return result
}
function isGatewayV2Event(event) {
let result = false
if (event?.version === '2.0') {
result = true
} else if (
typeof event?.requestContext?.http?.path === 'string' &&
Object.prototype.toString.call(event.headers) === '[object Object]' &&
typeof event?.requestContext?.http?.method === 'string'
// eslint-disable-next-line sonarjs/no-duplicated-branches
) {
result = true
}
return result
}
/**
* Determines if Lambda event appears to be a valid Lambda Proxy response.
*
* @param {object} response The response to inspect.
* @returns {boolean} Whether the given object contains fields necessary
* to create a web transaction.
*/
function isValidLambdaProxyResponse(response) {
return !!(response && response.statusCode)
}
module.exports = {
LambdaProxyWebRequest,
LambdaProxyWebResponse,
isLambdaProxyEvent,
isValidLambdaProxyResponse
}