+5

Resize hình ảnh với Amazon S3, AWS Lambda và Amazon API Gateway

Đặt vấn đề

Như các bạn đã biết, các thiết bị được sử dụng để truy cập internet với kích thước màn hình, độ phân giải khác nhau, vì vậy khi xây dựng ứng dụng có liên quan đến hình ảnh, developers chúng ta phải cung cấp hình ảnh với nhiều kích cỡ khác nhau nhằm tối ưu và đem đến trải nghiệm người dùng tốt hơn. Nếu hình ảnh được lưu trữ ngay trên server của chúng ta thì developers có thể sử dụng một số thư viện xữ lý, resize hình ảnh với kích thước phù hợp trước khi trả về. Nếu code PHP thì bạn có thể sử dụng package này Tuy nhiên, hiện nay thì hầu hết ứng dụng đều lưu trữ hình ảnh trên Amazon S3 nhằm giảm tải cho server. Và nếu như thế thì cách tiếp cận tốt nhất là sử dụng AWS Lambda để trigger event resize hình ảnh khi có một object mới được tạo trong Bucket của S3.

Flow xữ lý

  1. Người dùng request một kích thước nào đó với một hình ảnh trong Bucket của S3 thông qua static website hosting endpoint. Ví dụ mình có 1 tấm hình trên Bucket là vitd.jpg, mình muốn lấy về hình ảnh với kích thước 300x300 thì request sẽ là http://YOUR_BUCKET_WEBSITE_HOSTNAME_HERE/300×300/vitd.jpg
  2. Nếu kích thước hình ảnh chưa tồn tại trên Bucket thì request sẽ được chuyển hướng đến resize API method, yêu cầu thay đổi kích thước thông qua API Gateway.
  3. Phương thức API Gateway được cấu hình để trigger một function Lambda.
  4. Nhiệm vụ của function Lambda là lấy tấm ảnh gốc trên S3 Bucket, resize nó theo kích thước mà người dùng yêu cầu và upload tấm ảnh đã resize vào Bucket. (Với request ở trên thì sau khi xữ lý function Lambda trên Bucket sẽ tạo ra folder 300x300 và chứa tấm hình đã resize về đúng kích thước trong thư mục đó).
  5. Khi function Lambda hoàn thành, API Gateway sẽ redirect request đến file đã được xữ lý và lưu trữ trên S3.
  6. Bây giờ thì người dùng đã có tấm hình với kích thước mong muốn, ở những request tiếp theo, nếu một người dùng khác yêu cầu cũng với kích thước như vậy với tấm hình vitd.jpg thì function Lambda không được gọi nữa mà nó sẽ lấy tấm hình đã được resize trước đó và trả về cho người dùng.

Các bước thực hiện

Tạo và cấu hình S3 Bucket:

  1. Trên màn hình S3 console, tạo mới một Bucket.
  2. Chọn Permissions, Add Bucket Policy và thêm mới một Policy cho Bucket.
  3. Chọn Static Website Hosting, Enable website hosting và trong Index Document nhập vào index.html
  4. Lưu lại là xong.

Tạo function Lambda

  1. Trên màn hình Lambda console chọn Create a Lambda function, Blank Function.
  2. Chọn tích hợp API Gateway.
  3. Để cho phép tất cả user triệu gọi API method, trên Security chọn Open sau đó Next.
  4. Code entry type thì upload folder chứa code của bạn lên. Code function Lambda (Viết bằng NodeJS). Trong package.json thì thêm dependencies và devDependencies như sau:
"dependencies": {
    "sharp": "^0.17.3"
},
  "devDependencies": {
    "aws-sdk": "^2.36.0"
}

aws-sdk được cung cấp để thao tác với AWS. Code file index.js.

'use strict';

const AWS = require('aws-sdk');
const S3 = new AWS.S3({
  signatureVersion: 'v4',
});
const Sharp = require('sharp');

const BUCKET = process.env.BUCKET;
const URL = process.env.URL;

exports.handler = function(event, context, callback) {
  const key = event.queryStringParameters.key;
  const match = key.match(/(\d+)x(\d+)\/(.*)/);
  const width = parseInt(match[1], 10);
  const height = parseInt(match[2], 10);
  const originalKey = match[3];

  S3.getObject({Bucket: BUCKET, Key: originalKey}).promise()
    .then(data => Sharp(data.Body)
      .resize(width, height)
      .toFormat('png')
      .toBuffer()
    )
    .then(buffer => S3.putObject({
        Body: buffer,
        Bucket: BUCKET,
        ContentType: 'image/png',
        Key: key,
      }).promise()
    )
    .then(() => callback(null, {
        statusCode: '301',
        headers: {'location': `${URL}/${key}`},
        body: '',
      })
    )
    .catch(err => callback(err))
}

Sau khi lấy được key bằng event.queryStringParameters.key thì sẽ tiến hành bóc tách dựa vào Regex để lấy ra width, height và tên hình ảnh gốc.

  const match = key.match(/(\d+)x(\d+)\/(.*)/);
  const width = parseInt(match[1], 10);
  const height = parseInt(match[2], 10);
  const originalKey = match[3];

Sau đó sử dụng Sharp để resize nó về kích thước đã bóc tách ở trên rồi dùng putObject để upload nó lên S3, cuối cùng là trả về đường dẫn sau khi đã upload thành công.

Đọc qua đoạn code chắc các bạn sẽ thấy có một chổ chưa ổn lắm là nếu người dùng chọn size ảnh nào thì function Lambda cũng được trigger nếu size đó chưa tồn taị trên Bucket, để giải quyết vấn đề này thì bạn nên chỉnh lại đoạn code với việc cấu hình chỉ cho phép resize hình về những size nhất định mà bên mình quy định mà thôi. Ví dụ chỉ cho resize về các kích thước 300x300, 600x600, 800x800 còn nếu truyền lên các kích thước khác thì mình sẽ trả về hình ảnh gốc. 5. Để cấu hình cho function của bạn, trên Environment variables bạn thêm 2 cặp key-value sau: Key: BUCKET, value: tên Bucket mà bạn đã tạo ở trên. Key: URL, value: endpoint ở trên. 6. Định nghĩa execution role permissions cho function, trên Role chọn Create a custom role, View Policy Document, Edit, OK. 7. Thay thế tên Bucket YOUR_BUCKET_NAME_HERE bằng tên Bucket mà bạn đã tạo ở trên, copy đoạn code sau và bỏ vào policy document.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::__YOUR_BUCKET_NAME_HERE__/*"    
    }
  ]
}
  1. Lưu lại và test.

Kết luận:

Trên đây là cách resize hình ảnh trên S3 Amazon sử dụng Lambda function. Cảm ơn các bạn đã đọc.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí