AWS API Gateway + Lambda + S3 でバイナリデータを保存する

AWS API Gateway + Lambda + S3 でバイナリデータを保存する:

Qiitaにて他の方も書いている内容になります

今回これを紹介するにあたり画像加工を行うマイクロサービスを作りました

今回はそのサービスの一部のHTTP通信でS3にリソースファイルをアップロードする部分をピックアップしています


作製したマイクロサービス

  1. HTTP通信でS3(INPUT用バケット)にアップロード

    ※直接S3にアップロードしても可

  2. S3(INPUT)にアップロードされるとトリガーで画像加工Lambdaが起動

    ※GM on ImageMagick

    アップロードするファイルは以下の3つ



    • 画像リソース(複数)
    • 加工処理レシピファイル
    • コールバックファイル(任意)

  3. 画像加工処理



    • リサイズ
    • 画像合成
    • テキスト合成
    • 加工後ファイルに画像合成して更に加工する
    • ファイルフォーマット変更
  4. 完了後にS3(OUTPUT)に保存

    ※ホスティングを有効にしています
  5. コールバックファイルがある場合は、コールバック先へ加工後のリソースのURLとコールバックファイル内に記載されたレスポンスデータを返します


環境

ServerlessFramework 1.24.1
Node.js 6.10


実装


コード

serverless.yml
# Welcome to Serverless! 
 
service: serverless-imagemanager 
 
# You can pin your service to only deploy with a specific Serverless version 
# Check out our docs for more details 
frameworkVersion: "=1.24.1" 
 
# 設定定義 
custom: 
  defaultStage: dev 
  # AWSの接続先(aws_credentials) 
  profiles: 
    dev: default 
    prod: default 
 
# AWSに反映する設定定義 
provider: 
  name: aws 
  runtime: nodejs6.10 
  region: ap-northeast-1 
  stage: ${opt:stage, self:custom.defaultStage} 
  profile: ${self:custom.profiles.${self:provider.stage}} 
  memorySize: 1024 
  timeout: 12 
  deploymentBucket: deploy.${self:provider.stage}.${self:service} 
  environment: 
    suffix: ${self:provider.stage} 
    service_name: ${self:service} 
 
  # Lambda function's IAM Role 
  # https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-arns-and-namespaces.html 
  iamRoleStatements: 
    - Effect: 'Allow' 
      Action: 
        # Gives permission to Lambda in a specific region 
        - lambda:InvokeFunction 
        # Gives permission to S3 bucket in a specific 
        - s3:* 
      Resource: 
        - 'arn:aws:s3:::${self:service}.input.dev/*' 
        - 'arn:aws:s3:::${self:service}.input.prod/*' 
        - 'arn:aws:s3:::${self:service}.output.dev/*' 
        - 'arn:aws:s3:::${self:service}.output.prod/*' 
        - '*' # Lambda 
 
# you can add packaging information here 
package: 
  exclude: 
    - .DS_Store 
    - .git/** 
    - .serverless/** 
    - .npmignore 
    - .gitignore 
 
# APIリスト 
# https://serverless.com/framework/docs/providers/aws/guide/events/ 
# https://serverless.com/framework/docs/providers/aws/guide/serverless.yml/ 
functions: 
  # HTTPリクエストでS3にアップロード. 
  request_s3_upload_input: 
    handler: src/request_s3_upload_input.handler 
    memorySize: 256 
    events: 
      - http: 
          path: imagemanager/uploads 
          method: post 
 
# CloudFormation resource templates 
# http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html 
resources: 
  Resources: 
    # S3 
    input: 
      Type: AWS::S3::Bucket 
      Properties: 
        BucketName: ${self:service}.input.${self:provider.stage} 
      #DeletionPolicy : Retain 
    output: 
      Type: AWS::S3::Bucket 
      Properties: 
        BucketName: ${self:service}.output.${self:provider.stage} 
      #DeletionPolicy : Retain 
src/request_s3_upload_input.handler
'use strict'; 
 
const Aws = require('../libs/aws.js'); 
const Logger = require('../libs/logger.js'); 
 
const Resource = require('../model/resource.js'); 
 
/** 
 * HTTPプロトコルからアップロード. 
 **/ 
