Golang x Lambdaで画像をS3へアップロードする

公開:2020/10/24
更新:2020/10/24
3 min読了の目安(約2700字TECH技術記事

Golang on AWS Lambdaで、API GatewayからBase64形式の画像データを受け取って画像ファイルに変換した上でS3へアップロードする方法をまとめました。

この記事の説明で使っているコードは以下に掲載していますので、ご自由にお使いください。
yu-croco/golang_lambda_s3_sample
*serverless frameworkを使っています

実装

1. API Gatewayからデータを受け取る

POSTリクエストのAPI GatewayからBodyを取得するためには、リクエストBodyをbyteに変換した上でjson unmarshalで変換したい構造体を指定することでデータを取得できます。
*もっといい方法あるかもしれません。

ナイーブな実装としては以下の感じでしょうか

リクエストBodyの変換
func Handler(apiRequest events.APIGatewayProxyRequest) error {
	jsonBytes := []byte(requestBody)
	req := new(model.Request)

	if unmarshalErr := json.Unmarshal(jsonBytes, req); unmarshalErr != nil {
		return nil, unmarshalErr
	}
	...
}

2. Base64形式の画像データを処理する

構造体に変換したデータを用いてBase64形式の画像データをjpg形式に変換し、変換したデータをLambdaのローカルに一時保存します。

base64.StdEncoding.DecodeString を使ってデコードして、特定のファイル名でファイルを作成して保存する感じです。今回は/tmp 配下にjpgファイルを作成しています。

元データをデコードしてローカルに保存
type localFileRepositoryImpl struct {
	Request *model.Request
}

func NewLocalFileRepositoryImpl() domain.LocalFileRepository {
	return &localFileRepositoryImpl{}
}

func (impl *localFileRepositoryImpl) Add(req *model.Request, jstCurrentUnixTime int) error {
	data, decodeErr := base64.StdEncoding.DecodeString(req.Image)
	if decodeErr != nil {
		return decodeErr
	}

	file, err := os.Create(util.LambdaLocalFilePath(jstCurrentUnixTime))
	if err != nil {
		return err
	}

	defer file.Close()
	file.Write(data)

	return nil
}

3. S3へ画像をアップロードする

aws-sdk-go を使ってS3へ画像をアップロードします。先程作成したjpgファイルのパスを指定してS3へuploadします。
アップロードした画像がpublicで問題ない場合には、ACLpublic-read にしておきます。これでアップロード完了後すぐにリソースを読み込めます。

Lambda側ではS3へのアクセス権限を付与することをお忘れなく。

S3へ画像をアップロード
type s3RepositoryImpl struct{}

func NewS3RepositoryImpl() domain.S3Repository {
	return s3RepositoryImpl{}
}

func (impl s3RepositoryImpl) Add(req *model.Request, jstCurrentUnixTime int) error {
	file, openErr := os.Open(util.LambdaLocalFilePath(jstCurrentUnixTime))
	if openErr != nil {
		return openErr
	}

	defer file.Close()
	if uploadErr := impl.upload(req, file, jstCurrentUnixTime); uploadErr != nil {
		return uploadErr
	}

	return nil
}

func (impl s3RepositoryImpl) upload(req *model.Request, file *os.File, jstCurrentUnixTime int) error {
	uploader := s3manager.NewUploader(impl.newSession())

	if _, err := uploader.Upload(&s3manager.UploadInput{
		Bucket: aws.String(os.Getenv("BUCKET_NAME")),
		Key:    aws.String(util.S3FilePath(req, jstCurrentUnixTime)),
		Body:   file,
		ACL:    aws.String("public-read"),
	}); err != nil {
		return err
	}

	return nil
}

func (impl s3RepositoryImpl) newSession() *session.Session {
	return session.Must(session.NewSession(&aws.Config{
		Region: aws.String("ap-northeast-1"),
	}))
}