AWS APIGateway+lambda+S3を使ってダウンロード機能を実装する
背景
AWS使ってサーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。大分自分が欲しかった機能は実装出来てきた。
今後の為にもDynamoDBをバックアップをしたい。DynamoDB自体にバックアップ機構はあるものの、間違えてテーブル自体を削除してしまった時(構築スクリプトミスとか)の為に、CSVやjsonファイルなどでローカルPCに置いておきたい。
方法を考察
手法は色々あると思うが、大きく分けて以下の2つになる。
- どこかサーバー側で、aws-cliなどを使ってファイル出力する
- web画面にダウンロード機能を追加する
やっぱりここはダウンロード機能。このサービス開発は勉強も兼ねてるし。
ここで、単純にwebページからダウンロードと言ってもまたそこで細かい手法が存在する。
- データを取得して、クライアント側で文字列作成してダウンロード処理
- サーバー側で生成したファイルストリームをダイレクトレスポンスで返す
- S3にアップして、そのアドレスを返し、リダイレクト
- S3にアップ。フォルダ一覧ページも用意してそこからリンククリックダウンロード
考察ポイント
- 1はクライアントのみで処理が完結するもの向け
- 2,3はサイズが小さいファイル向け、4は比較的大きいファイル向け(大きいファイルは作成にも時間かかるので、ダウンロード失敗などで再実行したくない)。
- ファイル作成する方法だと、その後処理を考えないと不要なファイルが溜まっていく。
- 処理はLambdaでやる予定。 AWS Lambda の制限 は気を付ける。
決定した方針
- 将来的には扱うサイズが大きくなるかもしれないが、3番目のS3にアップしてそのアドレスを返してリダイレクトを選択する。
- 全データを出力するのでなく、一定期間で指定して出力できるようにして、そもそもデータ量を多くしないようにする。
- 出力するS3フォルダはログインユーザー毎のフォルダ
- S3バケットのライフサイクルポリシーを設定して一定期間過ぎたファイルは消す様にする。
- 関係するテーブル全て(伝票情報と残額情報)が対象
- パラメーターは開始終了日
実装
以下の技術要素が必要になる。
- lambdaでファイル作成
- 複数ファイルをzip圧縮
- S3のフォルダを決定する為の認証情報をlambdaに渡す
- lambdaからS3へファイルアップロード
- 返却されたS3のurlを使ってダウンロード実行
lambdaでファイル作成
今、lambdaで使用している言語はPython。Pythonにはファイル出力機能はもちろんあるが、lambdaでは物理マシンの概念が無く、用意されているのは/tmpフォルダのみ。このフォルダはOS的には一時ディレクトリと呼ばれる場所。一定時間が切れたり再起動されたら消える。反対に言えば放置してもOS的に処理してくれる。
tempfileモジュールで取得したフォルダを使うべきなのか、公式ページ上でlambdaで使用できるフォルダとして指定されてる/tmp表記を直接使うべきか悩むところではあったけど、tempfileモジュールを使う事に決定。
後々圧縮するので、tempfile.TemporaryDirectoryで一時ディレクトリ作って、その中で色々する事にする。
複数ファイルをzip圧縮
ここはそんなに悩む場所ではない。zipfileモジュール使って普通に処理。
S3のフォルダを決定する為の認証情報をlambdaに渡す
最初は認証のjwtトークンをlambdaに渡して色々する必要があると思ったが、S3のフォルダ名に使用するidentityIdはそこからは取得できなさそうだった。その為、クライアント側で取得できるidentityIdを普通にクエリパラメーターでそのまま渡す事にした。
lambdaからS3へファイルアップロード
ここもそんなに悩む場所ではない。boto3モジュールで普通に出来る。
ここまでのlambda側処理は以下の感じ。try句が2重になっているが、圧縮前ファイルをcloseした状態でないとちゃんとファイルに出力されていない為。flushすればいいかもしれないが、ファイルポインタ解放の為にもcloseすべき。そして、S3アップの前に一時ディレクトリをcloseしてしまうと消えてしまう可能性があ。という事で、結果的に2重のtry句になった。
# 宣言部分
import boto3
import os
import tempfile
import zipfile
# 中略
tmpdir = tempfile.TemporaryDirectory()
try:
packed_full_name = os.path.join(tmpdir.name, packed_file_name)
slip_full_name = os.path.join(tmpdir.name, slip_file_name)
balance_full_name = os.path.join(tmpdir.name, balance_file_name)
# 中略
slip_file = open(slip_full_name, 'w')
balance_file = open(balance_full_name, 'w')
try:
# 出力処理部分
finally:
slip_file.close()
balance_file.close()
# 出力ファイルをまとめてzip圧縮
with zipfile.ZipFile(packed_full_name, 'w', compression=zipfile.ZIP_DEFLATED) as new_zip:
new_zip.write(slip_full_name, arcname=slip_file_name)
new_zip.write(balance_full_name, arcname=balance_file_name)
# S3へアップロード。event['identityid'] がクエリパラメータで渡されたCognitoのidentityid
s3 = boto3.resource('s3')
bucket = s3.Bucket('myapp-userdata')
bucket.upload_file(packed_full_name, 'cognito/myhome-account/' + event['identityid'] + '/' + packed_file_name)
finally:
tmpdir.cleanup()
返却されたS3のurlを使ってダウンロード実行
出力先のS3バケットが、publicならそのurlを直接使えるが、privateの場合、AccessDeniedが出てしまう。s3.getSignedUrl で一時ダウンロードURLを生成し、それを使わなければならない。生成出来たらあとはlocation.hrefに指定するだけ。
download: function () {
const cognitoUser = this.$cognito.userPool.getCurrentUser()
var that = this
cognitoUser.getSession((err, session) => {
if (!err && session.isValid()) {
const itoken = session.getIdToken().getJwtToken()
// Initialize the Amazon Cognito credentials provider
AWS.config.region = awsconfig.Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsconfig.IdentityPoolId,
Logins: {
[PROVIDER_KEY]: itoken
}
})
var identityId = AWS.config.credentials.identityId
//・・中略・・
that.$axios.get(that.apienv.baseendpoint + 'download?' + prmstr, config).then(
response => {
var s3 = new AWS.S3({
params: { Bucket: S3_USERBACKETNAME }
})
var getUrlparams = {
// バケット名
Bucket: S3_USERBACKETNAME,
// S3に格納済みのファイル名(ファイル名がサーバー側から返ってくる)
Key: 'cognito/myhome-account/' + identityId + '/' + response.data,
// 期限(秒数)
Expires: 900
}
// URL発行
s3.getSignedUrl('getObject', getUrlparams, that.execdownload)
}
).catch(err => {
that.$message({message: err, type: 'error'})
})
}
})
},
execdownload: function (dummy, url) {
location.href = url
},
学んだ事
- ファイル出力を確定する前に、そのファイルに対して別の処理してはいけない。
- jwtから取得できる情報にはidentityId(s3などのユーザー毎キーになる)は含まれてない(?)
- オープンでないS3バケットに上げたものは、getObjectで直接取得できない。
- s3.getSignedUrlで一時URLを発行し、location.href すべし。
参考にさせてもらったページ
AWS Lambda の制限
Lambda + API Gateway入門。CSVやCORS
API Gateway + Lambdaでバイナリダウンロード
DynamoDBからデータをCSVにエクスポートする方法4つ
【備忘録・まとめ】AWS Lambda 開発者ガイド
【JavaScript入門】ファイルダウンロード処理を実装する方法とは?
Amazon Cognitoの認証情報を取得してみる~API Gateway+Lambda編~
【vue.js】AWS-S3へ簡単にファイルをアップロードする方法
Author And Source
この問題について(AWS APIGateway+lambda+S3を使ってダウンロード機能を実装する), 我々は、より多くの情報をここで見つけました https://qiita.com/silverbox/items/91a9c0482d890cf98bb5著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .