How I used AWS Lambda to hack my way into eating at Septime

AWS Lambda applied to the real world: feeding me.

Delicious dessert

Currently living in Paris with my girlfriend and being big foodies, Septime was a restaurant we always wanted going to. It’s extremely popular as it’s currently ranked 35th best restaurant in the world, and my girlfriend was describing to me the near-impossible experience to get a booking there (needing to have connections and all).

Wanting to verify this claim, I headed to the restaurant booking platform and couldn’t book. Surely, I can subscribe to the wait list, but do I really want to? No.

https://module.lafourchette.com/en_GB/module/10889-d34ca#/54499/pdh

But one should never say something is “impossible” to a programmer. I was going to hack my way into Septime, doing what I do best: programming.


How to book Septime programmatically

One attentive eye would have noticed that the booking platform is not hosted on the restaurant website at http://www.septime-charonne.fr/en/ but instead on https://module.lafourchette.com.

Upon using the Chrome Web Developer Tools to analyze the network calls being made between my browser and the booking service, I stumbled upon an easy to use and completely unprotected REST API:

Omnomnom

After a bit of reverse engineering, I found out that two URLs are used in the booking system:

Bottom line, finding an available booking time is as easy as checking a few REST API endpoints every minute or so… But I don’t want to keep my computer turned-on 24/7 and do that as a CRON job.

We live in 2019, I know better and I’ll use AWS Lambda!


AWS Lambda and the Serverless Framework

AWS Lambda is super trendy and allows you to build APIs, automations, and a bunch of cool stuff — including CRON jobs running in the cloud — in a serverless fashion. On top of it, I’ll run my Lambda function often enough while remaining in the free tier.

The architecture is super simple: use CloudWatch Events to trigger your Lambda function every minute, and write code that’s serverless friendly. Let’s start with the code

Simple architecture

The code

You can find the source code here: https://github.com/simplesteph/aws-lambda-septime/blob/master/handler.py

The idea is simple: every time the AWS Lambda function is triggered, check all the REST API endpoints for every date (about 16 API calls) and analyze the output payload to see if any time is available. If so, send myself a Pushbullet notification and call it a day. But a few challenges await you if you’re running in AWS Lambda

Function Timeout

Doing 16 REST API calls will most likely make you go over the AWS Lambda timeout (default 3 seconds). Thankfully, we can increase it to up to 15 minutes, but in our case we’ll set this to 20 seconds using the Serverless framework:

functions:
septime:
handler: handler.septime
timeout: 20
memorySize: 128
events:
- schedule: rate(2 minutes)

Passing secrets

The Pushbullet API key is what could be considered a “secret”. So let’s do something somewhat secure and store it in the SSM Parameter Store and retrieve it when we run the serverless framework

functions:
septime:
handler: handler.septime
timeout: 20
memorySize: 128
events:
- schedule: rate(2 minutes)
environment:
PUSHBULLET_API_KEY: ${ssm:/septime/pushbullet_api_key~true}

Alternatively, one could encrypt it using KMS and decrypt it at runtime using Lambda, or retrieve directly from the SSM Parameter Store at runtime to Lambda (make sure your IAM permissions are correct then)

Handling State

When a booking time is available, I want to make sure my phone doesn’t get notified every 2 minute. I just want to retrieve my notifications once for every change in the booking state. Using the fact that my AWS Lambda function container will be kept warm as I call it often enough, I can store the state as a global variable:

# contains a cache of the previous results
previous_result = {}

def septime(event, context):
global previous_result
result
= do()
# only notify when the results have changed
if result != previous_result:
previous_result = result
if result
["resas"]:
print("Sending notification to pushbullet")
send_to_pushbullet(json.dumps(result), os.environ.get("PUSHBULLET_API_KEY"))
else:
print("no available resa")

Alternatively, I could have used a DynamoDB table with on-demand enabled in case the state is lost to recover it properly (on-demand for DynamoDB is a cool new feature from AWS Re-Invent 2018)

Packaging

As I’m relying on a requirements.txt file to package my python dependencies, it’s great to use the serverless python requirements (https://github.com/UnitedIncome/serverless-python-requirements) plugin to package my function properly for me:

plugins:
- serverless-python-requirements

Deploying

Easy as 1..2..3 with the Serverless Framework:

sls deploy -v

Monitoring

It’s nice to know your AWS Lambda function is working properly using the CloudWatch dashboard for Lambda:

Great to see my function execution speed versus the timeout

The Results

Well, the results have been quite surprising… my Lambda function was working properly, but only returned results for lunch times (and weekdays, as the restaurant is closed on the weekend):

After running the program for over a month, I realized that Septime was being sneaky: you just can’t book night time tables using their online reservation system.

So, I had to revert to the last hack I had in mind… call the restaurant like a normal human being… which worked on first attempt. No AWS Lambda and Serverless needed, I got my booking and ate there a Friday night. Oh well, I had some fun with AWS Lambda…

I really look forward to using Google Duplex.

Closing Comments

Although my Lambda wasn’t the one making the booking for me, it provided me some insights into the booking patterns and I had ton of fun figuring out I actually had to call to make a booking for dinners (it’s not indicated on the website… sneaky sneaky).

If you’re inspired and you want to learn how to do use AWS Lambda for your own projects (small or huge scale), well I teach about it:

Happy learning and eating!