Sample Application - New Films in IMDB Top 250 - Part IV

Published on 20 October 2022

Part IV! In part I, I talked about what the sample application should achieve, and how I could go about that, and then went through the installation of AWS SAM (the AWS Serverless Application Model). In Part II, I created the first Lambda function that will get the contents of the IMDB Top 250 via a Lambda function, that was created, alongside a DynamoDB table using SAM. Part III was about a sam-app that would check for changes at IMDB and then email notifications out.

Part IV will be all about a really simple webpage that will take an email address that we could use for the subscribers list. I wanted to do that using servless tech of course, so no webservers here. We will place the email address into SES, so that when our app detects changes to the IMDB Top 250 list, it knows who it is supposed to notify.

I am not a developer and this isn't a professional app, so to keep it as simple as possible, it is basically just an input box on a webpage.

So how does it work? We will have four components:

  1. A lambda function, which will be triggered from our page, and talk to SES (created by SAM)
  2. An S3 bucket to host our site (also created by SAM)
  3. Our API endpoint that will be called by the webpage
  4. Our incredibly simple web-page

The first thing I needed to do is create another sam-app. I called mine sam-app3, imaginatively.

Then if we look at the Lambda function we need to create, this is pretty simple. It will talk to SES to add the email address to our list, and then it will return a message saying that it has been added. Otherwise, you have a box and button, but no feedback.

So the first part in lambda_handler is going to be the code to add the email address to SES. that is quite straight forward, and we just pass it in. Ideally we could actually do some checks on response here too, but that is for another day:

    def lambda_handler(event, context):
        print(event)
    
    
        response = ses.verify_email_identity(
    
    
            EmailAddress = event['queryStringParameters']['emailaddress']
    
    
        )

The second part of the function is what we return. here we need to return some headers, because we running from a bucket that will have a different name than our site name, and then we have the actual email address included in the message.

return {
    'headers': {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
        "Content-Type": "application/json"
        
    },
    'statusCode': response['ResponseMetadata']['HTTPStatusCode'],
    'body': 'Address ' + event['queryStringParameters']['emailaddress'] + ' added to SES.'
}

We will need to include that lambda function in our sam-app, where we will also create the S3 bucket that we are going to use. Creating the S3 bucket this way (rather than manually) means that we are not specifying the name, so it will be repeatable, without having to worry about name conflicts. To do that, we will add the following to our SAMs template.yaml.

      S3Bucket:
        Type: AWS::S3::Bucket
        Properties:
          AccessControl: PublicRead
          WebsiteConfiguration:
            IndexDocument: index.html
            ErrorDocument: error.html
        DeletionPolicy: Retain

We will also need the API endpoint that we will trigger from the webpage, that will call the lambda function.

So the full code, which I placed in src/python.py was:

    import boto3
    import json
    import os

    print('Loading function')
    # client created outside of the handler
    region_name = os.environ['REGION_NAME']
    ses = boto3.client('ses', region_name=region_name)



    def lambda_handler(event, context):
        print(event)
        response = ses.verify_email_identity(
            EmailAddress = event['queryStringParameters']['emailaddress']
        )

        return {
            'headers': {
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
                "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,       X-Amz-Security-Token",
                "Content-Type": "application/json"

            },
            'statusCode': response['ResponseMetadata']['HTTPStatusCode'],
            'body': 'Address ' + event['queryStringParameters']['emailaddress'] + ' added to SES.'
        }

We then need to template.yaml for our app, which will create the S3 bucket, create out lambda function, and our API.

    AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: >
        sam-app3
    
        Sample SAM Template for sam-app3
    
    # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/         docs/globals.rst
    Globals:
        Function:
        Timeout: 5
    
    Resources:
        HelloWorldFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/            awslabs/serverless-application-model/blob/master/versions/2016-10-31.           md#awsserverlessfunction
        Properties:
            CodeUri: src/subscribe
            Handler: python.lambda_handler
            Runtime: python3.9
            Policies:
            #  - SESCrudPolicy: 
            #      IdentityName: '*'
            - Version: '2012-10-17'
                Statement:
                - Effect: Allow
                    Action:
                    - 'ses:SendEmail'
                    - 'ses:VerifyEmailIdentity'
                    Resource: '*'
            Environment:
            Variables:
                REGION_NAME: !Ref AWS::Region
            Architectures:
            - x86_64
            Events:
            IMDBTop250:
                Type: Api # More info about API Event Source: https://github.com/awslabs/         serverless-application-model/blob/master/versions/2016-10-31.md#api
                Properties:
                Path: /films
                Method: post
        #APIGateway:
        #  Type: AWS::Serverless::Api
        #  Properties: 
        #    StageName: Prod
        #    Cors: "'*'"
        #      AllowMethods: "'GET, POST, OPTIONS'"
        #      AllowOrigin: "'*'"
        #      AllowHeaders: "'Content-type, x-api-key'"
    
        S3Bucket:
        Type: AWS::S3::Bucket
        Properties:
            AccessControl: PublicRead
            WebsiteConfiguration:
            IndexDocument: index.html
            ErrorDocument: error.html
        DeletionPolicy: Retain
    
    
    Outputs:
        # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
        # Find out more about other implicit resources you can reference within SAM
        # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/         generated_resources.rst#api
        HelloWorldApi:
        Description: "API Gateway endpoint URL for Prod stage for Hello World function"
        Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/            films/"
        HelloWorldFunction:
        Description: "Hello World Lambda Function ARN"
        Value: !GetAtt HelloWorldFunction.Arn
        HelloWorldFunctionIamRole:
        Description: "Implicit IAM Role created for Hello World function"
        Value: !GetAtt HelloWorldFunctionRole.Arn
        S3BucketName:
        Description: "S3 Bucket URL"
        Value: !GetAtt S3Bucket.WebsiteURL
    

Lastly, we need to the .HTML for our web page. I saved the following as index.html, and uploaded it to my bucket once I created it via SAM deployment. Basically, it is a form that will execute some javascript, that will run a POST. The actual URL will be different for you, so you will need to change that once you have deployed your app.

        <form>  
        Email Address:  
        <input id="useremail"> 
        <br>
        <br>
        <button type="button" onClick="UserAction()">Press to subscribe!</button>

        <script>

        function UserAction() {
            const userEmail = document.getElementById("useremail").value
            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function() {
                 if (this.readyState == 4 && this.status == 200) {
                     alert(this.responseText);
                 }
            };


            xhttp.open("POST", `https://34kgff6cil.execute-api.us-east-1.amazonaws.com/Prod/films?          emailaddress=${userEmail}`, true);
            xhttp.setRequestHeader("Content-type", "application/json");
            xhttp.send();
        }
        </script>

Like with our previous SAM app, we can then build and deploy.

sam deploy --guided

You can then see the components (API, S3 bucket, lambda function, and .html file) through the console.

Open you index.html file, and you should get your webpage:

Function

You can then enter you email address, press submit, and viola!

Function

What Next?

That is really it for me for the moment, but you would want to schedule your lambda functions to run, which you can do via Cloudwatch, and you could probably think about how replace some of your scheduling with events, to be even more cloud native!

But that is basically it for a 1.0 pilot. Using SAM, we have created a some an application that users can subscribe to, that will update them based on changes in the IMDB Top 250, using lambda functions, a bit of HTML, a bit of DynamoDB, API Gateway, and SES. All serverless!

comments powered by Disqus