🧛

XMLHttpRequestではmultipart/form-dataを明示しない方が良い

2021/10/03に公開

自身でmultipart/form-dataの形式にエンコードできる場合は問題ありませんが、FormDataを使用する場合はXMLHttpRequest.setRequestHeaderContentType: multipart/form-dataを指定するべきではありません。本来Content Typeに付与されるboundaryがsetRequestHeaderで指定された文字列に上書きされるためサーバー側でパース出来なくなってしまいます。

request.open("POST", url)
request.setRequestHeader("Content-Type", "multipart/form-data")

setRequestHeaderで指定した場合

Content-Typeを指定しない場合

Sample code

React.js
const Form: FC = ({ url }) => {
  const form = useRef()
  ...
  const send = useCallback(() => {
    const request = new XMLHttpRequest()
    const data = new FormData(form.current)
    request.open("POST", url)
    request.send(data)
  }, [url])
  ...
  return (
    <form ref={form}>
    ...
    </form>
  )
}

余談

以前公開した記事でXMLHttpRequestを使用する場合CloudStorage側でcorsの対応をするだけで利用することができます。
file.generateSignedPostPolicyV4で使用するbucketに対して以下のjsonを用意します。

cors_policy.json
[
  {
    "origin": ["https://example.com"],
    "responseHeader": ["Content-Type"],
    "method": ["POST"],
    "maxAgeSeconds": 3600
  }
]
gsutil cors set bucket_cors_policy.json gs://[適用したい bucket id]

上記の設定をすることでform tagのみでは出来なかったアップロードの進捗等のイベントをXMLHttpRequestを通じて監視することができました。

React.js
const Form: FC = ({ url }) => {
  const form = useRef()
  ...
  const send = useCallback(() => {
    const request = new XMLHttpRequest()
    const data = new FormData(form.current)
    request.open("POST", url)
    request.addEventListener("progress", (e) => {
      console.log(e.loaded ? (e.loaded / e.total) * 100 : 0, "%")
    })
    request.send(data)
  }, [url])
  ...
  return (
    <form ref={form}>
    ...
    </form>
  )
}

Discussion