Cloudflare ImagesにNode.jsからファイルアップロード
はじめに
Cloudflare ImagesにAPIを使ってファイルアップロードするときは、
multipart/form-data
を送る必要があります。
シェルで実行する場合は、こんな風にかなりシンプルでOK
curl -X POST \
-F file=@./image.png \
-F 'requireSignedURLs=true' \
-F 'metadata={"key": "value"}' \
-H "Authorization: Bearer ${API_TOKEN}" \
https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/images/v1
しかしNode.jsでやろうとするとけっこう面倒くさかったので、
メモに残しておきます。
TL;DR
ファイルアップロードの場合
const axios = require('axios')
const fs = require('fs')
const FormData = require('form-data')
const main = async () => {
try {
const form = new FormData()
form.append('requireSignedURLs', 'true')
form.append('metadata', JSON.stringify({ key: 'value' }))
// Cloudflare Imagesでは [jpeg, png, webp, gif, svg] に対応
const filepath = 'image.png'
const buffer = fs.readFileSync(filepath)
form.append('file', buffer, { filename: filepath }) // mimetypeは指定不要(FormDataが自動判別)
const { data } = await axios(`https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/images/v1`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
data: form,
})
console.log(data)
} catch (e) {
console.error(e.response?.data ?? e)
}
}
main()
URLアップロードの場合
const axios = require('axios')
const fs = require('fs')
const FormData = require('form-data')
const main = async () => {
try {
const form = new FormData()
form.append('requireSignedURLs', 'true')
form.append('metadata', JSON.stringify({ key: 'value' }))
// Cloudflare Imagesでは [jpeg, png, webp, gif, svg] に対応
const url = 'https://example.com/image.png'
form.append('url', url) // filenameは指定不要
const { data } = await axios(`https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/images/v1`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
data: form,
})
console.log(data)
} catch (e) {
console.error(e.response?.data ?? e)
}
}
main()
詳解
Cloudflare APIの基本
ファイルアップロードのドキュメントはこちら
- Upload an image using a single HTTP request
- Upload an image via URL
APIトークンは、この画面から作成。
アカウントIDも右端で確認可能(キャプチャではボカシ入れてます)
それぞれ環境変数 ACCOUNT_ID
API_TOKEN
に入れておきましょう。
ライブラリform-data
multipartで送れてない場合は、Cloudflareからこんなエラーが返ってきます。
ERROR 5415: Images must be uploaded as a form, not as raw image data. Please use multipart/form-data format
ブラウザのフォームからファイル添付したときと同じ状態をNode.js上で作る必要があって、
ライブラリform-data
を使って実現できます。まずはインストール。
yarn add form-data
const form = new FormData()
form.append('requireSignedURLs', 'true')
form.append('metadata', JSON.stringify({ key: 'value' }))
FormData
インスタンスを作って、APIの仕様にそってデータを何度もappend
する。
(なぜコンストラクタに渡せないのだろうか・・・)
ファイルアップロードの場合
const filepath = 'image.png'
const buffer = fs.readFileSync(filepath)
form.append('file', buffer, { filename: filepath }) // mimetypeは指定不要(自動判別)
Buffer
オブジェクトを作ってfile
プロパティにappend
すればOK。
第3引数のオプションにfilename
を指定しないと、ファイル名がブランクで飛んでいきます。
ローカルのファイル名と別名をつけ直してももちろんOK
filename
と同列にcontentType
を指定することもできるのですが、
FormDataが最適なコンテンツタイプを自動判別してくれるので、明記しなくてもOK
URLアップロードの場合
const url = 'https://example.com/image.png'
form.append('url', url) // filenameは指定不要
URLの文字列をurl
プロパティにappend
すればOK。
こちらはfilename
も自動判別で、指定しても無視されます。
URLアップロードの場合は、実際のファイル名が変えられないということなんですね。
ちなみにurl
とfile
を同時に指定すると、こんなエラーが起きます。
ERROR 5400: Bad request:
file
andurl
fields are mutually exclusive
リクエストボディにFormDataを指定
あとは、上で作ったFormData
オブジェクトをリクエストボディに乗せてあげればOK。
以下はaxiosでの例です。
const { data } = await axios(`https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/images/v1`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
data: form,
})
ちなみにaxios
の場合、以前はform.getHeaders()
をヘッダーに明記してあげないと、
multipart/form-data
のリクエストになってくれなかったのですが、
v0.27以降は省略可能になりました〜。
(というのをこの記事書いてる途中に発見して、別途Qiitaに記事を書きましたw)
コード全体
const axios = require('axios')
const fs = require('fs')
const FormData = require('form-data')
const main = async () => {
try {
const form = new FormData()
form.append('requireSignedURLs', 'true')
form.append('metadata', JSON.stringify({ key: 'value' }))
// Cloudflare Imagesでは [jpeg, png, webp, gif, svg] に対応
// ファイルアップロードの場合
const filepath = 'image.png'
const buffer = fs.readFileSync(filepath)
form.append('file', buffer, { filename: filepath }) // mimetypeは指定不要(FormDataが自動判別)
// URLアップロードの場合
// const url = 'https://example.com/image.png'
// form.append('url', url) // filenameは指定不要
const { data } = await axios(`https://api.cloudflare.com/client/v4/accounts/${process.env.ACCOUNT_ID}/images/v1`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
data: form,
})
console.log(data)
} catch (e) {
console.error(e.response?.data ?? e)
}
}
main()
catch
内でゴニョゴニョやってますが、
axiosのエラーとその他エラーで表示内容を切り替えているということです。
axiosのエラーオブジェクトはめちゃくちゃ長くて、全部出すと読めたもんじゃないのでw
ではまた!
参考文献
Nodeでmultipart/form-dataを送る
Node.js上からmultipart/form-data形式でHTTPリクエストをする
axios v0.27からmultipart/form-dataが簡単に送れるようになった
Discussion