$shibayu36->blog;

クラスター株式会社のソフトウェアエンジニアです。エンジニアリングや読書などについて書いています。

Hatena-Textbook 2018 学習日記(3)

Hatena-Textbook 2018 学習日記(2) - $shibayu36->blog; の続きです。

今回は https://github.com/shibayu36/go-Intern-Diary/compare/01b1518eb0159b4a6d410188fd70ca1901a0e0ef...dca88a611ca9bf338f7fc901a11c1ebbe063ec56 あたりまで。ユーザー名からユーザーを探してくるメソッドや、ログイン用トークンの発行まで行った。

学んだことは

  • ランダム文字列を作る方法
  • database/sqlとgo-sql-driver/mysqlの関係

ランダム文字列を作る方法

go-Intern-Diaryでランダム文字列を作っているところは、テスト用のユーティリティとログイン用トークンの発行。

テスト用は

import "math/rand"
// テストでランダム文字列を使いたいときが多い
func randomString() string {
	return strconv.FormatInt(time.Now().Unix()^rand.Int63(), 16)
}

これはmath/randを用いて64bitのランダム整数が作られて、それを16文字(4bit)で文字列化するので、16文字の[0-9a-f]の文字列が出来るみたい。


続いてログイン用トークンの発行。

import "crypto/rand"
func generateToken() string {
	table := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@"
	l := len(table)
	ret := make([]byte, 128)
	src := make([]byte, 128)
	rand.Read(src)
	for i := 0; i < 128; i++ {
		ret[i] = table[int(src[i])%l]
	}
	return string(ret)
}

これは128文字のランダム文字列を作っている。crypt/randを使っているので暗号学的に安全なランダム文字列が作られる。tableが64文字、byteが8bitで256通りのランダムな値が作られるので、使う文字に偏りなくランダムな文字列が生成されそう。

database/sqlとgo-sql-driver/mysqlの関係

database/sqlはデータベースアクセスへのインターフェースで、go-sql-driver/mysqlは接続先のデータベース種別ごとのドライバとなっている。go-sql-driver/mysqlの説明を見ると、以下のようにimportするだけでdatabase/sqlに登録される。

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)

これどうなってるのかなと思ったら、database/sqlRegisterという関数があって、これをgo-sql-driver/mysql/driver.goのinit関数で呼んで、ドライバを登録しているいるようだった。なるほど。

Hatena-Textbook 2018 学習日記(2)

Hatena-Textbook 2018 学習日記(1) - $shibayu36->blog; の続きです。

今回は https://github.com/shibayu36/go-Intern-Diary/compare/ddef40c003464b410ea73cfd6787995c2484136f...01b1518eb0159b4a6d410188fd70ca1901a0e0ef あたりまで。とりあえず登録ページの作成と、登録に必要なservice層のメソッドを作りつつある。

GolangのHTTP Middlewareをテストする

ほぼ https://medium.com/@PurdonKyle/unit-testing-golang-http-middleware-c7727ca896ea のまんまだけど、自分用にメモ。

GolangのHTTP Middlewareというのは Goで始めるMiddleware - Qiita に紹介されているようなもの。PerlだとPlack Middleware、RubyだとRack Middlewareと概念的には同じだと思う。

HTTP Middlewareを作った時、もちろんMiddlewareもテストをしたい。

テストしたいHTTP Middleware

例えば以下のようなセキュリティ的に重要なヘッダを自動で入れてくれるHTTP Middlewareを考える。

func headerMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-XSS-Protection", "1; mode=block")
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		next.ServeHTTP(w, r)
	})
}

どうテストするか

何もしないhttp.HandlerFuncをHTTP Middlewareに渡して、それをhttptest.NewServerに渡してテストサーバを立ち上げることでテストできる。

こんな感じ。簡単ですね。

// getTestHandler returns a http.HandlerFunc for testing http middleware
func getTestHandler() http.HandlerFunc {
	fn := func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Test Handler")
	}
	return http.HandlerFunc(fn)
}

func TestHeaderMiddleware(t *testing.T) {
	ts := httptest.NewServer(headerMiddleware(getTestHandler()))
	defer ts.Close()

	resp, err := http.Get(ts.URL)
	assert.NoError(t, err)
	defer resp.Body.Close()

	assert.Equal(t, "1; mode=block", resp.Header.Get("X-XSS-Protection"))
	assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
	assert.Equal(t, "DENY", resp.Header.Get("X-Frame-Options"))
}

まとめ

今回はGolangのHTTP Middlewareをテストする方法を書いてみた。実際のサンプルコードはここに置いてあります。