module.exports.handler = (event, context, callback) => { 
  Logger.info("start request_s3_upload_input."); 
 
  s3upload(event, context) 
    .then((result) => { 
      callback(null, { 
        statusCode: 200, 
        body: JSON.stringify(result) 
      }); 
    }) 
    .catch((error) => { 
      Logger.error(error); 
      callback(null, { 
        statusCode: 500, 
        body: JSON.stringify(error) 
      }); 
    }); 
}; 
 
function s3upload(event, context) { 
  return new Promise((resolve, reject) => { 
    Logger.info(event); 
 
    // コンテントタイプの文字列が固定されていなかったので網羅. 
    let contentType = event.headers["Content-Type"]; 
    if (!contentType) { 
      contentType = event.headers["Content-type"]; 
    } 
    if (!contentType) { 
      contentType = event.headers["content-type"]; 
    } 
    if (!contentType) { 
      contentType = event.headers["content-Type"]; 
    } 
 
    const queryParams = event.queryStringParameters; 
    const key = queryParams.key; 
 
    const resource = new Resource(); 
 
    Logger.debug("contentType: " + contentType); 
    Logger.debug(queryParams); 
 
    let body; 
    switch (contentType) { 
      case "image/jpeg": 
      case "image/gif": 
      case "image/png": 
        // リクエストボディに設定された画像データはBase64エンコードされているので、デコードする 
        body = Buffer.from(event.body, 'base64'); 
        break; 
      default: 
        body = event.body; 
    } 
 
    const params = { 
      Bucket: resource.getBucketName(), 
      Key   : key, 
      Body  : body, 
      ContentType: contentType 
    }; 
    Logger.debug(params); 
    resource.savePromiseForS3(params).then(resolve()); 
  }); 
} 
model/resource.js
'use strict'; 
 
const Aws = require('../libs/aws.js'); 
const Logger = require('../libs/logger.js'); 
 
module.exports = class Resource { 
  getBucketName() { 
    return process.env.service_name + ".input." + process.env.suffix; 
  } 
  promiseForTakeOverData(object) { 
    return new Promise(function(resolve, reject) { 
      resolve(object); 
    }); 
  } 
 
  // S3 リソースを参照. 
  loadPromiseForS3(bucketName, key) { 
    const s3 = Aws.s3(); 
 
    return new Promise(function(resolve, reject) { 
      // S3から読み込み 
      s3.getObject({Bucket: bucketName, Key: key}, function(err, data) { 
        if (err) { 
          Logger.error("Error: loadPromiseForS3." + err.toString()); 
          reject(err); 
        } else { 
          Logger.info("Success: loadPromiseForS3 " + key); 
          resolve(data.Body); 
        } 
      }); 
    }); 
  } 
 
  // S3 保存. 
  savePromiseForS3(params) { 
    const s3 = Aws.s3(); 
 
    return new Promise(function(resolve, reject) { 
      // S3に書き込み 
      s3.putObject(params, function(err) { 
        if (err) { 
          Logger.error("Error: savePromiseForS3." + err.toString()); 
          reject(err); 
        } else { 
          Logger.info("Success: savePromiseForS3 " + JSON.stringify(params, null, 2)); 
          resolve(); 
        } 
      }); 
    }); 
  } 
} 


使い方

デプロイすることでAPIGatewayにリクエストを受けつけるURLが作られます

  • メソッド: POST
  • Body部: Base64エンコードしたバイナリソース
  • URLクエリパラメータ: 『?key=<<保存ファイル名>>』


注意点

APIGatewayの設定のバイナリメディアタイプ


03.png

※設定を反映するには再度デプロイが必要になります

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)