Invoking AWS lambda functions from a Flask app


We have been hearing about serverless service for a while now. So what is serverless? ‘serverless’ means that you run a service without a server. In most cases, if you run a mobile app or an online web app, there are always some tasks to execute in the backend. For eg:- In a weather application, every time when the user clicks a button it gets the local weather information. This request involves doing a set of backend operations and fetch real-time data in response to this request. All that can certainly work well. But what happens when your app/workload spikes? Managing the code and compute resources carries a significant operating overhead. There may be cases where you would probably want to keep part of logic outside of the client. It is where AWS Lambda comes into picture. We can use AWS Lambda functions to expose the back end logic of our applications.

Note: You can find the code associated with this post on Qxf2’s GitHub
 

Why this post?

AWS Lambda offers a completely different approach for you to design your apps. We wanted to design our app in such a way that all the backend logic is done with the help of Lambda function. One way of doing it is, just invoking the Lambda function over the HTTP using Amazon API Gateway as the HTTP endpoint which will route the request to Lambda. If you don’t want to go through API gateway, you can just invoke the function directly from your application. In this post, we will be showing you one use case where we are directly invoking an AWS Lambda function from the web application. We have created an Flask app for calculating maximum and target heart rate for the given user and display the output on the web page. For the backend logic, we created an AWS Lambda and invoking it from the Flask app.


Lambda Overview

AWS Lambda lets you run code without provisioning or managing servers. You pay only for the compute time you consume – there is no charge when your code is not running. With Lambda, you can run code for virtually any type of application or backend service. Just upload your code and Lambda takes care of everything required to run your code. You can have the web application which can call one or more AWS Lambda functions that implement the logic you need. Those functions become your serverless back end.

