はじめに
忙しいタイトルをしていて、大変申し訳ありません。
現在、以下のアプリ構成でお仕事をしています。
フロントエンド: Vue.js
バックエンド: Flask (python)
Flaskの方については、これまで全く触ったことがなかったので、新鮮さを噛みしめつつ、日々触っています。
そもそもpythonの方も、ファイルを読み込んでcsvを吐き出してくれるというプチツールを作ったことがあるぐらいですので、多くのことを勉強させてもらっています。
本記事で書くこと
タイトルそのままですが、以下を実現します。
- Flask(Api)が、AWS SDK for Python (Boto3)を使って、AWS S3からファイルを取得する。
- 取得したファイルデータをレスポンスに詰め、クライアント(Vue.js)に返却する。
- クライアント(Vue.js)がレスポンスからファイルをダウンロードする。
Lambda + API GateWayで良いのでは・・・
こんなこともできるんだというぐらいの気持ちで読んでください。
本記事で書かないこと
- Lambda + API GateWayで上記の流れを実現すること
- Flaskについてのあれこれ
- pythonについてのあれこれ
- aws関連についてのあれこれ
- Vue.jsについてあれこれ
- fetch APIのあれこれ
おそらく上記は色々な方が実践済みだと思うので。
なぜやろうと思ったのか
そもそも、今回の機能は amplify で実現する予定でした。
が、「Internet Explorer」の場合は、なぜか、aws credentialの認証情報がamplifyにわたってくれず、s3からファイルを取得しようしたときに、403エラーになるという状況に・・・
そのため、このように実装することにしました。
ですので、「Internet Explorer」を対象外とする場合は、素直にamplifyを使うことをオススメします・・・
環境
- vue: 2.6.11
- boto3: 1.12.38
- Flask: 1.1.2
- python: 3系
事前準備
- awscli、boto3をインストールしておく
-
aws configure
で、awscliを使えるようにしておく
それでは内容へ
1. Flask(Api)が、AWS SDK for Python (Boto3)を使って、AWS S3からファイルを取得する。
s3からファイルを取得する場合に、以下二つの方法があります。
boto3.resource('s3').Bucket(BUCKET).download_file(Filename=KEY, Key=KEY)
boto3.client('s3').get_object(Bucket=bucket, Key=key)
Flaskのローカル(アプリケーション上)にファイルがダウンロードされてしまうと、そのファイルを消したりしないといけないのが面倒なので、後者のメモリ上にダウンロードする方法を採用しました。
ソースコード
@app.route('/file', methods=['GET'])
def get_file_from_s3():
s3 = boto3.client('s3')
bucket = 'YOUR_BUCKET_NAME'
key = 'YOUR_FILE_NAME'
obj = s3.get_object(Bucket=bucket, Key=key)
これで、/file
のエンドポイントを叩くことで、s3からファイルオブジェクトを取得することができました。
このobj
の内、Body
のバイト配列を今回は利用します。
2. 取得したファイルデータをレスポンスに詰め、クライアントに返却する。
それでは、取得したファイルオブジェクトからレスポンスをつくりましょう。
レスポンスはmake_response()でつくります。
ソースコード
@app.route('/file', methods=['GET'])
def get_file_from_s3():
s3 = boto3.client('s3')
bucket = 'YOUR_BUCKET_NAME'
key = 'YOUR_FILE_NAME'
obj = s3.get_object(Bucket=bucket, Key=key)
response = make_response()
# レスポンスobjectのdataに取得したファイル情報を設定します。
# read()で、ファイルを読み込みます。
response.data = obj['Body'].read()
# ダウンロード時のファイル名を定義します。
# quoted_filenameでURLエンコードします。また、日本語のファイル名でもできるように、UTF-8エンコーディングもします。
quoted_filename = urllib.parse.quote(name)
response.headers['Content-Disposition'] = "attachment; filename='{}'; filename*=UTF-8''{}".format(quoted_filename, quoted_filename)
return response
3. クライアントがレスポンス情報をダウンロードする。
それでは、クライアント(Vue.js)でレスポンスのファイル情報をダウンロードしましょう。
今回はFetch APIを利用します。
ソースコード
fetch('http://XXXXXX/file', {
method: 'GET',
}).then((response) = >{
// blob()で、Blob形式にします
return response.blob();
}).then((blob) => {
// IEの場合は、msSaveBlobを使います
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, 'ファイル名.拡張子');
} else {
const a = document.createElement('a');
a.download = 'ファイル名.拡張子';
a.href = URL.createObjectURL(blob);
a.click();
}
})
これで、ファイル名.拡張子
のファイルをダウンロードできます。
ファイル名.拡張子
をContent-Disposition
のfilenameとしたい場合は、
**response.headers.get('Content-Disposition)**でContent-Disposition
を取得できるので、そこから抽出して使うと良いと思います。
参考にさせていただいた記事
Flaskでファイルダウンロードを実現する3つの方法
Python の boto3 で S3 とダウンロード/アップロードする
Djnagoメモ Content-Dispositionのfilenameに日本語をセットする