事象
- ファイル名に日本語を含む(概要.md)ファイルを"Content-Type": "multipart/form-data; charset=UTF-8"で HTTP 送信すると、サーバー側でリクエストを受け取ると、ファイル名 が文字化けしてしまった
- 正しいファイル名:概要.md
- 文字化けされたファイル名:æ¦è¦.md
- しかし、ファイルの内容(markdown の中身)自体は文字化けしていなかった
なぜ ファイル名 は文字化けして、ファイルの内容は文字化けしなかったか?
- ファイル名 は、HTTP ヘッダーに含まれて、
- ファイル の内容は、HTTP ボディに含まれる
という違いがあります。
HTTP ヘッダー | HTTP ボディ | |
---|---|---|
扱えるもの | - ASCII 文字のみ | - 任意のデータ - エンコード方式 |
HTTP ヘッダー
扱えるデータは ASCII 文字のみです。
日本語は非 ASCII 文字なので、これが原因で文字化けが発生しました。
HTTP ボディと違ってエンコードはされません。
HTTP ボディ
HTTP ヘッダーと違って、HTTP ボディでは、テキストデータとエンコード方式を扱えます。※
データが HTTP リクエストのボディに含められる前に、エンコードされて、サーバーがリクエストを受信した後に、デコードされます。
つまり、HTTP ボディではエンコードとデコードが自動で処理されます。
HTTP ヘッダと HTTP ボディでこのような違いがあるため、HTTP ヘッダでは文字化けが起こり、HTTP ボディでは文字化けが起こらない、ということが発生しました。
なお、デフォルトのエンコード方式は UTF-8 のようですが、header の Content-Type に明示的に charset=UTF-8 と指定することをおすすめします。
※バイナリデータ(画像、ビデオ、オーディオファイルなど)も扱えますが、ここではシンプルにするためテキストデータのみで説明しました。
問題の解決法
HTTP ヘッダでは ASCII 文字しか扱えないため、日本語などの非 ASCII 文字を ASCII 文字にエンコードする必要があります。
const encodedFilename = encodeURIComponent(file.name);
encodeURIComponent()は下の文字以外を「% + 2 桁の 16 進法」という形式に変換します。
A-Z a-z 0-9 - _ . ! ~ * ' ( )
すると、すべて ASCII 文字になります。
で、サーバー側でリクエストを受信したあとで、デコードします。
const decodedFilename = decodeURIComponent(req.file.originalname);
確認するとちゃんと日本語で表示されました。