はじめに
Goで、FormFileがあり画像ファイルを読み込むHandlerを書いたがテストでハマってしまったので備忘録として書く。
TL;DR
writer.Close()
にdeferはつけない
本文
func HogeHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(32 << 20); err != nil { w.WriteHeader(http.StatusInternalServerError) return } fileSrc, fileHeader, err := r.FormFile("upload") if err != nil { w.WriteHeader(http.StatusInternalServerError) return } ... }
このような、Handlerがあったとする。ParseMultipartForm
とかFormFile
は詳しくは説明しないがPost formから画像などのファイルを読取るやつである。
詳しく:
さて、このテストを書く。 実際にhttptestでサーバを起動させ、POSTリクエストを送ることでテストする。
func server() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/", handler.HogeHandler) return mux }
テストは、画像をos.Open
で読み取り、multipartでまとめて送る。
以下のサイトを参考にした。
const IMAGE_PATH= "logo.png" func TestHoge(t *testing.T) { server := httptest.NewServer(handler) body := new(bytes.Buffer) writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("upload", IMAGE_PATH) require.NoError(t, err) f, err := os.Open(IMAGE_PATH) defer f.Close() require.NoError(t, err) _, err = io.Copy(part, f) require.NoError(t, err) writer.Close() resp, err := http.Post(server.URL+"/", writer.FormDataContentType(), body) require.NoError(t, err) require.Equal(t, resp.StatusCode, 200) }
ここで大事なのがwriter.Close()
を間違えてdefer writer.Close()
と書かないことである。Postする前にcloseしないとEOFエラーがHandlerのParseMultipartFormで発生してしまう。
自分はこれで数時間格闘したのでマジで気をつけてほしい。 実際、write.Close()の実装を見ると、
// Close finishes the multipart message and writes the trailing // boundary end line to the output. func (w *Writer) Close() error { if w.lastpart != nil { if err := w.lastpart.close(); err != nil { return err } w.lastpart = nil } _, err := fmt.Fprintf(w.w, "\r\n--%s--\r\n", w.boundary) return err }
https://cs.opensource.google/go/go/+/master:src/mime/multipart/writer.go;l=168-179
Close finishes the multipart message and writes the trailing
Closeはマルチパートメッセージを終了し、末尾を書き込みます
確かに書いてある。が、しかし、これは命名で結構勘違いしそうなのでCloseWrite
とかEndWrite
などの別の名前にしてほしいと思った。
だってほかのパッケージのCloseはdefer使って最後に実行するものがほとんどなんだもん。