😑
[AWS] Serverless Framework + Express で binary を返す
てこずってしまったのでメモ。
TL;DR
serverless() のオプションに binary として扱いたいタイプの配列を渡せばよい。
import serverless from 'serverless-http'
// 略...
module.exports.handler = serverless(app, { binary: ['image/*'] })
環境
Serverless Framework のExpress 用テンプレートをもとに開始。
Typescript 関連も追加。
$ serverless install --url https://github.com/serverless/examples/tree/v3/aws-node-express-api
$ npm install --save-dev @types/aws-lambda serverless-plugin-typescript
# 略
serverless.yml
# 略...
functions:
api:
handler: index.handler
events:
- httpApi: '*'
plugins:
- serverless-domain-manager
- serverless-plugin-typescript
custom:
customDomain:
domainName: my-domain.com
basePath:
certificateName: my-domain.com
createRoute53Record: true
endpointType: 'regional'
apiType: http
securityPolicy: tls_1_2
# 略...
S3 から GetObject で .png を取ってきてそのままクライアントに渡すのがやりたかった。
index.ts
// 略...
async getPng(request: Request, response: Response, next: NextFunction) {
const filename = `test.png`
const s3 = new S3Client({ region: 'ap-northeast-1', })
const output = await s3.send(
new GetObjectCommand({
Bucket: 'my-bucket',
Key: `${filename}`,
ResponseContentType: 'image/png',
})
)
const readable = output.Body as Readable
response.setHeader('Content-Type', 'image/png')
response.setHeader('Content-disposition', `attachment;filename=${filename}`)
response.setHeader('Content-Length', output.ContentLength as number)
const chunks = []
for await (let chunk of readable) {
chunks.push(chunk)
}
const buf = Buffer.concat(chunks)
return buf
}
// 略...
問題
test.png は見かけ上、無事ダウンロード。しかし、表示できない、壊れている?
バイナリエディタで確認。
表示できなかった test.png
S3から直接取ってきた test.png
表示できないファイルの先頭が EF BF BD
になってしまっている。Google 先生曰く、エンコードの問題?
とにかく、クライアントに届くまでに何らかの変換が行われてしまっている。
buf をそのまま生のバイナリとして送ってほしいのだけど。。。
試行錯誤
charset 指定してみる?
response.setHeader('Content-Type', 'image/png; charset=UTF-8')
だめ。著変無し。
REST API みたく json で返してみる?
return {
statusCode: 200,
body: buf.toString('base64'),
isBase64Encoded: true
}
だめ。そのままのjsonがクライアントに渡るだけ。
pipe してみる?
readable.pipe(response)
だめ。著変無し。
binary 明示して返してみる?
response.end(buf, 'binary')
だめ。著変無し。
ArrayBuffer 返してみる?
return buf.buffer
だめ。なんか変なのが返された。
Serverless のプラグイン使ってみる?
$ npm install --save-dev serverless-plugin-custom-binary
serverless.yml
# 略...
plugins:
- serverless-domain-manager
- serverless-plugin-typescript
- serverless-plugin-custom-binary
custom:
apigatewayBinary:
types:
- image/*
# 略...
だめ。著変無し。(HTTP API 用ではないので当たり前)
成功!
serverless-http のドキュメントに書いてあった。。。
serverless-http | Advanced Options
serverless() のオプションに binary として扱いたいタイプの配列を渡せばよい。
module.exports.handler = serverless(app, { binary: ['image/*'] })
Take home message
公式ドキュメント大切。
Discussion