Spring Cloud function cannot read function definition from Message header - amazon-web-services

I've created an AWS Lambda based Spring Cloud Function with multiple functions and it works fine when I define exactly one "spring_cloud_function_definition" environment variable in the AWS Lambda Configuration panel. These functions' input and output is a Message<?>, so the payload and http headers can be accessed runtime as well.
I'd like to use this AWS Lambda function in different API Gateway methods, so for example:
/item [POST] -> saveItem should be called
/item [DELETE] -> deleteItem should be called
... and so on...
https://cloud.spring.io/spring-cloud-static/spring-cloud-function/3.0.0.RC1/reference/html/spring-cloud-function.html#_function_catalog_and_flexible_function_signatures
I found this documentation on Spring.io which says it's feasible:
If the input argument is of type Message<?>, you can communicate routing instruction by setting one of spring.cloud.function.definition or spring.cloud.function.routing-expression Message headers. For more static cases you can use spring.cloud.function.definition header which allows you to provide the name of a single function (e.g., …​definition=foo) or a composition instruction (e.g., …​definition=foo|bar|baz)
So the following test event should run in AWS Lambda without any failure:
{
"method": "POST",
"body": "{ "key" : "value" }",
"headers": {
"spring.cloud.function.definition": "saveItem"
}
}
but of course it throws an exception:
No function defined: java.lang.IllegalStateException
java.lang.IllegalStateException: No function defined
at org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer.apply(AbstractSpringFunctionAdapterInitializer.java:187)
at org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler.handleRequest(SpringBootRequestHandler.java:51)
at org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler.handleRequest(SpringBootApiGatewayRequestHandler.java:140)
at org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler.handleRequest(SpringBootApiGatewayRequestHandler.java:43)
I've tried many configurations (application.properties, AWS Lambda env property, header, etc.) but nothing happened:
spring.cloud.function.routing-expression=headers.function_name
setting spring.cloud.function.definition=saveItem
What could be the problem? What is the best configuration in this case? Any help would be appreciated!
Used technologies:
Cloud: AWS
Cloud components: Lambda + API Gateway
AWS Lambda handler class: org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler
AWS Lambda input and output events: APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent
Java: 8
Spring Cloud Function version: 3.0.6.RELEASE

I've updated to v3.1.3 and switched to FunctionInvoker and I've added the function definition to the request template mapping header, and it worked fine.
Anyways, thanks for the help. :)

Related

How to build request handler class in lambda function for a Springboot CRUD Application?

I am trying to invoke a lambda function by the new feature - function URL"S https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html using which I don't require any trigger events.
I am able to get the output for a simple java application using the above approach written in documentation by using Java as runtimeEnvironment and passing Jar file from my local machine.
Now, I want to test lambda functionality by same concept but for Springboot-Application which has restful endpoints for GET,POST,PUT,DELETE for different crud operations connecting with MySql db. The problem I am facing is how to tell handler class that to route to these endpoints and trigger lambda functions.
Any help/suggestions appreciated here. Thanks in Advance !
It probably won't work with this URL's feature but it allows handling Lambda events similar to standard Spring web requests.
You can check this one: https://github.com/MelonProjectCom/lambda-http-router
Example usage:
#PathPostMapping("/example/test")
public String example(#Body String body) {
return "Hello World! - body: " + body;
}

How can I create a mapping template to map path parameter to lambda function in apigateway?

I am using serverless to deploy API gateway and lambda using lambda integration. Below is the infra code:
myLambda:
...
events:
- http:
path: customer/{id}
method: delete
integration: lambda
request:
parameters:
paths:
id: true
template:
text/plain: "$input.params('id')"
It creates a delete API to delete customer based on the id specified in the path parameter. I'd like to map the path id to myLambda's event parameter in its handler. But the above code doesn't work the lambda receives the whole http request from API gateway. How can I map the id parameter to the event parameter in lambda?
When you are using API Gateway and Lambda functions, you have 2 options:
Lambda proxy
Lambda integration
By default, the Serverless framework is using Lambda proxy. This way of connecting to a Lambda function is a lot quicker. It passes the whole HTTP request to a Lambda function and it lets you handle the request completely on the Lambda function side.
There is another way, as I mentioned, it's Lambda integration, without using Lambda proxy. If you want to use this method, you need to set up mappings on your request and your response. This way is "cleaner" on the LAmbda code side, because Lambda received mapped parameters in the event, compared to the whole HTTP request as in proxy. On the other hand, this kind of integration is more complicated to set up, as you need to make all the mappings, and it uses VTL (Apache Velocity Template Language) to do that.
In order to set this up using Serverless, take a look at the "Lambda Integration" section in Serverless framework API Gateway documentation:
I believe that ApiGateway event itself should contain that query / path parameter key value pair and you can directly query your parameters from the event, if you'd go with Lambda Proxy method which #Caldazar mentioned above.
For eg let's say if you have a URL
https://example.execute-api.us-west-2.amazonaws.com/prod/confirmReg?token=12345&uid=5
you could simply retrieve the values as below from your handler:
token = event['queryStringParameters']['token']
uid = event["queryStringParameters"]['uid']
EDIT:
Please follow the below steps for Lambda Integration:
Go to your respective API resource method and click on Integration Request section
Find the Mapping Templates area of the Integration request and open it up. Add a new mapping template for the application/json Content-Type. You'll need to change the type in the combo box from "Input passthrough" to "Mapping Template".
Then paste the following piece of JSON inside the template:
{
"id" : "$input.params('id')"
}
This way you'll not end up receiving the entire event object inside your lambda but only your path param which is sent in your request.
Some references: 1 & 2

