「AWS Amplify + Reactで既存のLambdaを呼び出す」では、既存のLambdaをAWS Amplify + Reactから呼び出す方法について紹介しました。
今回は、この発展形でLambdaに対してファイルを渡す方法、Reactから見るとファイルアップロードを行う方法について記載します。
解法
フロントエンド(React)
非常にシンプルなReactでの実装例になります。
import React, { useState } from 'react'
import { Auth } from 'aws-amplify'
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
const initialState = {
file: null,
someMetadata: ''
}
const SomeFileUploader = () => {
const [formState, setFormState] = useState(initialState)
const setInput = (key, value) => {
setFormState({ ...formState, [key]: value })
}
const convertBase64 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.readAsDataURL(file)
fileReader.onload = () => {
resolve(fileReader.result)
}
fileReader.onerror = (error) => {
reject(error)
}
})
}
const onFileInputChange = async (event) => {
const file = event.target.files[0]
const base64 = await convertBase64(file)
setInput('file', base64.replace(/^data:\w+\/\w+;base64,/, ''))
}
const uploadFile = async() => {
if (!formState.file) return
// メタデータも適当に設定(ファイルアップロードには無関係)
formState.someMetadata = 'メタデータです'
const credentials = await Auth.currentCredentials()
const client = new LambdaClient({
credentials: Auth.essentialCredentials(credentials),
region: process.env.REACT_APP_LAMBDA_REGION
})
const input = {
FunctionName: 'some-existing-lambda-' + process.env.REACT_APP_STAGE,
Payload: JSON.stringify(formState)
}
const command = new InvokeCommand(input)
const response = await client.send(command)
if (response) {
setFormState(initialState)
} else {
console.error(response.error)
}
}
return (
<Box>
<input
type="file"
onChange={onFileInputChange}
/>
<Button
onClick={uploadFile}
text="Upload File"
/>
</Box>
)
}
export default SomeFileUploader
Reactからsome-existing-lambda-dev
という名前の既存のLambdaを呼び出す例になっています。
.env
ファイルには下記のような設定をしています。
REACT_APP_STAGE=dev
REACT_APP_LAMBDA_REGION=ap-northeast-1
ファイルアップロードに関する箇所を以下にピックアップします。
<input
type="file"
onChange={onFileInputChange}
/>
でファイル選択のUIを作成します。このUIでは選択したファイルが異なる度にonChange
に指定したonFileInputChange
がコールされます。
onFileInputChange
は次のようになっています。
const onFileInputChange = async (event) => {
const file = event.target.files[0]
const base64 = await convertBase64(file)
setInput('file', base64.replace(/^data:\w+\/\w+;base64,/, ''))
}
まず、event.target.files[0]
で選択したファイルオブジェクトの参照を取得し、convertBase64
関数に渡してファイルコンテンツをBase64エンコードします。Base64エンコード結果は
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA.........
のようにメタ情報が最初についてきます。このメタ情報(〜base64,
まで)はファイルコンテンツのデータとしては不要なので、
base64.replace(/^data:\w+\/\w+;base64,/, '')
で除去し、formState.file
に結果をセットしています。
あとはaws-sdk
のLambda Clientを使ってLambdaを呼び出しています。
See Also
バックエンド(Lambda)
PythonによるLambdaの実装例を示します。 フロントエンドでアップロード指定したファイルを受け取ってS3に保存します。
import os
import logging
import boto3
import botocore.exceptions
import json
import base64
logging.basicConfig(
format="%(asctime)s %(module)s:%(funcName)s(%(lineno)d) - %(levelname)s - %(message)s"
)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
bucket_name = os.environ['S3_BUCKET']
upload_dir = os.environ['UPLOAD_DIR']
def lambda_handler(event, context):
s3 = boto3.resource('s3')
bucket = s3.Bucket(bucket_name)
key = "%s/%s" % (upload_dir, event['filename'])
body = base64.b64decode(event['file'])
try:
bucket.put_object(
Key = key,
Body = body,
Metadata = {
'some-metadata': event['someMetadata']
}
)
logger.info("S3にアップロードしました。 key: %s" % key)
except Exception as e:
logger.error("S3へのアップロードでエラーが発生しました。処理を中断します。: Exception name is %s. Detail is %s" % (type(e).__name__, e))
raise
ret_body = {
"input": event
}
return {
"statusCode": 200,
"body": json.dumps(ret_body)
}
# メイン関数
if __name__ == "__main__":
lambda_handler({"filename": "", "someMetadata": ""}, {})
event['file']
にBase64エンコードしたファイルコンテンツが入っていますので、
body = base64.b64decode(event['file'])
でデコードしてbucket.put_object()
メソッドにBody
として渡すことでS3に保存できます。
なお、put_objectメソッドではMetadata
にdictを渡すと、dictのkey名のカスタムメタデータをオブジェクトに対して設定できます。ここではsome-metadata
というカスタムメタデータに対して、Reactで入力したevent['someMetadata']
を指定しています。