Reputation: 7136
I am using serverless to deploy aws lambdas. Currently my set up is that api gateway triggers one lambda then I async invoke another lambda.
serverless.yml
service: scraper
useDotenv: true #added this because deprecation notice
frameworkVersion: '2'
provider:
name: aws
runtime: nodejs14.x
lambdaHashingVersion: 20201021
stage: prod
region: us-east-2
functions:
scraperRunner:
handler: handler.scraperRunner
provisionedConcurrency: 0
timeout: 90
events:
- http:
path: process/run
method: post
async: true
runTargetSite:
handler: handler.runTargetSite
plugins:
- serverless-offline
then I have a handler.js file where I define both handler functions:
'use strict';
const startScraper = require('./crawler/runner');
const AWS = require('aws-sdk');
AWS.config.region = 'us-east-2';
const lambda = new AWS.Lambda();
function processResponse(msg, statusCode, event) {
return {
statusCode: statusCode,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type',
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify({
message: msg,
input: event
})
}
}
module.exports.scraperRunner = async (event) => {
if (event) {
const data = event;
if (data.hasOwnProperty('target_id')) {
const prov = [<array of objects>]
for (const p of prov) {
data['target'] = p.name
const params = {
FunctionName: 'provider-scraper-prod-runTargetSite',
InvocationType: 'Event',
// LogType: 'Tail',
Payload: JSON.stringify(data)
};
await lambda.invoke(params).promise()
}
return processResponse(
'Event successfully received',
200, event)
}
}
return processResponse(
'Invalid body payload',
402, event)
};
module.exports.runTargetSite = async function(event, context) {
await startScraper(event)
return processResponse(
'Event successfully received for runTargetSite',
200, event)
}
I've also added the necessary roles (I think):
Using the command line, I'm able to call the first lambda which then calls the second:
serverless invoke local --function scraperRunner --data '{"target": "_tpp_"}'
However when submitting a post request to the API Gateway endpoint, the first lambda (scraperRunner) triggers but the second one is not invoked.
Upvotes: 3
Views: 1566
Reputation: 2903
When you are using the serverless invoke local
command, you are invoking the lambda function directly with event defined as '{"target": "tpp"}'. When you are invoking the Lambda function through API Gateway, the API request body isn't directly sent to the Lambda function. Instead, based on how you are integrating your AWS API Gateway with Lambda, API Gateway will transform the incoming request. There are two possibilities here:
AWS_PROXY
integration with Lambda: The Lambda event would be in the following format:{
"resource": "/my/path",
"path": "/my/path",
"httpMethod": "GET",
"headers": {
"header1": "value1",
"header2": "value2"
},
"multiValueHeaders": {
"header1": [
"value1"
],
"header2": [
"value1",
"value2"
]
},
"queryStringParameters": {
"parameter1": "value1",
"parameter2": "value"
},
"multiValueQueryStringParameters": {
"parameter1": [
"value1",
"value2"
],
"parameter2": [
"value"
]
},
"requestContext": {
"accountId": "123456789012",
"apiId": "id",
"authorizer": {
"claims": null,
"scopes": null
},
"domainName": "id.execute-api.us-east-1.amazonaws.com",
"domainPrefix": "id",
"extendedRequestId": "request-id",
"httpMethod": "GET",
"identity": {
"accessKey": null,
"accountId": null,
"caller": null,
"cognitoAuthenticationProvider": null,
"cognitoAuthenticationType": null,
"cognitoIdentityId": null,
"cognitoIdentityPoolId": null,
"principalOrgId": null,
"sourceIp": "IP",
"user": null,
"userAgent": "user-agent",
"userArn": null,
"clientCert": {
"clientCertPem": "CERT_CONTENT",
"subjectDN": "www.example.com",
"issuerDN": "Example issuer",
"serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
"validity": {
"notBefore": "May 28 12:30:02 2019 GMT",
"notAfter": "Aug 5 09:36:04 2021 GMT"
}
}
},
"path": "/my/path",
"protocol": "HTTP/1.1",
"requestId": "id=",
"requestTime": "04/Mar/2020:19:15:17 +0000",
"requestTimeEpoch": 1583349317135,
"resourceId": null,
"resourcePath": "/my/path",
"stage": "$default"
},
"pathParameters": null,
"stageVariables": null,
"body": "Hello from Lambda!",
"isBase64Encoded": false
}
AWS
integration with Lambda: The Lambda event depends on integration request templates defined. The templates decide how the incoming request is converted to a Lambda event.Your current code assumes the API request body is equivalent to the event, which is not true. Due to this, if (data.hasOwnProperty('target_id'))
is false and we never enter the if condition.
In your serverless.yaml, you have defined your lambda function event trigger as:
events:
- http:
path: process/run
method: post
async: true
From the serverless framework documentation:
Use async: true when integrating a lambda function using event invocation. This lets API Gateway to return immediately with a 200 status code while the lambda continues running. If not otherwise specified integration type will be AWS.
Note that it says the specified integration type will be AWS. Serverless Framework automatically adds an integration request template, which results in the following request body format (Reference):
{
"body": {},
"method": "GET",
"principalId": "",
"stage": "dev",
"cognitoPoolClaims": {
"sub": ""
},
"enhancedAuthContext": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-GB,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "GB",
"Host": "ec5ycylws8.execute-api.us-east-1.amazonaws.com",
"upgrade-insecure-requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Via": "2.0 f165ce34daf8c0da182681179e863c24.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "l06CAg2QsrALeQcLAUSxGXbm8lgMoMIhR2AjKa4AiKuaVnnGsOFy5g==",
"X-Amzn-Trace-Id": "Root=1-5970ef20-3e249c0321b2eef14aa513ae",
"X-Forwarded-For": "94.117.120.169, 116.132.62.73",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"query": {},
"path": {},
"identity": {
"cognitoIdentityPoolId": "",
"accountId": "",
"cognitoIdentityId": "",
"caller": "",
"apiKey": "",
"sourceIp": "94.197.120.169",
"accessKey": "",
"cognitoAuthenticationType": "",
"cognitoAuthenticationProvider": "",
"userArn": "",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"user": ""
},
"stageVariables": {},
"requestPath": "/request/path"
}
async: true
results in no response body and only a status code of 200 for every API request. Looking at your code, I am not sure you want the Lambda to be invoked asynchronously, since you are defining responses in your code. In case you decide to remove async: true
from your serverless.yaml, the Serverless framework would switch to using AWS_PROXY
integration so your lambda event format would differ.
In both scenarios, you should be able to solve your issue by changing:
const data = event;
to
const data = JSON.parse(event.body);
When testing locally:
serverless invoke local --function scraperRunner --data '{"body":"{\"target\": \"_tpp_\"}"}'
Also, take a look at Amazon CloudWatch logging for AWS Lambda if you aren't already using it. This is enabled by default with the correct IAM permissions defined for the Lambda function, and is very useful in debugging any issues on deployed applications.
Upvotes: 1