この記事では、Express.js アプリで S3 にホスティングされているファイルをダウンロードする実装例を紹介します。
まず1ファイルで処理の全体像を紹介したあと、もう少し本格的なアプリケーションらしくファイルを分割したバージョンも作成してみます。
処理の全体像
依存パッケージ
まず依存パッケージをインストールします。
$ npm i -S express aws-sdk
メインロジック
const express = require('express')
const AWS = require('aws-sdk')
const app = express()
// S3 を操作するためのインスタンスを生成
const s3Client = new AWS.S3({
accessKeyId: `アクセスキー`,
secretAccessKey: `シークレットキー`,
region: `リージョン`,
})
/**
* GET /download?filename=***
*/
app.get('/download', (req, res) => {
const { filename } = req.query
// ブラウザにダウンロードダイアログを表示させるための
// レスポンスヘッダーを付与
res.attachment(filename)
const params = {
Bucket: `バケット名`,
Key: filename,
}
// S3からダウンロードしたファイルの内容を
// ストリームオブジェクトに変換し、レスポンスに書き込む
s3Client.getObject(params)
.createReadStream()
.on('error', err => {
res.status(500).send({ error: err })
})
.pipe(res)
})
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`)
})
ポイントは以下の2点でしょう。
- res.attachment でブラウザにダウンロードダイアログを表示させるためのレスポンスヘッダー(
Content-Disposition
およびContent-Type
)を付与しています。 getObject
でダウンロードしたファイルは、createReadStream
でストリームオブジェクトに変換し、pipe
でレスポンスオブジェクトに書き込みます。こうすることでダウンロードしたファイルをブラウザに届けられるのですね。
リファクタリング
上掲のコードをいくつかのファイルに分割して見通しを改善してみようと思います。
ディレクトリ構成
最終的にこのような構成になります。
├─ controllers
│ └─ download.js
├─ services
│ ├─ download.js
│ └─ s3-client.js
├─ .env
└─ server.js
dotenv
S3 に接続するためのアクセスキーなどは設定ファイルに切り出して管理します。
今回は設定ファイルの内容を環境変数として読み出せる dotenv ライブラリを使用します。
$ npm install -S dotenv
.env
という名前で設定ファイルを作成します。
AWS_ACCESS_KEY=アクセスキー
AWS_SECRET_ACCESS_KEY=シークレットキー
AWS_S3_REGION=リージョン
AWS_S3_BUCKET=バケット名
dotenv は、動作環境によって異なる設定値を管理できます(たとえばテスト環境と本番環境の S3 バケットが異なるなど)。
また .env
はたいてい Git の管理から外します。パブリックなリポジトリにコードをアップロードしたときに接続パスワードなどのセンシティブな設定情報を晒さないためです。
Service
メインロジックのうち、HTTP メッセージの受送信、つまりリクエストやレスポンスとは関係のない部分をサービスとして切り出します。
S3クライアント
S3 クライアントを生成するコードです。
const AWS = require('aws-sdk')
const client = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_S3_REGION
})
module.exports = client
ダウンロードロジック
実際にファイルをダウンロードする関数を定義します。
const s3Client = require('./s3-client')
/**
* ファイルダウンロード
* @param {string} filename
* @returns {fs.ReadStream}
*/
function download(filename) {
const params = {
Bucket: process.env.AWS_S3_BUCKET,
Key: filename,
}
return s3Client.getObject(params).createReadStream()
}
module.exports = download
ストリームオブジェクトを生成するところまでをサービスの役割にしてみました。
このストリームオブジェクトを使ってレスポンスを作り出すのは、次に登場するコントローラーの役目です。
Contoller
HTTP メッセージの受送信を行うロジックをコントローラーに分類します。
const downloadService = require('../services/download')
/**
* ダウンロードコントローラーメソッド
*/
function download(req, res) {
const { filename } = req.query
res.attachment(filename)
downloadService(filename)
.on('error', err => {
res.status(500).send({ error: err })
})
.pipe(res)
}
module.exports = download
Server
最終的に、server.js
は以下のようになります。
const express = require('express')
const download = require('./controllers/download')
require('dotenv').config()
const app = express()
/**
* GET /download?filename=***
*/
app.get('/download', download)
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`)
})
以上、Express.js アプリで S3 にホスティングされているファイルをダウンロードする実装例を紹介しました。