一、背景

用户上传的原图需要以缩略图格式展示,如果每次都加载原图的话比较耗费时间。所以考虑使用png格式的缩略图进行处理。对此官方推荐使用Lambda@Edge对原图进行处理。官方参考文档:https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html

网上博主的参考文档:https://aws.amazon.com/cn/blogs/china/use-cloudfront-lambdaedge-for-transaction-processing/

二、配置cloudfront

1.1源站为s3

1.2配置cf行为

备注:可以配置多个行为,例如模糊匹配jpg png等,注意无论原图是哪种格式之后切的图片格式都是png格式。/*.jpg 表示匹配所有jpg文件

1.3配置缓存、源请求策略

备注:如果选择旧缓存设置则字符串策略要选择字符串永远匹配

三、配置lambda

3.1上传zip文件

备注:cloudfront-lambda-s3-picture.zip py文件改成lambda_function.py

import json
import boto3
import PIL
from PIL import Image
from io import BytesIO
import os
from urllib.parse import parse_qs
import base64

# All the buketname and dir name should be lowercase to meet the s3 naming requirements.a
bucketName = 'g6p-release'
rawDir = 'test-ops'
Cloudfront_Origin_Path='/test-ops-origin/'
PUT_TO_S3='FALSE'

def lambda_handler(event, context):
    # Get object
    cf = event['Records'][0]['cf']
    request = cf['request']
    params = {k: v[0] for k, v in parse_qs(request['querystring']).items()}
    image = params.get('image')
    size = params.get('size')
    rawObject = "{dir}/{key}".format(dir=rawDir, key=image)
    print('rawObject:',rawObject)
    response = ''

#
    if 'response' in cf:
        trigger_point = 'RESPONSE'
    else:
        trigger_point = 'REQUEST'

    if trigger_point == 'REQUEST':
        buffer = resize_s3_image(bucketName, rawObject, size)
# If triggerd by Original Rqeust, you may want to conitune forward to original server, so you should upload the file back to s3 then return request.
# For example, if your client does not accept base64 encoded picture files, you need to do so, In this case you can't use the original response trigger
#        return request
# Otherwise, return respone to viewer directly like flow:
        return generate_response(response, buffer, trigger_point)

    if trigger_point == 'RESPONSE':
        response = cf['response']
        if int(response['status'])>= 400 and int(response['status']) <= 599:
            buffer = resize_s3_image(bucketName, rawObject, size)
# You can decide whether to send the file back to s3 so that subsequent get operations can get the existing file directly .
            if PUT_TO_S3 == 'TRUE':
                uri = request['uri']
                newObject = "{rootpath}{ouri}".format(rootpath=Cloudfront_Origin_Path.strip('/'), ouri = uri)
                upload_to_s3(bucketName, newObject, buffer)
            return generate_response(response, buffer, trigger_point)
        else:
            return response

def resize_s3_image(bucket_name, objectKey, size):
    size_split = size.split('x')
    s3 = boto3.resource('s3')
    obj = s3.Object(
        bucket_name = bucket_name,
        key = objectKey,
    )
    obj_body = obj.get()['Body'].read()

    img = Image.open(BytesIO(obj_body))
    img = img.resize((int(size_split[0]),int(size_split[1])),PIL.Image.ANTIALIAS)

    buffer = BytesIO()
    img.save(buffer,'png')
    buffer.seek(0)
    return buffer

def upload_to_s3(bucket_name, objectKey, buffer):
    # Upload the generated objcect to s3
    s3 = boto3.resource('s3')
    obj = s3.Object(
        bucket_name = bucket_name,
        key = objectKey,
    )
    obj.put(Body=buffer,ContentType='image/png')

    # Construct Resopne.
def generate_response(response, buffer, trigger_point):
    if trigger_point == 'REQUEST':
        response = {
            'status': '',
            'statusDescription': '',
            'headers': {
                'cache-control': [
                    {
                        'key': 'Cache-Control',
                        'value': 'max-age=100'
                    }
                ],
                "content-type": [
                    {
                        'key': 'Content-Type',
                        'value': 'image/png'
                    }
                ],
                'content-encoding': [
                    {
                        'key': 'Content-Encoding',
                        'value': 'base64'
                    }
                ]
            },
            'body': '',
            'bodyEncoding': 'base64'
        }

    response['status'] = '200'
#Json cannot serialize binary picture files, so first force-code them with base64 into text files that json thinks can be serialized, and then reverse-code at the browser end
    buffer.seek(0)
    response['body'] = base64.b64encode(buffer.read()).decode()
    response['bodyEncoding'] = 'base64'
    response['headers']['content-type'] = [{'key': 'Content-Type', 'value': 'image/png'}]
    response['headers']['content-encoding'] = [{'key': 'Content-Encoding', 'value': 'base64'}]

    if trigger_point == 'REQUEST':
        response['statusDescription'] = 'Generated by CloudFront Original Request Function'
    if trigger_point == 'RESPONSE':
        response['statusDescription'] = 'Generated by CloudFront Original Response Function'

    return response
3.2配置触发器

3.3触发器选择对应cf id

3.4lambda函数超时时间修改

超时时间改成30秒

四、测试

https://d13c0gm99vxgzj.cloudfront.net/test-ops/test/ops-test-21213131.png?image=ops-test-2.jpg&size=301x250

备注:可以根据自身需求调整缩略图大小,lambda第一次处理时时间有点长大概有6s,而切好的图片缓存中保存的时间为一天。

五、总结

  • 源码来着网上,有些东西还是被限制了,比如定义缩略图目录(变量rawDir只能是字符串,不能为空且不能写"/")
  • lambda每次更新完要重新部署
  • 触发器获取事件是源响应模式,所以原图访问不受影响,缩略图的话就通过加args参数进行获取
  • 对于uri字符串ops-test21212121.png表示切割后的图片,ops-test-2.jpg表示原图

六、附带nginx的配置文件

如果大陆想通过cn2服务器中转请求的话,该场景比较合适

[root@g6p.cn vhost]# egrep -v "#|^$" oss.g6p.cn.conf 
server {
        listen 80;
        server_name oss.g6p.cn;
        access_log /data/wwwlogs/oss-cn-shenzhen.aliyuncs.com.log;
      location / {
                proxy_set_header host $http_host;
                proxy_set_header    Host                        "d13c0gm99vxgzj.cloudfront.net";
                proxy_set_header    X-Real-IP                    $remote_addr;
                proxy_set_header    X-Forwarded-For              $proxy_add_x_forwarded_for;
                proxy_set_header    HTTP_X_FORWARDED_FOR      $remote_addr;
             proxy_pass http://d13c0gm99vxgzj.cloudfront.net;
        }
      location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico|js|css|webp)$ {
                proxy_set_header    X-Real-IP                    $remote_addr;
                proxy_set_header    X-Forwarded-For              $proxy_add_x_forwarded_for;
              expires 30d;
            access_log off;
              proxy_cache cache_one;
                proxy_pass http://d13c0gm99vxgzj.cloudfront.net;
      }
}
server {
        listen 443 ssl;
        ssl_certificate vhost/g6p.cn.pem;
        ssl_certificate_key vhost/g6p.cn.key;
        ssl_session_timeout 5m;
        ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        access_log /data/wwwlogs/oss-cn-shenzhen.aliyuncs.com_ssl.log;
        error_log /data/wwwlogs/oss-cn-shenzhen.aliyuncs.com-error_ssl.log debug;
        server_name oss.g6p.cn;
        location / {
                proxy_set_header    Host                        "d13c0gm99vxgzj.cloudfront.net";
                proxy_set_header    X-Real-IP                    $remote_addr;
                proxy_set_header    X-Forwarded-For              $proxy_add_x_forwarded_for;
                proxy_set_header    HTTP_X_FORWARDED_FOR      $remote_addr;
                proxy_pass http://d13c0gm99vxgzj.cloudfront.net;
        proxy_cache cache_one;
                proxy_cache_key $host$uri$is_args$args;
         }  
        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico|webp|woff)$ {
                expires 3d;
         proxy_set_header    Host                        "d13c0gm99vxgzj.cloudfront.net";
                proxy_set_header    X-Real-IP                    $remote_addr;
                proxy_set_header    X-Forwarded-For              $proxy_add_x_forwarded_for;
                access_log off;
                proxy_cache cache_one;
                proxy_pass http://d13c0gm99vxgzj.cloudfront.net;
            } 
}
最后修改日期: 2023年12月16日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。