Personal link shortener in AWS

Frederique Retsema

I like to do presentations about a lot of topics. Most of these presentations are recorded and the link to that recording is then shared to people who couldn’t attend the presentation live. Up to now I used bitly.com to shorten my URLs. Last time that I used bitly I made a small mistake. This lead to a link that wasn’t usable for my audience. Unfortunately I wasn’t able to correct my mistake, at least not without upgrading my bitly account to a paid account. This would cost me $29 per month, which is a lot of money for the few times per year I’m using this service.

But then I thought: wouldn’t it be nice to implement some kind of link shortener myself? It would use the API Gateway and a Lambda function to get the request, the DynamoDB server to store the short and long URLs and then I’d use Route53 to get a nice domain name. The total costs are not that high, because the API Gateway, Lambda and DynamoDB all have free tier prices for people who don’t use these services a lot. A Route53 domain costs € 10 per year, which is also not too much.

Let’s look at the architecture and then discuss all the AWS services starting with DynamoDB and Lambda and then looking at some features of the API Gateway:

You can play along by using the CloudFormation template in GitHub [1] to deploy all resources in one go, you can also choose to use the GUI to deploy this step-by-step. After the deployment of the stack, you can use the AddRecord.ps1 powershell command to add one record to the DynamoDB table.

DynamoDB

First, I created a table. I called it frlink after the DNS domain name I am using for my personal link shortener, which is frlink.nl. The table has short_url as partition key, it doesn’t have a sort key. The path is stored in the short url, for example /Demo2021-12-28 . The long URL is stored in an attribute called long_url, in the example it points to https://nos.nl

Lambda

The Lambda function doesn’t do a lot: it will check if the request is a GET request, and when it is a GET request it will then search the DynamoDB table for the path that is given. When the path is present in DynamoDB, it will set the status code to 302 (redirect) and return the long URL.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import boto3

def get_long_url(short_url):
  dynamodb = boto3.client('dynamodb')

  response = dynamodb.get_item(
	  TableName='frlink',
	  Key={
		  'short_url': { 'S': short_url }
	  },
	  AttributesToGet=[
		  'long_url'
	  ],
	  ConsistentRead=False,
	  ReturnConsumedCapacity='NONE'
  )
  print("Response from get_item:")
  print(response)

  return response["Item"]["long_url"]["S"]


def lambda_handler(event, context):

  statusCode = 403
  headers    = {}
  body       = {}

  try:
	  print(event)

	  if (event["httpMethod"] == "GET"):
		  location = get_long_url(event["path"])
		  print("New location: "+location)

		  statusCode = 302
		  headers = { "location": location }
		  body = {}
		  
  except Exception as e:
	  print("Error:")
	  print(e)
  
  return {
	  'statusCode': statusCode,
	  'headers': headers,
	  'body': json.dumps(body)
  }

API Gateway

In the API Gateway, I created a REST API. I wanted to get the URL with the full path that is requested. The API Gateway should not check for valid requests, it is the Lambda function that will do the checks. This is different than the normal behavior of the API gateway: in general you will want to specify each valid method and for each methods also the valid HTTP methods (GET, POST, etc). Only valid requests will be sent to specific Lambda functions to deal with each combination of method and HTTP request type.

I’m using the proxy functionality of the API gateway to achieve my goal: all HTTP request types and all requests to the API Gateway will be passed to the Lambda function. You can see in the text of the Lambda function that just GET requests are processed. All other requests will result in status 403 (Forbidden). When a short link is requested that cannot be found in the DynamoDB database, then a 403 error is returned as well. Valid requests get HTTP status code 302 (Found) and they will be redirected to the new location.

When you are playing along, then you can go to the Stages menu and then select prod. This will show the URL of the API Gateway. You can use this URL to test the code. You can ignore the stage name (in this example: prod). To test the previous example you can use https://3n5o9y1mt4.execute-api.eu-west-1.amazonaws.com/prod/Demo2021-12-28 and you will then be redirected to https://nos.nl .

When you select Custom Domain Names in the left menu, you will see the custom domain name frlink.nl. In the API Mappings tab, you can see that this domain name will use the prod stage of the API Gateway with the name PersonalLinkShortenerAPI .

Certificate manager

Though it is possible to automate the deployments of certificates and Route53 records, I didn’t do that.

I had some bad experiences in the past when I tried to automate the deployment of public certificates in ACM: there is a limit of 20 deployments per year. Though this can be increased by sending a ticket to AWS support, I didn’t like to run into issues (again). When you want to follow along, you can deploy a new certificate in the us-east-1 region (even though the API gateway is configured as a regional gateway!). When you open the certificate manager, you can see on the left the ARN of the certificate that you need to pass as a parameter of the CloudFormation template.

Route53

When you deployed your stack, then the last thing to do is to add an alias record in Route53. The alias will refer to a CloudFront distribution, even though the API Gateway is a regional gateway.

And… it works!

When I go to https://frlink.nl/Demo2021-12-28 then I am nicely re-routed to https://nos.nl . The advantage is that when I made a mistake and want to change the routing to https://rtl.nl where the original link name stays the same, I can do so without spending a lot of money: I just have to go to DynamoDB and change the url…

Links

[1] Link to GitHub: https://github.com/FrederiqueRetsema/AWS-Blogs-2021

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Next Post

Apache NiFi: Reading COVID data from a REST API and producing it to a Kafka topic

Apache NiFi can be used to accelerate big data projects by allowing easy integration between various data sources. Using Apache NiFi it is easy to track what happened to your data (data provenance) and to provide features like guaranteed ordered delivery and error handling. In this example I’m going to […]
%d bloggers like this: