10.27.2022
Visualforceから(ContentVersionに)大容量ファイルをアップロードする
50MB をアップロードする機能要件が必須だったため試行錯誤。
Blob データを挿入または更新するから、REST API でマルチパートメッセージなら 2GB まで大丈夫らしい。
下記のようなリクエストボディを作る必要がある。
--boundary_string
Content-Disposition: form-data; name="entity_content";
Content-Type: application/json
{
"ContentDocumentId" : "069D00000000so2",
"ReasonForChange" : "Marketing materials updated",
"PathOnClient" : "Q1 Sales Brochure.pdf"
}
--boundary_string
Content-Type: application/octet-stream
Content-Disposition: form-data; name="VersionData"; filename="Q1 Sales Brochure.pdf"
Binary data goes here.
--boundary_string--
最初は、new FormData()
でボディを作ろうとしたけど Blob 以外はうまく渡せず断念。
Binary data goes here.
に、アップロードしたいファイルのバイナリデータをそのまま挟んであげないといけない。
その前後の文字列はTextEncoder
で Uint8Array に変換、挟み込むバイナリデータも Uint8Array にして 3 つのバイナリを一つにしてリクエストしてあげれば成功する感じ。
const uploadFile = async (
file_name_with_ext: ContentVersion['PathOnClient'],
description: ContentVersion['Description'],
reference_id: ContentVersion['FirstPublishLocationId'],
content_type: Blob['type'],
data: ArrayBuffer,
) => {
const te = new TextEncoder()
const res = await axios.post(
`/services/data/v53.0/sobjects/ContentVersion`,
new Uint8Array([
...te.encode(`--boundary_string
Content-Disposition: form-data; name="entity_content";
Content-Type: application/json
${JSON.stringify({
PathOnClient: file_name_with_ext,
Description: description,
FirstPublishLocationId: reference_id,
})}
--boundary_string
Content-Type: ${content_type}
Content-Disposition: form-data; name="VersionData"; filename="${file_name_with_ext}"
`),
...new Uint8Array(data),
...te.encode(`
--boundary_string--`),
]).buffer,
{
headers: {
Authorization: `Bearer ${session_id}`, // {!$Api.Session_ID}とか
'Content-Type': `multipart/form-data; boundary=\"boundary_string\"`,
},
},
)
const result: { id: string; success: boolean; errors: unknown[] } = res.data
return result.id
}
使い方は下のイメージ。
const changeInputFile = async (_: React.ChangeEvent<HTMLInputElement>) => {
const datas = await Promise.all(
Array.from(_.target.files!).map(
_ =>
new Promise((resolve: ({ file, binary_data }: { file: File; binary_data: ArrayBuffer }) => void) => {
const r = new FileReader()
r.onload = () => resolve({ file: _, binary_data: r.result as ArrayBuffer })
r.readAsArrayBuffer(_)
}),
),
)
for (const d of datas) {
await uploadFile(d.file.name, 'test', 'some_salesforce_id', d.file.type, d.binary_data)
}
}
理論上は 2GB だけど Chrome だと 100MB 少し超えたあたりでクラッシュ。
その他ブラウザ、マシンスペックでもアップロードできるサイズは変わってきそう。