GoでFormFileがあるHandlerのテストを書く

はじめに

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から画像などのファイルを読取るやつである。

詳しく:

note.com

www.irohabook.com

さて、このテストを書く。 実際にhttptestでサーバを起動させ、POSTリクエストを送ることでテストする。

func server() *http.ServeMux {
    mux := http.NewServeMux()

    mux.HandleFunc("/", handler.HogeHandler)

    return mux
}

テストは、画像をos.Openで読み取り、multipartでまとめて送る。 以下のサイトを参考にした。

stackoverflow.com

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で発生してしまう。

stackoverflow.com

自分はこれで数時間格闘したのでマジで気をつけてほしい。 実際、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使って最後に実行するものがほとんどなんだもん。

Webアプリのログイン情報保存について

Webアプリケーションでログイン処理を作る際、ログイン情報の保存は必要となる。しかし、どのようにして保存するのか方法が沢山あるため個人的な備忘録としてまとめたい。

この記事はid:cateiruの薄い知識の上に成り立っているため間違えている箇所があれば指摘ください。

認証方式

GIGAZINEの記事がわかりやすいのでそちらを参照してください。

https://gigazine.net/news/20201227-web-authentication-methods-compared/

多分、今からwebサイトを構築する場合はopen id connectなどを使用したSSOでログイン実装すると思います(諸説あり)。

で、ログイン情報保存方法は?

  1. 多分一番多いのはCookieです。サーバー側で取得できるのでログイン情報を保存する専用にあると言っても過言ではないのでしょうか?

  2. 次に使用されるのはlocal storageです。ページごとにデータを永続的に保存できるストレージであり、色々な使い方ができます。

  3. また、session storageというものもあります。これはlocal storageのセッション間のみバージョンです。タブごとに保存されそのタブを閉じればデータは削除されます。

  4. 技術的には可能だが別にそれを使う必要無くね?というものもあります。indexed dbです。容量無制限でYouTube premiumの動画オフライン保存などで使用されています。KVSのような扱いができ結構便利です。

Cookie

Cookieを使用する場合どのようにログイン情報を作成するかについても考えます。

多分多くのWebサイトで使用されているのはセッションベースの認証です。そのセッションごとにユニークなTokenを作成して認証します。 そのTokenを作成するには

  1. ログインページでログインしてもらう
  2. ユーザーを識別できる期限付きCookieを予め用意しておく
  3. IPアドレスやUser-Agent

の2種類があると思います。1は説明通りです。 2は、そのユーザーを識別できるTokenが漏洩した際の対策でセッションのTokenを更新するごとに一緒に更新したほうがいいです。

3はやってはいけないと思っています。フリーWi-Fiなどでログインした場合では他の人もアクセスできてしまう脆弱性があるためです。

local/session storage

APIのPOST時にヘッダをつけて送信する必要があるためそれ用の処理を書かなければならない手間はありますが、

  1. Cookieがブロックされてもしようできる
  2. curlなどでも簡単に叩ける

などの利点も有ります。ですが、やはりCookieと比べるとフロント側の処理がスパゲティになるイメージがあります。

まとめ

とりあえずCookie使えばいいと思います。

あけおめ初投稿

明けましておめでとうございます。id:cateiru です。

ブログに記事が0件なのは流石に悲しいのでとりあえず去年のまとめと今年の抱負を書き散らしてみようかと思います。

去年はどうだったか

2021年は個人的にあっという間に過ぎていき、内容も濃かったような気がします。

1月

成人式が消えました。中学時代は黒歴史を量産していましたが、同級生と会いたかったなってのは少し思いました。

2月

春休みです。カレンダーを見ると、私は週3フルタイムでインターンをしていたみたいです。外出自粛していたのか単に遊ぶ友達がいなかったのか、、、多分後者です。

3月

エヴァ見に行ったり実家に帰ったりしました。あとは、インターンぐらいです。

4月

ここからカレンダーの予定がめちゃくちゃ増えます。前期の授業が始まりました。今回から他学部履修をしておりいつもより忙しかった記憶があります。

5月

おそらくここらへんで別の分野で仕事をしたいと思うようになりました。この頃のインターンはAI開発関係で詳しくは話せないですが、すごく新しい論文読んで実装したりめちゃくちゃ長いPythonコードと睨めっこしてました。

6月

6月いっぱいでインターンをやめることを決めます。並行して次のインターン先を応募していました。

7月

応募した長期インターンが書類落ちしましたガッデム

2社目のインターン(短期)を申し込んだけど面接で求める技術スタックが合わなかったためまた落ちました。しかし、2社目の面接してくれた方から色々アドバイスいただき夏休みに何かサービス作ろうと決意しました。

あとオリンピックありましたね。そのせいで期末試験の日程がズレたりして大変でした。

8月

めちゃくちゃ開発しました。昼夜逆転もしました。Hello Slideというものを作ったのですが時間がたくさんあったため新しい技術や自分の知らない技術を詰め込みました。

結果的に、フロントはNext.jsでバックエンドにGoでDaprでマイクロサービス化してk8s上にデプロイさせました。

9月

長年使っていたiPhone8が引退し、iPhone13を迎えました。

10月

すごく良いアルバイト先を見つけたので応募しました。前の落ちたインターンの経験元に志望動機は素直に自分の思ったことを書き、働けることになりました。

11月

前のインターンも楽しかったけどこのバイトも楽しい。さらに、新しい技術を語る会見たいのがあり色々な知見が溜まっていって楽しい。

12月

バイトしたり授業したり結構充実した。個人開発でSSOサービスを実装したくなり少しずつ書き始めた。

あと、エンカして遊んだ。楽しかった。

今年はどうしたいか

今年は研究もありさらに大学生最後の年なので悔いのないようにしたいなと思う。

あとは、頑張りすぎてへとへとにならないよう適度に休憩していきたい。恋人もほしいな。