Create thumbnails with AWS Lambda
well~ long story
We need a S3 to put thumbnails, so create S3 bucket first. HOW
1. Create Lambda function
Function name: Thumbnail-stageRuntime: Python 3.8Execution role: Create a new role with basic Lambda permissions
2. Add role
In function, click Permissions, there is a role name in Execution role block, click the name, it will open AWS Roles editor, click Attach policies.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::YOUR_ORIGIN_IMAGE_BUCKET_NAME/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::YOUR_THUMB_IMAGE_BUCKET_NAME/*"
]
}
]
}
Save it and remember the role name.
Make sure in Permission block, there is your role just added.
3. Add trigger
Click Add trigger button in Designer block.
Trigger configuration: Choose API GatewayAPI: Choose Create an APIAPI type: HTTP APISecurity: OpenDeployment stage: default
4. Python code
Make python3.8 env first tho.
mkdir lambda-projects
cd lambda-projects
python3.8 -m venv thumbnail-stage
cd thumbnail-stage
source bin/activate
python --version
Python 3.8.3pip install --target ./package Pillow
Add lambda_function.py
# coding=UTF-8import PIL
from PIL import Image
from io import BytesIO
import json
import boto3, botocore, os, re, base64s3 = boto3.resource('s3')# Lambda Environment Variablessource_bucket = os.environ['SOURCE_BUCKET']
source_prefix_key = os.environ['SOURCE_PREFIX']
target_bucket = os.environ['TARGET_BUCKET']
target_prefix_key = os.environ['TARGET_PREFIX']
source_replace = os.environ['SOURCE_REPLACE']
sizechart = os.environ['SIZECHART']def check_object_exists(bucket, key):
try:
s3.Object(bucket, key).load()
except botocore.exceptions.ClientError as e:
return False
else:
return Truedef lambda_handler(event, context):
# s3 307 answer
try:
key = event['queryStringParameters']['key']
except NameError as e:
return {
"statucCode": "404",
"body": "Can not find the key."
}
except Exception as e:
return {
"statucCode": "408",
"body": str(e)
} match = re.search('((\d+)[a-zA-Z](\d+))\/(.*)', key) # Check value
if match == None:
return {
'statusCode': '404',
'body': 'Key is invalid.'
} # check in size chart
if match.group(1) not in sizechart.split(','):
print("size chart: " + match.group(1))
return {
'statusCode': '404',
'body': 'Sizechart not found.'
} width = int(match.group(2))
height = int(match.group(3))
extension = os.path.splitext(key)[1].lower() r_key = str(match.group(4))
if source_replace != '':
r_key = re.sub(r'(http|https)://' + source_replace + source_prefix_key, '', r_key) # path: prefix/v/WxH/W_H_key
# v/1000x1000/path1/path2/test_image.png
key_list = key.split("/")
prefix = key_list[0].lower() # path: prefix/WxH/W_H_key
if prefix == "v":
source_sub_path = "item/v_image/"
elif prefix == "c":
source_sub_path = "item/c_image/"
else:
prefix = ""
source_sub_path = "" if prefix > "":
putkey = target_prefix_key + prefix + "/" + str(match.group(1)) + "/" + r_key
else:
putkey = target_prefix_key + str(match.group(1)) + "/" + r_key key = source_prefix_key + source_sub_path + r_key if extension in ['.jpeg', '.jpg']:
format = 'JPEG'
if extension in ['.png']:
format = 'PNG' # check object exists
if check_object_exists(target_bucket, putkey) == False: if check_object_exists(source_bucket, key) == False:
return {
'statusCode': '404',
'body': 'Image name not found.'
} # get original's images
obj = s3.Object(
bucket_name=source_bucket,
key=key
) obj_body = obj.get()['Body'].read() img = Image.open(BytesIO(obj_body))
SourceWidth, SourceHeight = img.size # Make image to not distortion
# Based on width
if SourceWidth >= SourceHeight:
if SourceWidth > width:
# width:x = SourceWidth:SourceHeight
ResizeWidth = width
ResizeHeight = int(width * SourceHeight / SourceWidth)
# Based on height
else:
if SourceHeight >= height:
# x:height = SourceWidth:SourceHeight
ResizeHeight = height
ResizeWidth = int(height * SourceWidth / SourceHeight) if 'ResizeWidth' not in locals() and 'ResizeHeight' not in locals():
ResizeWidth = SourceWidth
ResizeHeight = SourceHeight img = img.resize((ResizeWidth, ResizeHeight), Image.ANTIALIAS) # Past white background
ResultImage = Image.new("RGB", [width, height], (255, 255, 255, 255))
ResultImage.paste(img, (int((width - img.size[0]) / 2), int((height - img.size[1]) / 2))) # distortion
buffer = BytesIO()
ResultImage.save(buffer, format)
buffer.seek(0) obj = s3.Object(
bucket_name=target_bucket,
key=putkey
) #object_acl = s3.ObjectAcl(
bucket_name=target_bucket,putkey
)
obj.put(Body=buffer)
hex_data = buffer.getvalue()
else:
obj = s3.Object(
bucket_name=target_bucket,
key = putkey
)
hex_data = obj.get()['Body'].read() return {
"isBase64Encoded": True,
"statusCode": 200,
"headers": {"content-type": "image/jpg"},
"body": base64.b64encode(hex_data).decode("utf-8")
}
zip all packages and upload to lambda
cd package
zip -r9 ${OLDPWD}/function.zip . adding: PIL/ (stored 0%)
adding: PIL/FtexImagePlugin.py (deflated 60%)
adding: PIL/SunImagePlugin.py (deflated 63%)
adding: PIL/ImageDraw.py (deflated 76%)
adding: PIL/DcxImagePlugin.py (deflated 56%)
......cd $OLDPWD
zip -g function.zip lambda_function.py
adding: lambda_function.py (deflated 66%)
Upload zip file to lambda with AWS CLI version 2.
aws lambda update-function-code --function-name my-function --zip-file fileb://function.zip
(Replace my-function with your lambda function name, ex: Thumbnail-stage)
5. Lambda settings
Add Environment variables
SIZECHART: required, split with ','
ex: 1000x1000,800x800,600x600,550x550,500x500,300x300,120x120
SOURCE_BUCKET: required, source image bucket
ex: my-storages-source
TARGET_BUCKET: required, thumbnail bucket
ex: my-thumbnail
SOURCE_PREFIX: source prefix path
ex: images/
SOURCE_REPLACE: If your source is a url, You can paste a domain & ‘/’ to replace it without ‘http://’ or ‘https://’.
TARGET_PREFIX: target prefix path
ex: thumb/
Basic settings block
Handler: lambda_function.lambda_handler
Memory: 128MB
Timeout: 0 min 10 sec
Execution role: choose Existing role
pick the role created in step 2click Save
6. S3 settings
Step 1, check your source S3 bucket, you can keep private settings to prevent access from outside.
Step 2, check the thumbnail S3 bucket, in Properties tab, click the Static website hosting, then click Use this bucket to host a website, copy and paste redirect rules below. You need to replace YOUR_API_GATEWAY_HOST_NAME with your api gateway host, ex:0123456789.execute-api.us-west-1.amazonaws.com, and YOUR_API_PATH value with your api path ex: default/Thumbnail-stage. This can be found in API Gateway block in your lambda function.
<RoutingRules>
<RoutingRule>
<Condition>
<KeyPrefixEquals/>
<HttpErrorCodeReturnedEquals>403</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>YOUR_API_GATEWAY_HOST_NAME</HostName>
<ReplaceKeyPrefixWith>YOUR_API_PATH?key=</ReplaceKeyPrefixWith>
<HttpRedirectCode>307</HttpRedirectCode>
</Redirect>
</RoutingRule>
</RoutingRules>
7. Add cloudfront point to static_website_endpoint of thumbnail bucket
In Create Distribution, origin domain name must point to the website endpoint in Step 6. This can trigger 307 http code (Temporary Redirect) when file is unaccessible (403 http code here).
Edit behavior in cloudfront, update these fields.
Cache Based on Selected Request Headers: Whitelist
Whitelist Headers: Accept, AuthorizationQuery String Forwarding and Caching: choose Forward allm cache based on all
Now, you can put an image in SOURCE_BUCKET, and visit https://your_thumbnail.cloudfront.net/120x120/image.jpg
8. How to debug
CloudWatch > CloudWatch Logs > Log groups
find your lambda function name, that is.