How to send multi part key value via http get

I am trying to learn to use AWS Lambda, DynamoDB, and API Gateway.
I am able to set up a run a successful test of the Lambda that would take querystring parameters and returns the data from the database and I know I have the ability to access the Lambda from my API as I have created one to just return hello world. However, I am stuck as to how to send the parameters I need to the Lambda. These work fine from the Lambda test feature within Lambda.
{
"queryStringParameters": {
"TableName": "Exams",
"Key": {
"ExamName": "MyFirstExam"
}
}
}
However I can not seem to create a html query http://apiurl/route?parameters
as I have no idea how to send the Key.
I have tried everything I can think of here is just a sample of what I have tried
http://apiurl/route?TableName=Exams&Key=ExamName&ExamName=MyFirstExam
http://apiurl/route?TableName=Exams&key=ExamName&Keyvalue=MyFirstExam
http://apiurl/route?TableName=Exams&key=ExamName&value=MyFirstExam
http://apiurl/route?TableName=Exams&key=ExamName:MyFirstExam
but nothing has worked so I am wondering how do you send key = ExamName : MyfirstExam so that it is a part of the querystring parameters?
UPDATE:
I found that the rest api gave me more feed back and it looks like I need to use
````http://apiURL/Route?TableName= Exams&Key= { ExamName: MyFirstExam } ```
as this populates the querystring parameters.
"queryStringParameters":{"TableName":"Exams","Key":"{ ExamName: MyFirstExam }"}
However, I am still getting an error. That leads me to believe that I still may not have the format right. The problem is that when I used the same data in the test from within the lambda console it works and returns the data. When I use the querystring above which appears to be the same data as used in the lambda test it throws an error.
Lambda execution failed with status 200 due to customer function error: The provided key element does not match the schema. So my Key is getting there as I get a status 200 and if I leave the key off of the querystring it throws and error that the key is missing but it appears to not be formatted correctly apparently or is it something else that I am missing. It appears to be the same as the format that is working in the Lambda test.
This works from the lambda test
{ "queryStringParameters": { "TableName": "Exams", "Key": { "ExamName": "MyFirstExam" } } }
this does not work from the API test
"queryStringParameters":{"TableName":"Exams","Key":"{ ExamName: MyFirstExam }"}
This is how I am formatting the querystring
http://apiURL/Route?TableName= Exams&Key= { ExamName: MyFirstExam }
How do I format a querystring so that it works from the API gateway?
The easiest and most commonly used method of joining API Gateway and lambda is through AWS_PROXY integration between your API and your function.
With this type of integration:
API Gateway applies a default mapping template to send the entire request to the Lambda function and transforms the output from the Lambda function to HTTP responses.
This means that every query parameter you provide through your API endpoint is going to be delivered directly into lambda function:
This request data includes the request headers, query string parameters, URL path variables, payload, and API configuration data.
The event format your lambda function gets is described here.
I resolved this by having the key part already in the code and just now only need to pass a normal variable ExamName=Exam.
var queryParameters ={
TableName: request.TableName,
Key:{
"ExamName": request.ExamName
},
};```

Api gateway get output results from step function?

I followed tutorial on creating and invoking step functions
I'm getting output in my GET request of api as
{
"executionArn": "arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorld:MyExecution",
"startDate": 1.486772644911E9
}
But, instead of above response I want my step functions output, which is given by end state as below.
{
"name":"Hellow World"
}
How to achieve this?
Update: You can now use Express Step Functions for synchronous requests.
AWS Step Functions are asynchronous and do not immediately return their results. API Gateway methods are synchronous and have a maximum timeout of 29 seconds.
To get the function output from a Step Function, you have to add a second method in API Gateway which will call the Step Function with the DescribeExecution action. The API Gateway client will have to call this periodically (poll) until the returned status is no longer "RUNNING".
Here's the DescribeExecution documentation
Use Express Step Functions instead. This type of Step Functions can be called synchronously. Go to your API Gateway and in the Integration Request section make sure you have the StartSyncExecution action:
After that, go a bit lower in the same page to the Mapping Templates:
and include the following template for the application/json Content-Type:
#set($input = $input.json('$'))
{
"input": "$util.escapeJavaScript($input)",
"stateMachineArn": "arn:aws:states:us-east-1:your_aws_account_id:stateMachine:your_step_machine_name"
}
After that, go back to the Method Execution and go to the Integration Response and then to the Mapping Templates section:
And use the following template to have a custom response from your lambda:
#set ($parsedPayload = $util.parseJson($input.json('$.output')))
$parsedPayload
My testing Step Function is like this:
And my Lambda Function code is:
Deploy your API Gateway stage.
Now, if you go to Postman and send a POST request with any json body, now you have a response like this:
New Synchronous Express Workflows for AWS Step Functions is the answer:
https://aws.amazon.com/blogs/compute/new-synchronous-express-workflows-for-aws-step-functions/
Amazon API Gateway now supports integration with Step Functions StartSyncExecution for HTTP APIs:
https://aws.amazon.com/about-aws/whats-new/2020/12/amazon-api-gateway-supports-integration-with-step-functions-startsyncexecution-http-apis/
First of all the step functions executes asynchronously and API Gateway is only capable of invoking the step function (Starting a flow) only.
If you are waiting for the results of a step function invocation from a web application, you can use AWS IOT WebSockets for this. The steps are as follows.
Setup AWS IOT topic with WebSockets.
Configure the API Gateway and Step functions invocation.
From the Web Frontend subscribe to the IOT Topic as a WebSocket listener.
At the last step (And in error steps) in the Step Functions workflow use AWS SDK to trigger the IOT Topic which will broadcast the results to the Web App running in the browser using WebSockets.
For more details on WebSockets with AWS IOT refer the medium article Receiving AWS IoT messages in your browser using websockets.
Expanding on what #MikeD at AWS says, if you're certain that the Step Function won't exceed the 30 second timeout, you could create a lambda that executes the step function and then blocks as it polls for the result. Once it has the result, it can return it.
It is a better idea to have the first call return immediately with the execution id, and then pass that id into a second call to retrieve the result, once it's finished.

Api Gateway: AWS Subdomain for Lambda Integration

I'm attempting to integrate my lambda function, which must run async because it takes too long, with API gateway. I believe I must, instead of choosing the "Lambda" integration type, choose "AWS Service" and specify Lambda. (e.g. this and this seem to imply that.)
However, I get the message "AWS ARN for integration must contain path or action" when I attempt to set the AWS Subdomain to the ARN of my Lambda function. If I set the subdomain to just the name of my Lambda function, when attempting to deploy I get "AWS ARN for integration contains invalid path".
What is the proper AWS Subdomain for this type of integration?
Note that I could also take the advice of this post and set up a Kinesis stream, but that seems excessive for my simple use case. If that's the proper way to resolve my problem, happy to try that.
Edit: Included screen shot
Edit: Please see comment below for an incomplete resolution.
So it's pretty annoying to set up, but here are two ways:
Set up a regular Lambda integration and then add the InvocationType header described here http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html. The value should be 'Event'.
This is annoying because the console won't let you add headers when you have a Lambda function as the Integration type. You'll have to use the SDK or the CLI, or use Swagger where you can add the header easily.
Set the whole thing up as an AWS integration in the console (this is what you're doing in the question), just so you can set the InvocationType header in the console
Leave subdomain blank
"Use path override" and set it to /2015-03-31/functions/<FunctionARN>/invocations where <FunctionARN> is the full ARN of your lambda function
HTTP method is POST
Add a static header X-Amz-Invocation-Type with value 'Event'
http://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html
The other option, which I did, was to still use the Lambda configuration and use two lambdas. The first (code below) runs in under a second and returns immediately. But, what it really does is fire off a second lambda (your primary one) that can be long running (up to the 15 minute limit) as an Event. I found this more straightforward.
/**
* Note: Step Functions, which are called out in many answers online, do NOT actually work in this case. The reason
* being that if you use Sequential or even Parallel steps they both require everything to complete before a response
* is sent. That means that this one will execute quickly but Step Functions will still wait on the other one to
* complete, thus defeating the purpose.
*
* #param {Object} event The Event from Lambda
*/
exports.handler = async (event) => {
let params = {
FunctionName: "<YOUR FUNCTION NAME OR ARN>",
InvocationType: "Event", // <--- This is KEY as it tells Lambda to start execution but immediately return / not wait.
Payload: JSON.stringify( event )
};
// we have to wait for it to at least be submitted. Otherwise Lambda runs too fast and will return before
// the Lambda can be submitted to the backend queue for execution
await new Promise((resolve, reject) => {
Lambda.invoke(params, function(err, data) {
if (err) {
reject(err, err.stack);
}
else {
resolve('Lambda invoked: '+data) ;
}
});
});
// Always return 200 not matter what
return {
statusCode : 200,
body: "Event Handled"
};
};