Skip to content

Hosting a website on Lambda Function URL

In the past, I did a lot of Perl programming and a lot of CGI code that still run on various websites around the world today. When I migrated my knowledge across to AWS and serverless infrastructure, I found myself having to develop code in a similar structure to what I did previously in Perl and PHP.

With this article, the goal is to demonstrate how you could dynamically generate HTML in a Lambda function, and use Lambda as a make-shift web server for some simple applications using Python.

Setting up the infrastructure

  • Log onto AWS, and open up the Lambda console.
  • Create a new function
  • Ensure Author from scratch is selected
  • Give the function a name
  • Select Python 3.9 as the runtime.
  • Leave everything else as is, and click Create function

Wait for the function to get created. Once it is created,

  • Click on Configuration
  • Select Function URL
  • Click on Create function URL
  • Under Auth type select NONE
  • Leave the permissions as is, and click Save

At this point, the function URL will be created. You can open this in a new browser window -- this is what will become our dummy website.

Sending some HTML back to the browser

If you happen to click the link that you just created, you will likely see the message "Hello from Lambda!". That's great - that means everything is working, but it's hardly a website. Let's tweak this a little, and start to send some content back to the browser.

Go into your Lambda function, edit the code, paste the following blob of code in, and save the function.

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'headers': { 'Content-Type': 'text/html' },
        'body': '<html><h1>Hello</h1><p>Lambda rocks!</p></html>'
    }

This code should be relatively self-explanatory - we set the status code, provide a content type, and then send some HTML back to the browser. When you run the function code again, you should be able to see the "Lambda rocks!" message.

Reading a _GET variable

Typically a GET method is where the data is embedded in the URL. Let's retrieve the variable called myVar from the URL.

def lambda_handler(event, context):

    # Read a GET variable
    myVar = event.get('queryStringParameters',{}).get('myVar')

    return {
        'statusCode': 200,
        'headers': { 'Content-Type': 'text/html'},
        'body': f'<html><h1>Hello</h1><p>Your GET variable is {myVar}</p></html>'
    }

Update your code in the Lambda function, and execute the Lambda function URL with the added parameter

https://xxxxx.lambda-url.ap-southeast-2.on.aws/?myVar=123

Reading a _POST variable

Reading a POST variable is a little bit more complicated. We have to parse the body being sent before we can query the variable itself.

from urllib import parse
import base64

def lambda_handler(event, context):
    # Read a POST variable
    if event.get('isBase64Encoded'):
        body = base64.b64decode(event.get('body',b'')).decode('UTF-8')
    else:
        body = event.get('body',b'').decode('UTF-8')
    parsedBody = parse.parse_qs(body)
    myVar = parsedBody.get('myVar',[ '' ])[0]

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/html',
        },
        'body': f'''<html>
            <h1>Hello</h1>
            <form method=post>
                Enter a variable : <input type=text name=myVar>
                <input type=submit>
            </form>
            <p>Your POST variable was {myVar}</p>
            </html>'''
    }

Reading a _SERVER variable

If you're familiar with the $_SERVER variables in PHP, then you'll see what we're doing here. The key variables are also available within the Lambda Function. I've built some code to extract the key variables that you may need in your application development.

def lambda_handler(event, context):
    # Define our known server variables
    SERVER = {
        'REMOTE_ADDR'       : event['requestContext']['http']['sourceIp'],
        'HTTP_USER_AGENT'   : event['requestContext']['http']['userAgent'],
        'REQUEST_METHOD'    : event['requestContext']['http']['method'],
        'SCRIPT_URI'        : event['requestContext']['http']['path'],
        'SERVER_PROTOCOL'   : event['requestContext']['http']['protocol'],
        'QUERY_STRING'      : event['rawQueryString']
    }

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/html',
        },
        'body': f'''<html>
            <h1>Hello</h1>
            <p>Your IP address is {SERVER['REMOTE_ADDR']}</p>
            </html>'''
    }

As you can see, this piece of code simply returns your IP address.

Cookies

When we want to set a cookie variable, we simply need to send the data through the Set-Cookie header

def lambda_handler(event, context):

    cookieVariable = "this is how the cookie crumble"

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/html',
            'Set-Cookie' : f"cookieVariable={cookieVariable}; Secure; HttpOnly;",
        },
        'body': f'''<html>
            <h1>Hello</h1>
            </html>'''
    }

CAVEAT -- There is a bug in Lambda Function URL that prevents you from sending multiple cookies of the same name. If you're trying to send more than one, it will not work. Watch this space for more information as the investigation unfolds.

Here's some code to allow you to read whatever cookie has been returned by the browser.

def lambda_handler(event, context):
    cookies = {}
    for c in event.get('cookies',[]):
        (k,v) = c.split('=',1)
        cookies[k] = v

    return {
        'statusCode': 200,
        'headers': { 'Content-Type': 'text/html' },
        'body': f'''<html>
            <h1>Hello</h1>
            <p>Your cookie variable is {cookies['cookieVariable']}</p>
            </html>'''
    }

More information

  • https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html
  • https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads