4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

multipart/form-dataで、日本語ファイル名が文字化けする(API Gateway, Lambda構成)

Posted at

はじめに

API Gateway, AWS Lambda構成で、multipart/form-dataを使ってファイル送信をした際に、日本語ファイルが文字化けする事象に遭遇しました。その対応方法についてまとめます。


対象となる読者

multipart/form-dataを使って日本語ファイルを送信したが、ファイルが文字化けして困っている方


Lambda実行環境


結論

最初に結論だけ述べると、フロント側でファイル名をエンコードして、データ送信することで文字化けを回避することが出来ました。
バックエンドでの処理を模索しましたが、解決出来なかったため、もし解決方法ご存知の方がいらっしゃれば、コメントいただけると嬉しいです。


詳細

multipart/form-dataはデータ送信時のデフォルトcharsetはISO-8859-1になるようです。multipart/form-data, what is the default charset for fields? の回答が参考になりました。ISO-8859-1は日本語がないため、この段階で文字化けが発生します。

そのためAPI gatewayにデータを送信する前に日本語ファイル名をencodeURIを使い、ISO-8859-1でエンコードされる前にUTF-8エンコードし、サーバー側で受け取ったデータをdecodeURIでデコードすることで文字化けなくファイル名を取得できました。

今回はクライアントサイドはJavascriptのFormDataFetch APIを使って処理しました。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Form Test</title>
  </head>
  <body>
    <form>
      <div>
        <label for="file_name">Choose file to upload</label>
        <input type="file" id="file_name" name="file_name" multiple />
      </div>
      <button type="submit">Submit</button>
    </form>
    <script>
      const form = document.querySelector('form')
      form.addEventListener('submit', (e) => {
        e.preventDefault();
    
        const formData = new FormData(form)
        const fileInput = document.getElementById("file_name")
        const [file] = fileInput.files; // File APIの使用
        const fileName = file.name
        const fileExtention = fileName.substring(fileName.lastIndexOf(".") + 1);
        const blob = file.slice(0, file.size, file.type);
        const encodedFileName = encodeURI(file.name.substring(0, fileName.lastIndexOf("."))); //エンコードしたファイル名の取得
        const renamedFile = new File([blob], encodedFileName + "." + fileExtention, {type: file.type});
        formData.set('file_name', renamedFile)
    
        fetch("https://sample.com", {
          method: "POST",
          mode: 'cors',
          body: formData
        })
        .then(res => res.json())
      })
    </script>
  </body>
</html>

ファイル名の取得にはFile APIを使用しました。

Fetch APIを使ってデータを送信する場合、小さなハマりどころがありました。

multiple/form-dataはMIME-TYPEの指定と、そのパラメータとしてboundaryの指定が必須です。Hypertext Transfer Protocol -- HTTP/1.1より。
そのためFetch APIでのリクエストは以下のように設定しました。

formdata.js
// NG
const formData = new FormData(form)
fetch("https://sample.com", {
  method: "POST",
  headers: {
    "Content-Type": "multipart/form-data"
  },
  body: formData
})

上記のように指定した場合、サーバー側で Content-Type: multipart/form-data が送られませんでした。 fetch - Missing boundary in multipart/form-data POSTのstack overflowに事象の詳細がまとめられています。
これについては、FormDataのMDNにも以下のように警告されていましたのでご注意ください。

警告: FormData を使用して、XMLHttpRequest または Fetch_API を使用して、 multipart/form-data の Content-Type で POST リクエストを送信する場合 (Files や Blob をサーバーにアップロードする場合など)、リクエストの Content-Type ヘッダーを明示的に設定しないでください。そうすると、ブラウザーがリクエスト本文のフォームフィールドの区切りに使用する境界の表現で Content-Type ヘッダーを設定することができなくなります。

formdata.js
// OK
const formData = new FormData(form)
fetch("https://sample.com", {
  method: "POST",
  body: formData
})

Node.jsを使って、Lambda側でのデータの受け取りは以下のように処理をしました。
Lambdaでnode_modulesを使う場合は、レイヤーにzipファイルを追加する必要があります。レイヤーにnode_modulesを追加する方法はLambdaレイヤーにnode_modulesを登録してみたを参考にしました。

index.js
const multipart = require('parse-multipart-data')

module.exports.handler = async(event) => {
  const encodeBody = event['body-json']; // eventからエンコードされたmultipart/form-dataの中身を取得
  const header = event.params.header
  const decodeBody = Buffer.from(encodeBody.toString(), "base64")
  const boundary = multipart.getBoundary(header['Content-Type'])
  const parsedBody = multipart.parse(decodeBody, boundary)
    
  let fileName, type, content
    
  for (const formdata of parsedBody) {
    if (formdata["name"] == "file_name") {
      fileName = decodeURI(formdata["filename"]);
      type = formdata["type"]
      content = Buffer.from(formdata["data"], "base64").toString("base64")
    }
  }
}
4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?