程序员

AWS Lambda笔记-事件触发Lambda函数-11【含源代

2020-07-04  本文已影响0人  lazy_zhu

当我们把图片放在云端时,随着时间推移图片会越来越多,这是我们希望生产缩略图方便快速浏览,AWS为这个提供了更好的解决方案,当云端资源中发生某种特定的事件时,将触发Lambda函数。比如,将上传的图片通过异步调整图片大小。

  1. 配置API网关上传图片到S3
  2. Lambda函数响应S3事件
  3. 配置CloudFront让图片访问更快更方便

源代码

代码下载地址:https://pan.baidu.com/s/1RYcGMEh0W_-n_MkQlEYCAw
提取码:umci

工程说明

新建一个Bucket,并通过CloudFormation配置API网关上传图片,Bucket中配置新增对象事件(s3:ObjectCreated:*)用来通知Lambda函数,Lambda函数中收到通知获取到图片进行Copy操作。
lambda-imageresizer: 接受通知,处理图片的函数。

工程结构图 工程关系配置

1. 配置API网关上传图片到S3

图片上传的方法是/users/{userid}/picture。要实现该功能我们需要四个小步骤:
1)创建一个Bucket用来存储图片
2)为API网关创建一条Role规则
3)配置REST资源即/users/{userid}/picture路径
4)创建API网关的PUT方法

1)创建一个Bucket用来存储图片。配置中的${DomainName}是在cloudformation.template文件头部定义的变量(详见源代码),该变量的值来自build.gradle,因为S3存储桶的名称必须要全局唯一。注意,后续这个配置会增加Bucket的事件通知,用来触发Lambda函数。

"ProfilePicturesBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": {
          "Fn::Sub": "${DomainName}-profilepictures"
        }
}

2)为API网关创建一条Role规则。这个规则只对图片存储桶(ProfilePicturesBucket)授予s3:PutObject和s3:PutObjectAcl权限,这个权限会被添加到AWS::ApiGateway::Method方法中(UsersIdPicturePutMethod)。

//创建一个Role,API网关可以认定这条规则只对图片存储Bucket
//授权S3:PutObject和S3:PutObjectAcl权限
"ApiGatewayProxyRole": {
  "Type": "AWS::IAM::Role",
  "Properties": {
    "AssumeRolePolicyDocument": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": [
              "apigateway.amazonaws.com"
            ]
          },
          "Action": "sts:AssumeRole"
        }
      ]
    },
    "Path": "/",
    "Policies": [
      {
        "PolicyName": "S3BucketPolicy",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
               //具体可以执行的动作
              "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
              ],
              //资源指定ProfilePicturesBucket
              "Resource": [
                {
                  "Fn::Sub": "arn:aws:s3:::${ProfilePicturesBucket}"
                },
                {
                  "Fn::Sub": "arn:aws:s3:::${ProfilePicturesBucket}/*"
                }
              ]
            }
          ]
        }
      }
    ]
  }
},

3)配置REST资源即/users/{userid}/picture路径,看这个路径这边配置了三个资源/users,/{userid},/picture,其中/users资源已经在上一章配置过,下面是 /{userid},/picture两个资源的配置,需要注意配置中ParentId对应的资源Ref Id。

/{userid}资源

"UsersIdResource": {
  "Type": "AWS::ApiGateway::Resource",
  "Properties": {
    "PathPart": "{id}",
    "RestApiId": {
      "Ref": "RestApi"
    },
    "ParentId": {
      "Ref": "UsersResource"
    }
  }
},

/picture资源

"UsersIdPictureResource": {
  "Type": "AWS::ApiGateway::Resource",
  "Properties": {
    "PathPart": "picture",
    "RestApiId": {
      "Ref": "RestApi"
    },
    "ParentId": {
      "Ref": "UsersIdResource"
    }
  }
},

4)创建API网关的PUT方法。这里把API网关的Http请求属性映射S3 API调用,把请求包传到S3 API。具体看下面的详细备注