When you call a function with AWS Lambda, you provide an event and a context in the input:

  • The event is the way to send input parameters for your function and is expressed using JSON syntax.
  • The context is used by the service to describe the execution environment and how the event is received and processed.

  • How does AWS Lambda Work?

    The AWS Lambda process is quite simple. Start off by writing the code in AWS Lambda. From there, set up the code to trigger from other AWS services, HTTP endpoints, web or mobile apps. AWS Lambda will only run the code when it’s triggered and will also only use the computing resources needed to run it. The user has to pay only for the compute time used.


    Defining our usecase – Sample Lambda web application

    We have built a sample Flask web application which calculates the maximum heart rate and based on this maximum heart rate calculates the target heart rate of the user. To brief, our sample Flask web application does the following:

    1. Creates a Lambda client using boto3.
    2. Gets the user input ‘Age’ using the HTTP Get method and builds a JSON file.
    3. Invokes an AWS Lambda function by passing AWS Lambda function name (where the computation logic resides), InvocationType and JSON data as parameters
    4. Displays the returned response on the browser along with a message.

    Pre-requisites: Setting up AWS – make sure you have a valid AWS account with console access.

    Note:- For this sample application, we have created a credentials.py under conf folder to store the aws_access_key_id, aws_secret_access_key, region and lambda function name details. To run the app, you need to update the conf/credentials.py file with the AWS credentials (access_key and secret_key) and region (For eg:- If your region is “Oregion”, enter ‘us-west-2’ in the region field) and specify the lambda function name.

    Lets dive into the code. We created a file named sample_lambda_heart_rate.py with following content:

    sample_lambda_heart_rate.py

    """
    This is a Flask application which calculates the target heart rate range based on the age of the user. 
    """
     
    from flask import Flask, jsonify, render_template, request
    import json
    import boto3
    import logging
    import conf.credentials as conf
     
    # create lambda client
    client = boto3.client('lambda',
                            region_name= conf.region,
                            aws_access_key_id=conf.aws_access_key_id,
                            aws_secret_access_key=conf.aws_secret_access_key)
     
    app = Flask(__name__)
    app.logger.setLevel(logging.ERROR)
     
    @app.route("/", methods=['GET', 'POST'])
    @app.route("/calculate_heartrate_range", methods=['GET', 'POST'])
    def calculate_heartrate_range():
        "Endpoint for calculating the heart rate range"
        if request.method == 'GET':
            #return the form
            return render_template('sample_lambda.html')
        if request.method == 'POST':
            #return the range        
            age = int(request.form.get('age'))        
            payload = {"age":age} 
            #Invoke a lambda function which calculates the max heart rate and gives the target heart rate range              
            result = client.invoke(FunctionName=conf.lambda_function_name,
                        InvocationType='RequestResponse',                                      
                        Payload=json.dumps(payload))
            range = result['Payload'].read()      
            api_response = json.loads(range)               
            return jsonify(api_response)    
     
    #---START OF SCRIPT(We only need this for local run)
    if __name__ == '__main__':
        app.run(host='127.0.0.1', port=6464, debug= True)

    The code is basically self-explanatory but let’s discuss briefly. First, we created a Lambda client using boto3. We use this client to invoke a Lambda function (which is created using AWS Console which we will discuss in below sections). Then we make a Flask object, use the ‘route’ decorator functions to define GET (Render a template when the route is triggered with GET method) and POST (post some data to that URL) and call a ‘run’ function when we run it locally (which you can confirm by calling python sample_lambda_heart_rate.py and visiting localhost:6464 in your browser.)


    Creating a Lambda function

    From the practical perspective, to define a new Lambda function or update an existing one, you need to upload your code as a zip or jar archive, then AWS Lambda does the rest. For simple functions, you can use the internal source code editor that is available online directly in the Lambda Console. We used internal source code editor for writing the code. Follow below steps for creating a lambda function.

      1) In the Lambda console panel, click on create function. Give your function a name, in our case, it is ‘patient_health_record’

      2) Select the runtime depending on the language you choose to use. We used Python2.7 for this example

      3) Lastly, give your function’s role a name and,

      4) From Policy Templates, select Simple Microservice permissions.

      5) Click on the Create Function and you will be taken to the next screen where you can provide the actual code.

    For more details on creating a Lambda function you may refer here


    About our AWS Lambda function

    We wrote an AWS Lambda function to use for our backend computation for calculating the target heart rate of the user. Within the inline code editor in the AWS Lambda Console, updated the lambda_handler function definition with below content.

    import json
     
    def lambda_handler(event, context):
        #lambda handler function
        print "handler started"
        age = event['age']
        calculate_max_heart_rate(age)
        calculate_low_heart_rate_range(event,context)
        calculate_high_heart_rate_range(event,context)
        return {
            "low": round(low_heart_rate_range),
            "high":round(high_heart_rate_range)}
     
     
    def calculate_max_heart_rate(age):
        # calculates maximum heart rate
        global max_heart_rate
        max_heart_rate = 220 - age
        return max_heart_rate
     
    def calculate_low_heart_rate_range(event,context):
        # calculates the low heart rate range
        global low_heart_rate_range
        low_heart_rate_range = max_heart_rate * .7
        return low_heart_rate_range
     
    def calculate_high_heart_rate_range(event,context):
        # calculates the high heart rate range
        global high_heart_rate_range
        high_heart_rate_range = max_heart_rate * .85
        return high_heart_rate_range

    The lambda_handler function is the default entry point for Lambda and takes two arguments, event and context. The event will be created by us and this event brings along a set of data with it and the context consists of the runtime information which will be supplied by AWS lambda. They both take the form of JSON objects.

    The rest of the code is pretty straightforward, by reading the JSON input data ‘age’ we are calculating the maximum heart rate. A rough rule of thumb for computing maximum heart rate is: 220 – age. In the method calculate_max_heart_rate() we are calculating the maximum heart rate for the given user by using this rule. Based on this maximum heart rate value, we are calculating the target heart rate range which is 70-85% of the maximum heart rate. For example, consider a person who is 35 years old. 220 minus the age (35) is 185. 70% of 185 is 129 and 85% of 185 is 157. So the target heart rate range is between 129 and 157 during exercise.

    Lambda console
    Lambda console

    Invoking the Lambda function

    Another really cool thing about AWS Lambda is that you can invoke a Lambda function through a web endpoint i.e. they can be triggered via HTTP calls. You can enable triggers from AWS API Gateway option or you can just invoke functions directly from the application. An AWS Lambda is only a function, so something will have to trigger that function. When the data source triggers the event, the details are passed to a Lambda handler function as parameters. In our code, we are using client.invoke() to invoke a Lambda function. We want to POST data to a URL and trigger the function. You can specify the function name you want to invoke along with the data and Invocation Type and it returns a dict response. In the above code, in the POST method, we created a JSON data and passed it to Payload parameter in the client.invoke(). The result is a JSON response.

    This operation invokes a Lambda function:

     
    result = client.invoke(FunctionName='patient_health_record',
                        InvocationType='RequestResponse',                                      
                        Payload=json.dumps(payload))

    Response from Lambda handler function

    The handler can return a value. Depending on the InvocationType you use when invoking the Lambda function the returned value is handled.

    • If you use the ‘RequestResponse’ invocation type (synchronous execution), AWS Lambda returns the result of the Python function call to the client invoking the Lambda function (in the HTTP response to the invocation request, serialized into JSON). For example, AWS Lambda console uses the RequestResponse invocation type, so when you invoke the function using the console, the console will display the returned value.
    • If the handler returns NONE, AWS Lambda returns null.
    • If you use the Event invocation type (asynchronous execution), the value is discarded.

    In our case, the Invocation Type is RequestResponse, so the response is returned to the method calling the Lambda function in the Flask app. The Lambda function returns a result Payload containing the target heart rate range values which we are loading into a JSON and displaying in the web page. See below code.

    range = result['Payload'].read()      
            api_response = json.loads(range)               
            return jsonify(api_response)

    Below is our web application with response details:

    Sample lambda heart rate application
    Sample lambda heart rate application

    Monitoring Lambda Applications

    AWS Lambda automatically monitors Lambda functions and report metrics to analyse the function invocation through Amazon CloudWatch. Lambda logs all requests handled by your function and also automatically stores logs generated by your code through Amazon CloudWatch Logs.

    Accessing Amazon CloudWatch Logs
    Accessing Amazon CloudWatch Logs

    You can get started with Amazon CloudWatch for free. Most AWS Services (EC2, S3, Kinesis, etc.) offer metrics automatically for free to CloudWatch. Many applications should be able to operate within these free tier limits. You can learn more about AWS Free Tier here.

    You can find the above code in our git repo


    In this post, we have gone over some of the AWS Lambda basics and created a simple Flask application and invoked a Lambda function from it. I hope you enjoyed reading this post!


    References

    1. Python Boto3 and AWS Lambda
    2. Using boto to invoke lambda functions
    3. Serverless Python Web Applications With AWS Lambda and Flask


    Leave a Reply

    Your email address will not be published. Required fields are marked *