form submission時の改行コード変換を考慮する
フロントエンドでの文字数バリデーションは通るのに、バックエンドでの文字数バリデーションは通らないという問題に遭遇しました。
原因はブラウザによって行われる改行コードの正規化でした。
問題が起きる仕組み
form submission時に、改行が \r\n
に正規化されてバックエンドへ送信されます[1]。
一方、textareaの値を取得すると、改行は \n
に正規化されます[2]。
その結果、フロントエンドでは95文字として見えていたのに、改行が10個あるせいでバックエンドでは105文字として見えるという事象が発生します。
解決策
案1. バックエンドで改行を正規化
状況ごとに解決方法は違うでしょうが、シンプルな解決策の1つは、バックエンドで改行を \n
に正規化することです。
フロントエンドでのバリデーション時と同じ状況を作り出します。
案2. form submissionを使わない
fetchを使う案も考えられます。FormDataはform submissionになってしまうので使わない。
application/json
だけでバックエンドとやりとりするようなアプリケーションも多いでしょうから、これで解決するケースも多いでしょう。
ファイルのアップロード機能などで multipart/form-data
を使う場合は、そこに限定して異なる対処を行えば良いでしょう。
実験
以下のコードで、form submission時の改行の正規化の振る舞いを確認します。
textareaに改行付きで文章を入力してsubmitすると、ブラウザのコンソールには \n
で正規化された文字列が表示され、nodeの標準出力には \r\n
で正規化された文字列が表示されます。
一方、案2の方法でリクエストを送ると、nodeの標準出力には改行コードが \n
の文字列が表示されます。
import http from 'http';
import fs from 'fs';
async function parseBody(req) {
let body = '';
for await (const chunk of req) {
body += chunk;
}
return body;
}
const server = http.createServer(async (req, res) => {
if (req.method === 'POST') {
const body = await parseBody(req);
console.log(JSON.stringify(decodeURIComponent(body)));
}
const html = fs.readFileSync('index.html');
res.setHeader('Content-Type', 'text/html');
res.end(html);
});
server.listen(8000);
<!DOCTYPE html>
<html>
<body>
<form action="/" method="post" id="form">
<textarea name="content" id="textarea"></textarea>
<button type="submit">submit</button>
</form>
<button id="fetch">use fetch</button>
<script>
document.getElementById("textarea").value = "a\rb\rc";
document.getElementById("form").addEventListener("submit", (e) => {
e.preventDefault();
const textarea = document.getElementById("textarea");
console.log(JSON.stringify(textarea.value));
e.target.submit();
});
document.getElementById("fetch").addEventListener("click", () => {
const textarea = document.getElementById("textarea");
console.log(JSON.stringify(textarea.value));
fetch("/", {
method: "POST",
body: textarea.value,
});
});
</script>
</body>
</html>
補足
以前はブラウザごとに挙動が違った
WHATWGのブログ記事にform submission時の改行コードの取り扱いに関するものがあります。
ブラウザごとに改行の正規化タイミングが異なっていたため、結果も違うものになってしまっていたそうです。
仕様を変更して振る舞いの統一を図った結果、現在はブラウザごとの差異は無くなっているようです。
文字数はlengthで数えないほうが良い
JavaScriptでは文字列の長さをlengthでとろうとすると、誤った値が取得される場合があります。Sosuke Suzukiさんの記事が参考になります。
疑問点
form submissionするケースで、改行コードを正規化できない、またはできるけどかなり面倒な場合はどうするか。悩み中。
例えば、Spring Bootで文字列長のバリデーション付きのハンドラを書くと以下のようになります。 \r\n
はそのまま2文字としてカウントされてしまうのですが、事前に \n
に正規化しようとするとかなり面倒です。
public void post(@Size(max = 100) String content) {
Discussion