"UsersIdPicturePutMethod": {
  "Type": "AWS::ApiGateway::Method",
  "Properties": {
    //PUT方法
    "HttpMethod": "PUT",
    "RestApiId": {
      "Ref": "RestApi"
    },
    //上传图片需要自定义授权
    "AuthorizationType": "CUSTOM",
    "AuthorizerId": {
      "Ref": "ApiGatewayAuthorizer"
    },
    //定义资源
    "ResourceId": {
      "Ref": "UsersIdPictureResource"
    },
    //请求路径参数{userid}, 头参数Content-Type,Content-Length
    "RequestParameters": {
      "method.request.path.id": "True",
      "method.request.header.Content-Type": "True",
      "method.request.header.Content-Length": "True"
    },
    "Integration": {
      "Type": "AWS",
      "Uri": {
        "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:s3:path/${ProfilePicturesBucket}/uploads/{filename}"
      },
      "IntegrationHttpMethod": "PUT",
      //集成所需的凭证
      "Credentials": {
        "Fn::GetAtt": [
          "ApiGatewayProxyRole",
          "Arn"
        ]
      },
      "RequestParameters": {
        //context.requestId 是APIGateway的请求ID
        //path.filename即Uri中filename,生产路径名称
        "integration.request.path.filename": "context.requestId",
        "integration.request.header.Content-Type": "method.request.header.Content-Type",
        "integration.request.header.Content-Length": "method.request.header.Content-Length",
        //客户端在Post较大数据到服务端之前,允许双方“握手”,如果匹配上了Client才开始发送较大数据
        //原因是:如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。
        "integration.request.header.Expect": "'100-continue'",
        //添加bucket权限控制,有private,public-read-write,bucket-owner-read等
        "integration.request.header.x-amz-acl": "'public-read'",
        //S3中获取同时 getUserMetaDataOf("user-id") 获取{id}值
        "integration.request.header.x-amz-meta-user-id": "method.request.path.id"

      },
      "RequestTemplates": {
      },
      //当集成没有映射到模板的内容类型时,允许传递
      "PassthroughBehavior": "WHEN_NO_TEMPLATES",
      //方法的后端处理完请求后提供的响应
      "IntegrationResponses": [
        {
           //一个匹配正则
          "SelectionPattern": "4\\d{2}",
          "StatusCode": "400"
        },
        {
          "SelectionPattern": "5\\d{2}",
          "StatusCode": "500"
        },
        {
          "SelectionPattern": ".*",
          "StatusCode": "202",
          "ResponseTemplates": {
            "application/json": {
              "Fn::Sub": "{\"status\": \"pending\"}"
            }
          }
        }
      ]
    },
    //可发送到调用方法的客户端的响应
    "MethodResponses": [
      {
        "StatusCode": "202"
      },
      {
        "StatusCode": "400"
      },
      {
        "StatusCode": "500"
      }
    ]
  }
}   

小细节:在S3存储上传图片会把图片文件当作UTF-8的文本文件,我们需要在API网关手动配置指定Content-Type头视为二进制文件。登陆APIGateway控制台https://console.aws.amazon.com/apigateway/home?#如图设置下即可。

APIGateway配置头信息
发布工程./gradlew deploy 我们就可以通过命令上传自己的图片,这里是需要授权认证的,需要确认DynamoDB中UserTable 和 TokenTable 这两个表是否存在并且Token表中含有一条severless的授权记录,这方面有疑问可以看《AWS Lambda笔记-数据持久化DynamoDB-10》
命令中serverless.xxxx.com替换成自己的域名
curl --request PUT 'https://serverless.xxxx.com/users/042b00ef-1c50-41e0-bc9e-ff092567427f/picture' \
--header 'Authorization:Bearer serverless' \
--header 'Content-Type:image/jpeg' \
--data-binary '@/Users/lazy/Desktop/temp/timg.jpeg'

上传成功后,我们可以看到我们上传的图片信息在serverless.kkksee.com-profilepicturesBucket下的 /uploads目录下。接下来,我们需要监听这个目录的事件,当这个目录由新增事件时,通知Lambda函数,将图片copy一份。


上传图片

2.Lambda函数响应S3事件

当/uploads目录下面由新增事件时,我们触发Lambda函数。主要是AWS::S3::Bucket下的NotificationConfiguration配置,定义了Bucket的事件,监听的目录,响应的Lambda函数。
Bucket事件通知主要有:新增对象事件(s3:ObjectCreated:*)、对象删除事件(s3:ObjectRemoved:*)、还原对象事件(s3:ObjectRestore:*)、复制事件(s3:Replication:*)。
注意:防止事件触发死循环,例如我们在/uploads目录中监听s3:ObjectCreated:事件,同时我们把处理完的图片又保存到/uploads目录下,这样就又触发s3:ObjectCreated:事件,导致无限循环。
Lambda函数响应S3事件具体操作步骤:
1)创建Lambda事件响应函数
2)配置Bucket事件通知Lambda函数

1)创建Lambda事件响应函数
按照目录新建工程lambda-imageresizer,并把该工程加到根目录的settings.gradle文件中。还需要导入S3的标准化事件库aws-lambda-java-events(build.gradle添加dependencies)

lambda-imageresizer工程目录

根目录settings.gradle新增:

include 'lambda-imageresizer'

当前工程的build.gradle配置新增

dependencies {
    compile group: 'com.amazonaws', name: 'aws-lambda-java-events', version: '1.3.0'
}

工程中Handle.java类(响应Bucket事件)

