LoginSignup
2
1

More than 5 years have passed since last update.

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

Posted at

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
※設定を反映するには再度デプロイが必要になります

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1