Skip to content
This repository has been archived by the owner on May 9, 2024. It is now read-only.

Commit

Permalink
feature/alb-support (#32)
Browse files Browse the repository at this point in the history
* Adding native support of ALB requests

* Adding the raw event to the minik event
  • Loading branch information
pdiazvargas authored Oct 10, 2019
1 parent 7c3b5b5 commit 2ea04f7
Show file tree
Hide file tree
Showing 33 changed files with 952 additions and 213 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,7 @@ venv.bak/

# Python3 virtual env
venv3/
tvenv/
tvenv/

# VS Code
.vscode
14 changes: 14 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
Minik Changelog
===============

Version 0.5.0
-------------

Released on October 8th, 2019, codename Yeni Ridge

- Large refactoring effort to natively support requests from an ALB (issues/33)
- Introducing a new router to keep track of the route/handler pairs
- Refactoring the requests builders to correctly build a MinikRequest based
on the event type (api_request, or alb_request)
- Adding an example to associate a lambda function to an ALB (examples/alb-events)
- Updating documentation.
- Updated the MinikRequest to keep the event received from the lambda function (issues/15).


Version 0.4.0
-------------

Expand Down
82 changes: 79 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Minik: Serverless Web Framework

|circle| |pypi version| |apache license|

Using AWS lambda functions and API Gateway, minik will serve as the framework
that facilitates development in the serverless space.
A web framework that facilitates the development of serverless applications using
AWS resources. Minik natively supports request from the API Gateway and the
Application Load Balancer.

Installing
**********
Expand Down Expand Up @@ -141,6 +142,79 @@ By default the debug mode is set to False.
Initializing the app in debug mode will relay the stack trace back to the consumer.


ALB Support
***********
Along with having a native integration with the API Gateway, minik can now be used
to handle requests coming from an Application Load Balancer (ALB). The definition
of the web application is identical in both cases. There is no additional code required
to use minik with an ALB.

If a lambda function is the `target of an ALB`_, minik will parse the raw event,
find the view associated with the route and execute the view with the correct context.

.. code-block:: python
from minik.core import Minik
app = Minik()
@app.get('/greetings')
def get_greetings():
app.response.headers = {"Content-Type": "text/html; charset=utf-8"}
return """
<html>
<head>
<title>Hello World!</title>
<style>
html, body {
margin: 0; padding: 0;
font-family: arial; font-weight: 700; font-size: 3em;
text-align: center;
}
</style>
</head>
<body>
<p>Hello World!</p>
</body>
</html>"""
Notice that there is nothing specific about the source that will eventually invoke
this lambda function. This codeblock can be used to handle a request either
from the API Gateway or from an ALB.

.. _`target of an ALB`: https://aws.amazon.com/blogs/networking-and-content-delivery/lambda-functions-as-targets-for-application-load-balancers/


Request Object
**************
Any view has access to the **app.request** instance as a way to retrieve the general
information of a request. The fields of this object include the query parameters,
the path parameters, headers, payload... Given that different sources might have
a set of additional fields, minik will store a copy of the original event in the
**app.request** instance.

For instance, the API Gateway has the concept of stage variables that is missing
from an event received from the ALB. In this case, the generic app.request instance will
not have a field called stage_variables. Instead, minik keeps a copy of the original
event and context objects in the request. In this case a developer can access these
values using the app.request.aws_event['StageVariables']. Where the aws_event is
the event minik received as the handler of the lambda function.

.. code-block:: python
from minik.core import Minik
app = Minik()
@app.post('/events')
def post_view():
# app.request.json_body: The payload of a post request as a JSON object.
# app.request.aws_event: The raw event sent by a source to the lambda function.
# app.request.aws_context: The context of the lambda function.
return {'result': 'complete'}
Motivation
**********
The team behind this framework is adopting a very minimal set of features to enhance
Expand Down Expand Up @@ -168,9 +242,11 @@ Things to be aware of when working using minik:
- When used in your lambda function, you're responsible for including the source
code of minik in your .zip artifact. For packaging purposes we recommend using
`Juniper`_.
- Minik is service agnostic, as a web framework it natively supports requests
from the API Gateway and an Application Load Balancer (ALB).
- Unlike other frameworks like Flask or Django, where using the decorator is
sufficient to define the routes of the web app, in minik, you’re responsible
for linking a lambda function to the API gateway. We recommend using a
for linking a lambda function to the API Gateway. We recommend using a
`SAM`_ template.
- Minik does not include a local development server! For testing purposes, you can
either deploy your lambda to AWS using `sam package` and `sam deploy`. For local
Expand Down
15 changes: 14 additions & 1 deletion docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,17 @@ given name is valid or not.

Keep in mind that with a valid route, the value of field will be a string!

.. _`function annotations`: https://www.python.org/dev/peps/pep-3107/
.. _`function annotations`: https://www.python.org/dev/peps/pep-3107/


ALB Native Support
******************
A lambda function can be used as the target of an ALB, while using this configuration,
a web developer needs to route and parse the raw event sent by the ALB to the lambda.
With minik, a developer can use a familiar interface to define the web application. It
is the framework's responsibility to correctly parse and route a request.

An application originally configured to receive requests from an API Gateway endpoint
can be seamlessly used to handle requests from an ALB. No code changes are required
at all to make the transition. Minik will determine the event type based on the
raw event it receives and it will handle the request correctly.
100 changes: 99 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ view would look like:
@app.get('/articles/{author}/{year}/')
def get_articles_view(author: str, year: int):
app.response.headers = {
"Content-Type": "Content-Type": "text/html; charset=utf-8",
"Content-Type": "text/html; charset=utf-8",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date",
Expand Down Expand Up @@ -272,6 +272,104 @@ API Gateway and for associating the lambda function with the gateway endpoint. M
is just the framework that allows you to write your api in a straight-forward fashion.


Minik with ALB
**************
When working with a lambda function as the handler of a request from an Application
Load Balancer (ALB), a simple hello world application looks like:

.. code:: python
def lambda_handler(event, context):
response = {
"statusCode": 200,
"statusDescription": "200 OK",
"isBase64Encoded": False,
"headers": {
"Content-Type": "text/html; charset=utf-8"
}
}
response['body'] = """<html>
<head>
<title>Hello World!</title>
<style>
html, body {
margin: 0; padding: 0;
font-family: arial; font-weight: 700; font-size: 3em;
text-align: center;
}
</style>
</head>
<body>
<p>Hello World!</p>
</body>
</html>"""
return response
In this scenario, the event your lambda function receives from the ALB looks like this:

.. code:: json
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:XXXXXXXXXXX:targetgroup/sample/6d0ecf831eec9f09"
}
},
"httpMethod": "GET",
"path": "/",
"queryStringParameters": {},
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"accept-encoding": "gzip",
"accept-language": "en-US,en;q=0.5",
"connection": "keep-alive",
"cookie": "name=value",
"host": "lambda-YYYYYYYY.elb.amazonaws.com",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:60.0) Gecko/20100101 Firefox/60.0",
"x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520",
"x-forwarded-for": "192.0.2.1",
"x-forwarded-port": "80",
"x-forwarded-proto": "http"
},
"body": "",
"isBase64Encoded": false
}
Without Minik, every single API endpoint will need to parse the raw object
as a way to get the data values you care about.