/*
函数使用S3预置标准事件 依赖aws-lambda-java-events 库
*/
public class Handler implements RequestHandler<S3Event, Void> {
  private static final Logger LOGGER = Logger.getLogger(Handler.class);
  final AmazonS3 s3client;
  public Handler() {
    //构造时创建S3Client
    s3client = new AmazonS3Client(new DefaultAWSCredentialsProviderChain());
  }
  //copy图片到当前Bucekt的users/{userId}/picture/small.jpg
  private void resizeImage(String bucket, String key) {
    LOGGER.info("Resizing s3://" + bucket + "/" + key);
    //获取user-id,对应配置x-amz-meta-user-id
    final String userId = s3client.getObjectMetadata(bucket, key).getUserMetaDataOf("user-id");
    LOGGER.info("Image is belonging to " + userId);
    final String destinationKey = "users/" + userId + "/picture/small.jpg";
    //s3自带copy方法
    s3client.copyObject(bucket, key,bucket, destinationKey);
    LOGGER.info("Image has been copied to s3://" + bucket + "/" + destinationKey);
  }
  @Override
  public Void handleRequest(S3Event input, Context context) {
    //循环触发的事件,并调用resizeImage方法
    input.getRecords().forEach(s3EventNotificationRecord ->
        resizeImage(s3EventNotificationRecord.getS3().getBucket().getName(),
            s3EventNotificationRecord.getS3().getObject().getKey()));
    return null;
  }
}

2)配置Bucket事件通知Lambda函数
我们需要在CloudFormation中定义Lambda函数及授权器,并在上面的bucket配置中增加NotificationConfiguration。

Lambada函数

"ImageResizerLambda": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "com.serverlessbook.lambda.imageresizer.Handler",
        "Runtime": "java8",
        "Timeout": "300",
        "MemorySize": "1024",
        "Description": "Test lambda",
        "Role": {
          "Fn::GetAtt": [
            "LambdaExecutionRole",
            "Arn"
          ]
        },
        "Code": {
          "S3Bucket": {
            "Ref": "DeploymentBucket"
          },
          "S3Key": {
            "Fn::Sub": "artifacts/lambda-imageresizer/${ProjectVersion}/${DeploymentTime}.jar"
          }
        }
      }
    },

Lambda授权器

 "ImageResizerLambdaPermisson": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "ImageResizerLambda"
        },
        "Principal": "s3.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": "arn:aws:s3:::${DomainName}-profilepictures"
        }
      }
    },

Bucket添加通知事件,在这里详细定义了Bucket监听的事件,触发条件和响应的Lambda函数。

    "ProfilePicturesBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": {
          "Fn::Sub": "${DomainName}-profilepictures"
        },
        "NotificationConfiguration": {
          "LambdaConfigurations": [
            {
              //响应 新增事件
              "Event": "s3:ObjectCreated:*",
              //函数调用条件
              "Filter": {
                "S3Key": {
                  "Rules": [
                    {
                      "Name": "prefix",
                      "Value": "uploads/"
                    }
                  ]
                }
              },
              //触发的Lambda函数
              "Function": {
                "Fn::GetAtt": [
                  "ImageResizerLambda",
                  "Arn"
                ]
              }
            }
          ]
        }
      }
    },

以上配置完,发布工程。我们在上传一次图片,就可以发现新建的Bucket下创建了 users/用户ID/picture目录,里面有一个small.jpg的图片。

上传的图片
事件触发Lambda函数的功能基本完成,为了方便我们通过自己的域名访问这张small.jpg图片,我们只需要在之前《AWS Lambda笔记-内容分发(CDN)-7》中简单的新增配置,就可以完成。

3. 配置CloudFront让图片访问更快更方便

我们需要用自己的域名访问修改的samll.jpg图片(例如:http://serverless.xxxxx.com/1234/picture/small.jpg
1)配置CloudFront源站信息
2)配置cloudFront缓存行为

1)配置CloudFront源站信息
把刚创建的ProfilePicturesBucket的bucket作为CloudFront的源站,及在 CloudformationDistribution的Origins中增加:

{
        //指定源的域名
    "DomainName": {
        "Fn::Sub": "${ProfilePicturesBucket}.s3.amazonaws.com"
    },
        //源的唯一标识符,可以自己定义,在下面的缓存行为中使用到
    "Id": "PROFILE_PICTURES",
    "S3OriginConfig": {}
}

CacheBehaviors中增加

"CacheBehaviors": [
            {
              "PathPattern": "/users/*/picture/*",
              //定义的源站
              "TargetOriginId": "PROFILE_PICTURES",
              "Compress": true,
              "AllowedMethods": [
                "GET",
                "HEAD",
                "OPTIONS"
              ],
              "ForwardedValues": {
                "QueryString": "false",
                "Cookies": {
                  "Forward": "none"
                }
              },
              //TTL都为0,直接访问源
              "DefaultTTL": 0,
              "MinTTL": 0,
              "MaxTTL": 0,
              "ViewerProtocolPolicy": "redirect-to-https"
            }
          ]

再一次发布工程./gradlew deploy,我们可以在[CloudFront控制台],通知(https://console.aws.amazon.com/cloudfront/home)看到刚才的配置信息,同时可以输入自定义域名及图片路径访问到图片。

源站信息
缓存行为

源代码

代码下载地址:https://pan.baidu.com/s/1RYcGMEh0W_-n_MkQlEYCAw
提取码:umci:

上一篇 下一篇

猜你喜欢

热点阅读