View project on GitHub
Prerequisites
- Docker
- AWS CLI (
brew install awscli
) - SAM CLI (
brew tap aws/tap && brew install aws-sam-cli && sam --version
) - AWS credentials set in
~/.aws/credentials
- Understanding of SAM (watch here)
Running and Deployment
Local invocation
You may want to ensure that your local SAM setup is up and ready. To do so, run the following command. Note, it may take a while to download the docker image for SAM on the first run.
sam local invoke Resin --no-event
# START RequestId: ... Version: $LATEST
# END RequestId: ...
# REPORT RequestId: ...
# {"statusCode": 500, "headers": {"Content-type": "application/json", "Cache-control": "max-age=0"}, "body": "{\"error\": \"Missing required configuration key\"}"}
If you get errors, it may be to do with the Python version used by SAM. Furthermore, you might need to upgrade packages, documented below.
To run locally:
cp example-testenv.json testenv.json # Needed the first time only
nano testenv.json # Set the environment variables
sam local start-api [-p<port>] [--profile=<aws-profile>] --env-vars=testenv.json
# Mounting Resin at http://127.0.0.1:3030/s/{path+}
# ...
- You can set
-p<port>
to use a port other than the default3000
. - You can use
--profile=<aws-profile>
to use another profile that you have stored in~/.aws/credentials
.
When running, we'll be able to navigate
localhost:<port>/s/<width>x<height>/<base_64>[/base_name[?sgn=signature]]
to invoke the script. In example-testenv.json
, Flickr's CDN is whitelisted. If you keep this example, you can test the setup this link. Note, this can be slow as SAM-CLI mounts the package into the runtime container.Development
To install new packages in a fork
You need to install packages for Python3.7 as that's the environment that Lambda will use in production. To do so, you'll need to install the package in a
virtalenv
in a docker image similar to Lambda's, and then copy the files from the virtualenv
to your code repository.docker run --rm -it -v "$PWD":/code lambci/lambda:build-python3.7 bash
cd /code
virtualenv env
source env/bin/activate
pip install [package-name]
cp env/lib/python3.7/site-packages/[package-name] resin/
Deployment
Deploying from local to AWS
sam package --template-file template.yaml [--profile=<aws-profile>] --output-template-file deploy.yaml --s3-bucket=<bucket-name>
sam deploy --capabilities CAPABILITY_IAM [--profile=<aws-profile>] --template-file deploy.yaml --stack-name ResinV0
Environment variables
- BUCKET_NAME
- If
UPLOAD_TO_S3
set to1
, then the name of the bucket to upload to.
- CACHE_CONTROL
- Default
max-age=31536000
.
- DEFAULT_OBJECT_POLICY
- Default
public-read
.
- KNOWN_DOMAINS
- A comma separated list of domains. If the source image is from one of these domains, then the image can be processed, otherwise a "signature" is required.
- SIGNATURE_KEY
- A string to provide basic protection when using images from domains outside
KNOWN_DOMAINS
. Equivalent tomd5(path + SIGNATURE_KEY)
, and appended to the URL with the parameter?sgn=<value>
.
URL Structure
The URL in the template is written as
/s/{path+}
. If this path is not met, then you'll get the error{"message":"Missing Authentication Token"}
A full URL example is
/s/<width>x<height>/<base64-src>/base-name.jpg?sgn=value
/s/
Required to create a sub-folder in your bucket to keep resized images separate.<width>x<height>
e.g.300x300
, the width and height of the image. Creates a sub-folder under/s/
.<base64-src>
This is the source of the image. If the image was not base64, then we'd break S3 (and our CloudFront fallback would fail).- This can be a base64 URL
- Or a base64 JSON object with options (see below).
<basename>
[optional] The basename, for SEO.?sgn
[optional] Use only if the image source is not from a "known domain".
URL JSON Options
s
- Required. The source of the image
- c
- Default
[0.5, 0.5]
- Optional. In the event a crop is performed, the centering of the crop as a percentage between 0 and 1
- q
- Default
80
- Optional. The quality of the scale between 0 and 100
CloudFront
This part is optional but recommended. It is implemented after you have successfully deployed your lambda application. As things stand, we will always resize images using our lambda script which is expensive. We will instead use CloudFront Origin Groups to only ever resize an image if it does not exist.
Example
- Navigate to
xxx.cloudfront.net/s/wxh/image-source/basename.png
. - CloudFront hasn't cached the image.
- CloudFront requests the image from S3.
- S3 returns either 404 or 403.
- CloudFront fails over to Lambda.
- Lambda generates the image, saves it on S3 and then returns the binary.
- ... Time passes
- Navigate again to
xxx.cloudfront.net/s/wxh/image-source/basename.png
. - Image is no longer in CloudFront's cache.
- CloudFront requests the image from S3.
- S3 returns the image.
To set up
- Create a new CloudFront distribution using the bucket you set for the environment variable
BUCKET_NAME
. - On the
Origins and Origin Groups
tab, clickCreate Origin
. - Find the Lambda URL for your new app.
- Set the base path as
Origin Domain Name
, e.g.<id>.execute-api.eu-west-1.amazonaws.com
. - Set
/Prod
asOrigin Path
. (/Prod
comes fromtemplate.yaml: Outputs.ResinApi.Value
).
- Save the new origin.
- On the
Origins and Origin Groups
again, clickCreate Origin Group
. - Set the S3 origin as the primary endpoint, and then add the Lambda origin as the backup.
- Select
404
and403
as the onlyFailover criteria
.
Example Implementations
- Laravel
cp implementations/laravel/ImageResize.php ~/laravel-project/app/Utilities/
cp implementations/laravel/ImageResizeTest.php ~/laravel-project/tests/Feature/
composer dump-autoload
- Copy the stub in
implementations/laravel/helpers.php
to your Laravel project'shelpers.php
- Copy the example env vars in
implementations/laravel/env
to your.env
and.env.example
. {!! imageResize('known-domain-image-source')->setDimensions(width, height)->render(['attribute' => 'value']) !!}
{!! imageResize('random-image-source')->setDimensions(width, height)->sign(true)->render(['class' => 'w-100']) !!}