問題の発生方法
タイトルの通り文字コードをasciiで指定したリクエストを受けた際に発生したので、調査したところ以下のリクエストヘッダーでPOSTリクエストしたところ再現することができました
Content-Type: application/json charset=ascii
例外発生箇所
エラー発生時のスタックトレースでbody-parser
であることは理解していたのですが、実際にどのような処理が行われているのか確認したところ、以下の通りでした。
// assert charset per RFC 7159 sec 8.1
var charset = getCharset(req) || 'utf-8'
if (charset.substr(0, 4) !== 'utf-') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
ちなみに、ソースコードにはRFC7159と記載されていますが確認したところ既に廃止されて、RFC8259に置き換わっていました。
ざっくり内容を見たけど、JSONは基本的にutf-8を使用する必要があるため、ライブラリでエラーにしてそうです。(基本的にと書いたのは以前は指定なかった感じなので)
対策方法
Nest.jsの例外フィルターにhttp-errors
パッケージのHttpError
クラスを追加してハンドリングするようにします。
import { Catch, ExceptionFilter, ArgumentsHost, HttpException } from '@nestjs/common'
import { HttpError } from 'http-errors' // http-errorsの例外クラスをインポート
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const ctx = host.switchToHttp()
const response = ctx.getResponse()
const request = ctx.getRequest()
let status: number
let message: string
if (exception instanceof HttpException) {
// Nest.jsのHttpExceptionをハンドリング
status = exception.getStatus()
message = exception.getResponse() as string | object
} else if (exception instanceof HttpError) {
// http-errorsのHttpErrorをハンドリング
status = exception.statusCode || 500
message = exception.message
} else {
// その他の例外をハンドリング
status = 500
message = 'Internal Server Error'
}
// (ここから先は省略)
// 監視用のログ出力、エラーレスポンス返却など
}
}
限定的に対策するなら、UnsupportedMediaType
クラスでハンドリングするのもありです
import { HttpError } from 'http-errors'
// ↓に書き換え
import { UnsupportedMediaType } from "http-errors"
最後に
この問題はNest.jsというよりjsonデータを受け付ける際にbody-parser
からの例外をハンドリングできていない場合に発生するので、Expressを利用している場合も発生すると考えられます。
本来、こんな通信をしてくることは無いと思うので対応は必須ではありませんが、ログ監視を考えると正しくハンドリングしておいた方が良いので、関わっているプロジェクトでも心当たりあれば再現確認&対策検討をしてみてください。
参考サイト