net/httpパッケージのルーター機能を拡張する(httprouterの利用)

サードパーティ製ライブラリのルーター機能を使う
===

`net/http`パッケージでは基本的なルーティング機能のみの対応となるため、
サードパーティ製のルーター機能を実装したライブラリを使用するのがよい。

httprouterの利用
---

サードパーティ製ルーターの中でも最速と言われる[^1]`httprouter`を利用する。

[^1]: [Go HTTP request router benchmark](https://github.com/julienschmidt/go-http-routing-benchmark), [Hacker News](https://news.ycombinator.com/item?id=7872113)

`httprouter`を使うには、まず下記の一行を書く。

```go
	router := httprouter.New()
```

上記では`httprouter.Router`のコンストラクタを呼んでいる。
`httprouter.Router`構造体は、`http.Handler`インターフェースを満たすように
実装されている。

以下では、これを利用し、`net/http`パッケージのみで書かれたコードを
`httprouter`を使うように書き換える方法を見ていく。

### http.Handler

はじめに、`http.HandleFunc`を使用した下記のコードを
`http.Handler`を使うように書き換える。

```go
func homeHandle(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome to the home page!")
}

func main() {
	http.HandleFunc("/", homeHandle)
	http.ListenAndServe(":8080", nil)
}
```

↓

```go
func homeHandle() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Welcome to the home page!")
	})
}

func main() {
	http.Handle("/", homeHandle())
	http.ListenAndServe(":8080", nil)
}
```

上記では、まずmain関数内の`http.HandleFunc`を`http.Handle`に変更している。
`http.Handle`では下記の通り、第二引数に`http.Handler`を指定するよう定義されて
いるためである。

```go
func Handle(pattern string, handler Handler) { ... }
```

次に、homeHandle関数を`http.Handler`を返すように変更する。
返す値には`http.HandlerFunc`を使用する。

```go
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}
```

`http.HandlerFunc`とは、上記の通り`http.Handler`インターフェースを満たす
型として定義されている。

上のコードでは、`func(w http.ResponseWriter, r *http.Request) { ... }`を
`http.HandlerFunc`型にキャストすることで、homeHandle関数の戻り値としている。

これで元のコードを`http.Handler`を使うように変更できた。

### httprouter.Handle

今度は`httprouter.Router`を使用するように書き換えていく。

```go
	router := httprouter.New()
    ...
    http.ListenAndServe(":8080", router)
```

まず、`httprouter.Router`が`http.Handler`を満たすことを利用し、
上記のように`http.ListenAndServe`に渡すようにする。

次に、routerメソッドを使用していくのだが、ここの定義が標準パッケージとは
異なるため変更が必要となる。

下記が主要な`Handle`メソッドの定義となる。

```go
func (r *Router) Handle(method, path string, handle Handle) {
    ...
}
```

標準パッケージとの違いは、第一引数にmethodが挿入されること、第三引数の
`Handle`がパッケージ独自のものであることである。

まず、第一引数には7種類のリクエストメソッドを指定するのだが、
`httprouter.Router`には、この`Handle`メソッドを利用した7種類のメソッドが
実装されているため、そちらを使うことになる。

例えば、GETメソッドだと下記の通り定義されている。

```go
func (r *Router) GET(path string, handle Handle) {
	r.Handle("GET", path, handle)
}
```

元のコードは以下のように書くことができる。

```go
	router := httprouter.New()
	router.GET("/", ＜httprouter.Handle型＞)
    http.ListenAndServe(":8080", router)
```

次に、`router.GET`メソッドの第二引数に指定する`httprouter.Handle`を見ていく。
`httprouter.Handle`の定義は下記となる。

```go
type Handle func(http.ResponseWriter, *http.Request, Params)
```

上記をみると、`http.HandlerFunc`に第三引数を追加したような型にも見える。
ただし、`http.Handler`インターフェースは満たしていないことに注意する。

元のコードのhomeHandle関数の戻り値は`http.Handler`であるため、何らかの方法で
`httprouter.Handle`に書き換える必要がある。

二通りの書き換え方法がある[^2]ため、以下で見ていく。

[^2]: [How to pass the variable to 'handler'?](https://github.com/julienschmidt/httprouter/issues/198)

#### homeHandle関数自体を`httprouter.Handle`を返すように変更する

ひとつは、homeHandle関数自体を`httprouter.Handle`を返すように変更する
方法である。

```go
func homeHandle() httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		fmt.Fprintf(w, "Welcome to the home page!")
	}
}

func main() {
	router := httprouter.New()
	router.GET("/", homeHandle())
	http.ListenAndServe(":8080", router)
}
```

こちらは記述量が少ないこと、また`httprouter.Params`がそのまま使用できるのが
利点となる。

反対に欠点としては、ひとつはhomeHandleのような関数が既に複数あった場合に、
その関数ごとに変更を入れる必要があり手間となることがあげられる。

もうひとつは、`httprouter.Handle`というパッケージ独自の型を各Handleごとに
利用するため、例えば他のフレームワークへの移行を考えた場合など、
パッケージ相互の互換性が低くなってしまうことがある。

#### `httprouter.Handle`を返却する`wrapHandler`関数を作成する

次に、もうひとつのパターンを見ていく。

`http.Handler`をラップして`httprouter.Handle`を返却する
`wrapHandler`のような関数を作成する方法である。

```go
func wrapHandler(h http.Handler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		h.ServeHTTP(w, r)
	}
}

func homeHandle() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Welcome to the home page!")
	})
}

func main() {
	router := httprouter.New()
	router.GET("/", wrapHandler(homeHandle()))
	http.ListenAndServe(":8080", router)
}
```

`wrapHandler`という別の関数を追加したことで一見すると記述量は多く見える。

ただし、homeHandleのような関数が既に複数あった場合には、むしろ記述量を減らせる
こととなる。各Handle関数ごとに変更を入れる必要はなく、`router.GET`などに
渡す際に`wrapHandler`でキャストしてやればよいのである。

また、`httprouter.Params`に格納された値が、そのままでは使用できないという点にも
注意する必要がある。

`httprouter.Params`の値を使用する場合は`context`パッケージを使うとよい
とのこと。[^2]

使用例としては下記のようになる。

```go
func wrapHandler(h http.Handler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// cf. https://github.com/julienschmidt/httprouter/issues/198
		ctx := r.Context()
		ctx = context.WithValue(ctx, "params", ps)
		r = r.WithContext(ctx)
		h.ServeHTTP(w, r)
	}
}

func homeHandle() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Welcome to the home page!")
	})
}

func helloHandle() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	    ps, ok := r.Context().Value("params").(httprouter.Params)
	    if !ok {
	    	logger.Errorf("ps is not type httprouter.Params")
	    }
        fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
    })
}

func main() {
	router := httprouter.New()
    router.GET("/", wrapHandler(homeHandle()))
	router.GET("/hello/:name", wrapHandler(http.HandlerFunc(helloHandle)))
	http.ListenAndServe(":8080", router)
}
```

上記のままでは`context.WithValue`の部分でgolintエラー[^4]が出力される。
ここの解消方法については調べた後にまとめたいと思う。

[^4]: エラー内容は`should not use basic type string as key in context.WithValue`というもの。

以上のふた通りとなるが、どちらを選ぶべきかについては以下のように考える。

素早くシンプルに実装して試したい、といった場合は前者が適している。
ただ、コードの保守性を考えると、基本的には後者を選択した方が良さそうである。

例えば、ミドルウェアを追加するライブラリを使用しようとした際に、
標準パッケージに沿った`http.Handler`を介しての変更が比較的容易になるといった
メリットを生かすことができるためである。

## まとめ

上記でhomeHandle関数を`http.Handler`を使うように書き換えたパターンを見てきた。

しかし、この関数自体の変更せずに書き換える方法があることに、この記事を書いている
途中[^3]で分かったので最後にメモとして残しておく。

[^3]: 正確には、`http.HandlerFunc`について調べていた時。

下記では、`func(http.ResponseWriter, *http.Request)`型を`http.Handler`を
満たすよう`http.HandlerFunc`でキャストしたものを、`wrapHandler`に渡すことで
`httprouter.Handle`型に対応させている。

```go
func wrapHandler(h http.Handler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		h.ServeHTTP(w, r)
	}
}

func homeHandle(w http.ResponseWriter, r *http.Request) {	
	fmt.Fprintf(w, "Welcome to the home page!")
}

func helloHandle(w http.ResponseWriter, r *http.Request) {
	ps, ok := r.Context().Value("params").(httprouter.Params)
	if !ok {
		logger.Errorf("ps is not type httprouter.Params")
	}
	fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
	router := httprouter.New()
    router.GET("/", wrapHandler(http.HandlerFunc(homeHandle)))
	router.GET("/hello/:name", wrapHandler(http.HandlerFunc(helloHandle)))
	http.ListenAndServe(":8080", router)
}
```

#### 参考記事

- [Guide to 3rd Party Routers in Go](https://www.nicolasmerouze.com/guide-routers-golang/)