With Minik, you get a clear familiar interface that hides the complexity of dealing
with the raw representation of the event and context objects. We take care of parsing
the ALB object for you, so that you can focus on writing your business logic.
Using the above object and endpoint as an example, our lambda function would instead be:

.. code:: python
from minik.core import Minik
app = Minik()
@app.route("/test/{action}")
def hello(action):
name = app.request.query_params.get('name')
# With the values defined in the object above this will return.
# {'hello': 'me'}
return {action: name}
Just like with any other lambda function you are responsible configuring the lambda
function as the `target of the ALB`_. Minik is just the framework that facilitates the
definition of the web application.

Notice that the code to handle a request from the API Gateway is identical to the
code used in the ALB example. This shows that minik is service agnostic, an web
application that was associated to an API Gateway definition, can seamlessly be
used to handle requests from an ALB without changing the code.

.. _`target of the ALB`: https://aws.amazon.com/blogs/networking-and-content-delivery/lambda-functions-as-targets-for-application-load-balancers/

.. _SAM: https://github.com/awslabs/serverless-application-model
.. _Chalice: https://github.com/aws/chalice
.. _Serverless: https://serverless.com/
Expand Down
4 changes: 2 additions & 2 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ in this page. To run the examples:
Once you're in this folder, you can use the commands defined in the make file.

