🛋️

goで http.Requestのbodyが途中でエラーを吐いてもサーバーが見落とすパターン

2023/10/25に公開

遭遇したこと

クライアントサイドで、多少ややこしいbodyを作る場面があり、「このbody(Reader)がもし途中でエラーを吐いたら、サーバーはちゃんとそれを検出できるのか?」とふと気になって検証をしました。
すると、かなり盲点だったことを見つけました。
サーバーサイドの実装によってはerrorに気づかないことがありえます。

コード

クライアントサイドがこういうコードだとします


func main()
	res, err := http.Post("http://localhost:8080", "text/plain", &badReader{})
	fmt.Printf("%+v\n", res.StatusCode)
	fmt.Printf("%+v\n", err)
}

// reader returns error
type badReader struct {
}

// Read
func (s *badReader) Read(p []byte) (n int, err error) {
	copy(p, []byte(`{"a":1}`))
	return 9, fmt.Errorf("badReaderError")
}

問題のあるBodyをシミュレーションしています。Readしていると途中でエラーが出てしまいます。
期待としては、このBodyを処理するサーバーサイドは、どこかでエラーを捕捉してほしいものです。
そこで、よくあるサーバーサイドを書いてみました。

	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		m := map[string]interface{}{}
		err := json.NewDecoder(r.Body).Decode(&m)
		fmt.Printf("%+v\n", m)
		fmt.Printf("%+v\n", err)
		fmt.Fprintf(w, "Hello")
	})

期待としては、 err := json.NewDecoder(r.Body).Decode(&m)がエラーを返してくれると、漠然と思っていました。
ところがprint結果はこうなります。

map[a:1]
<nil>

エラーが返らないのですね。

なにが起きているか

「json.Decoder」を使ってリクエストをパースすると問題が起きるという理解です。もしクライアントサイドでリーダーがエラーを返した場合であっても、サーバー側はこれを正しく読み取ってしまいます。なぜなら、「json.Decoder」は入力ストリームをパースし、その結果として一応完結したJSONを返すからです。

サーバー側が確実にエラーを把握するには、io.ReadAll(r.Body)を使うなどして、確実にボディ全体を読み切る必要があります。
io.ReadAllすると unexpected EOFがちゃんと返ります。

json.DecoderがBodyを読み切らないというのは結構盲点でした。
Bodyは全部読み切るのが基本である気もします。ちょっと既存の実装を見直す必要もあるかも。。。

Discussion