Skip to content

Commit 3232ac2

Browse files
authored
fix: crash in Lambda handler with ELB or API Gateway event with no 'headers' (#3289)
If the lambda handler object for an ELB- or API Gateway-triggered Lambda invocation did not have a 'headers' field, then the Lambda instrumentation would crash. I'm not sure how to get an event with no 'headers' field, but we should still be defensive here. Fixes: #3286
1 parent 530cc05 commit 3232ac2

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

lib/lambda.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function setApiGatewayData (agent, trans, event, context, faasId, isColdStart) {
144144
: undefined),
145145
method: requestContext.http.method,
146146
url: event.rawPath + (event.rawQueryString ? '?' + event.rawQueryString : ''),
147-
headers: event.normedHeaders,
147+
headers: event.normedHeaders || {},
148148
socket: { remoteAddress: requestContext.http.sourceIp },
149149
body: event.body
150150
}
@@ -162,7 +162,7 @@ function setApiGatewayData (agent, trans, event, context, faasId, isColdStart) {
162162
url: requestContext.path + (event.queryStringParameters
163163
? '?' + querystring.encode(event.queryStringParameters)
164164
: ''),
165-
headers: event.normedHeaders,
165+
headers: event.normedHeaders || {},
166166
socket: { remoteAddress: requestContext.identity && requestContext.identity.sourceIp },
167167
// Limitation: Note that `getContextFromRequest` does *not* use this body,
168168
// because API Gateway payload format 1.0 does not include the
@@ -264,7 +264,7 @@ function setElbData (agent, trans, event, context, faasId, isColdStart) {
264264
event.queryStringParameters && Object.keys(event.queryStringParameters) > 0
265265
? '?' + querystring.encode(event.queryStringParameters)
266266
: ''),
267-
headers: event.normedHeaders,
267+
headers: event.normedHeaders || {},
268268
body: event.body,
269269
bodyIsBase64Encoded: event.isBase64Encoded
270270
}

lib/parsers.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const {
2828
* However, some cases (e.g. Lambda and Azure Functions instrumentation)
2929
* create a pseudo-req object that matches well enough for this function.
3030
* Some relevant fields: (TODO: document all used fields)
31+
* - `headers` - Required. An object.
3132
* - `body` - The incoming request body, if available. The `json` and
3233
* `payload` fields are also checked to accomodate some web frameworks.
3334
* - `bodyIsBase64Encoded` - An optional boolean. If `true`, then the `body`
@@ -109,9 +110,21 @@ function getContextFromResponse (res, conf, isError) {
109110
return context
110111
}
111112

113+
/**
114+
* Extract appropriate `{transaction,error}.context.user` from an HTTP
115+
* request object.
116+
*
117+
* @param {Object} req - Typically `req` is a Node.js `http.IncomingMessage`.
118+
* However, some cases (e.g. Lambda and Azure Functions instrumentation)
119+
* create a pseudo-req object that matches well enough for this function.
120+
* Some relevant fields: (TODO: document all used fields)
121+
* - `headers` - Required. An object.
122+
*/
112123
function getUserContextFromRequest (req) {
113124
var user = req.user || basicAuth(req) || req.session
114-
if (!user) return
125+
if (!user) {
126+
return
127+
}
115128

116129
var context = {}
117130

test/lambda/transaction2.test.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
// "AgentMock". The mock doesn't fully test the "transaction" intake event
1313
// object creation path.)
1414

15+
const fs = require('fs')
16+
const path = require('path')
17+
1518
const lambdaLocal = require('lambda-local')
1619
const tape = require('tape')
1720

@@ -35,7 +38,7 @@ process.env.AWS_LAMBDA_LOG_STREAM_NAME = '2021/11/01/[1.0]lambda/e7b05091b39b4aa
3538
process.env.AWS_PROFILE = 'fake'
3639

3740
function loadFixture (file) {
38-
return require('./fixtures/' + file)
41+
return JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', file)))
3942
}
4043

4144
tape.test('lambda transactions', function (suite) {
@@ -215,8 +218,41 @@ tape.test('lambda transactions', function (suite) {
215218
t.equal(trans.context.request.method, 'POST', 'transaction.context.request.method')
216219
t.deepEqual(trans.context.response, { status_code: 502, headers: {} }, 'transaction.context.response')
217220
}
221+
},
222+
{
223+
name: 'API Gateway event, but without ".headers" field: APM agent should not crash',
224+
event: (function () {
225+
const ev = loadFixture('aws_api_http_test_data.json')
226+
delete ev.headers
227+
return ev
228+
})(),
229+
handler: (_event, _context, cb) => {
230+
cb(null, { statusCode: 200, body: 'hi' })
231+
},
232+
checkApmEvents: (t, events) => {
233+
const trans = events[1].transaction
234+
t.equal(trans.name, 'POST /default/the-function-name', 'transaction.name')
235+
t.equal(trans.outcome, 'success', 'transaction.outcome')
236+
}
237+
},
238+
{
239+
name: 'ELB event, but without ".headers" field: APM agent should not crash',
240+
event: (function () {
241+
const ev = loadFixture('aws_elb_test_data.json')
242+
delete ev.headers
243+
return ev
244+
})(),
245+
handler: (_event, _context, cb) => {
246+
cb(null, { statusCode: 200, body: 'hi' })
247+
},
248+
checkApmEvents: (t, events) => {
249+
const trans = events[1].transaction
250+
t.equal(trans.faas.name, 'fixture-function-name', 'transaction.faas.name')
251+
t.equal(trans.outcome, 'success', 'transaction.outcome')
252+
}
218253
}
219254
]
255+
220256
testCases.forEach(c => {
221257
suite.test(c.name, { skip: c.skip || false }, function (t) {
222258
const handler = c.handler || (

0 commit comments

Comments
 (0)