>>> make build
>>> make package AWS_PROFILE=<your profile>
>>> make deploy AWS_PROFILE=<your profile>
>>> make package profile=<your profile>
>>> make deploy profile=<your profile>

.. _`SAM cli`: https://github.com/awslabs/aws-sam-cli
.. _`Juniper`: https://github.com/eabglobal/juniper
23 changes: 23 additions & 0 deletions examples/alb-events/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
s3_bucket=temp-${profile}
stack_name ?= minik-alb
sam_template_base=sam
sam_template=$(sam_template_base).yml
sam_output=$(sam_template_base)-new.yml


build:
juni build

deploy:
sam package \
--s3-bucket $(s3_bucket) \
--template-file $(sam_template) \
--output-template-file ./dist/$(sam_output) \
--profile $(profile)

sam deploy \
--template-file ./dist/$(sam_output) \
--stack-name $(stack_name) \
--capabilities CAPABILITY_IAM \
--region us-east-1 \
--profile $(profile)
Empty file.
60 changes: 60 additions & 0 deletions examples/alb-events/lambda_function/lambda_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from minik.core import Minik

app = Minik()

req_type_by_name = {
'api_request': 'API Gateway!',
'alb_request': 'ALB!'
}


@app.get("/events")
def get_events():
"""
The view handler for the `/events` route, this function will return an html
response with a list of events. Each event points to another route to get
more information on the event.
This view will also display the request type based on the service that invoked
the lambda function. Minik supports request from API Gateway and Application
Load Balancer natively.
"""

app.response.headers = {
"Content-Type": "text/html; charset=utf-8",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date",
"Authorization": "X-Api-Key,X-Amz-Security-Token"
}

req_type = req_type_by_name.get(app.request.request_type)

return f"""
<html>
<head>
<title>Hello from {req_type}</title>
</head>
<body>
<h1>Hello from {req_type}</h1>
<a href="/events/20902">Silver Spring Events</a>
<a href="/events/32608">Alachua County Events</a>
</body>
</html>"""


@app.get("/events/{zip_code}")
def get_event(zip_code: int):
"""Very simple handler that returns a json response based on the zip code."""

if zip_code == 20902:
return {'events': ['MD Gran fondo', 'Old Busthead']}

return {'events': ['other events']}


@app.post("/events/{zip_code}")
def post_event(zip_code: int):
""" An echo function to return the body and zip of the request."""
# Store a new event
return {'zip_code': zip_code, 'post_data': app.request.json_body}
6 changes: 6 additions & 0 deletions examples/alb-events/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
functions:
events:
requirements: ./requirements.txt
include:
- ./lambda_function/lambda_handler.py
- ../../minik
Empty file.
16 changes: 16 additions & 0 deletions examples/alb-events/sam.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API Gateway with Lambda Token Authorizer
Resources:

EventsFunction:
Type: AWS::Serverless::Function
Properties:
# This function uses the python 3.6 runtime.
Runtime: python3.6

# This is the Lambda function's handler.
Handler: lambda_handler.app

# The location of the Lambda function code.
CodeUri: ./dist/events.zip
Loading

0 comments on commit 2ea04f7

Please sign in to comment.