1
0

More than 1 year has passed since last update.

net/http で学ぶ interface

Last updated at Posted at 2022-11-24

自己紹介

普段はインターン先では Python を使って ML を用いた ソフトウェアの開発をしていますが、エンジニアとして成長するために静的型付け言語を学ぼうと思いました。その第一歩に選ばれた Go を学んでいくにあたり、interface 周りでつまづき、色々調べる中で自分なりに腑に落ちたので共有させていただきます。

TL; DR

Golang における net/http モジュールのルーティングついての紹介です。
ルーティングするとき誤魔化していたところをしっかり公式や記事を読んだことで理解につながったので共有させていただきます。
結論から言うと http で interface を実装してくれているので何も考えずに

http.HandleFunc("/endpoint", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World")
})

といったふうに書くことができます。

この記事を読んでわかることは、

  • Go ってすごい
  • interface の使われ方

などです。気楽に読んでいただければ嬉しいです 🙇‍♂️


⭐️ interface とは

Golang におけるインターフェースとは、簡単に言うとメソッドの塊のことです。
また、とある構造体などに対してインターフェースに定義されているメソッド全てを定義していることをとインターフェースを実装していると言います。
この時点で Python しか書いてこなかった僕の頭は ??? になりました。
実際のコードを見てみましょう。

type Animal interface {
	Eat()
    Sleep()
}

type Dog struct {}

func (d *Dog) Eat() {
	fmt.Println("mogumogu") // mogumogu と表示されます
}

func (d *Dog) Sleep() {
	fmt.Println("suyasuya")
}

序盤 4 行で interface Animal を定義しています。
Dog と言う構造体を定義し、Dog に対して Animal で宣言されているメソッドたちを定義します。この時点で、Dog は Animal interface を実装したことになりました。
例えば、 Cat と言う名前の構造体を定義し、EatSleep を定義することで Cat も Animal interface を自動的に実装したことになります。


⭐️ http における紛らわしいやつらの違い

http における紛らわしい奴等リストは

  • Handler
  • HandlerFunc
  • Handle
  • HandleFunc

です。
実際にそれぞれ net/http/server.go に実装されているので見ていきましょう。

Handler

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Handler は interface です。ただ 1 つ ServeHTTP と言う関数さえ実装してくれれば OK な interface です。

HandlerFunc

type HandlerFunc func(ResponseWriter, *Request)

HandlerFunc は関数の型です。ResponseWriter と *Request を引数に取るような関数の型ですね。

Handle

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

Handle は実際の関数です。関数内部で何をやっているかについては長いので割愛させていただきます。簡単に言うと pattern エンドポイントに対して handler を紐付けています。(関数内部の Handle は外の Handle とは関係なく、DefaultServeMux のメソッドであることに注意してください。)

HandlerFunc

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

HandlerFunc も実際の関数ですね。TL; DR ではこの関数を呼び出していました。

こちらは短いので内部の関数をみてみましょう。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

最終行に全てが詰まっています。

mux.Handle(pattern, HandlerFunc(handler))

つまり、

  • HandlerFunc メソッドは Handle メソッドを呼び出していること
  • handler (コールバック関数) を HandlerFunc でラップしていること

が肝になっているわけです。
これらによって、僕たちは何も考えずに TL; DR で書いたようなコードを書けるわけです。


⭐️ 実際の関数の流れ

これまでに勉強したことを実際の流れに即して見ていきましょう。きっと感動してしまうことでしょう。
まず、TL; DR で書いたように僕たちは

http.HandleFunc("/endpoint", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello")
})

エンドポイントを実装する時はこう書くだけでよかったのです。
HandleFuncが呼ばれているので見てみましょう。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
  • pattern = “/endpoint”
  • handler = func(w, r)

となっていて良さそうですね。
次に呼ばれているのは DefaultServeMux.HandleFunc です。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}
  • pattern = “/endpoint”
  • handler = func(w, r)

ここも良さそうですね。
次に呼ばれるのが mux.Handle です。
ここが大事なポイントです。(*1)

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    // ルーティング
}

mux.Handle に関しては先ほど割愛させてもらったので引数だけ見ます。(関数としては実際にルーティングしている部分を担っています。)

  • pattern = “/endpoint”
  • handler = Handler(func(w, r))

1 つ前の メソッドで func(w, r) を HandlerFunc 型にキャストしていましたが、今回のメソッドの第 2 引数 Handler ではないです。。。なぜでしょう。。。
見返してみると、Handler は interface だったので、HandlerFunc(f(w, r)) がどこかで Handler interface を実装してないといけません。つまり、HandlerFuncServeHTTP メソッドを定義していないといけないわけです。
僕たちはいちいち コールバック関数に ServeHTTP メソッドを実装していないのできっとパッケージ側で実装してくれているのだろうと踏んで見てみると

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

2083 行目からありました!
つまり HandlerFunc は晴れて Handler interface に昇格して(*1)のメソッドが通り、無事ルーティングできるわけです。


⭐️ 実装できるのは構造体だけじゃない

「⭐️ interface とは」で見たような超具体的な構造体に対して interface を実装することが多くあると思うのですが、Golang の標準モジュールでやっていたことは、
HandlerFunc と言う 関数の型 に対して interface を実装する
と言うより抽象化されたことでした。
これのおかげで、関数をキャストして、 HTTP サーバーのハンドラーとしてふるまうようにできたのです。(すごい 😭)
またすごいポイントとしては ServeHTTP はその処理内容をハンドラーに完全に委譲していたことです。これのおかげでハンドラーに柔軟性が出てよしななエンドポイントが作成できます。
interface のおかげで全く裏のことを知らずにエンドポイント作成できるのは非常にいいことですね

最後に

ここまで読んでくださった方、ありがとうございました。
何も考えずに実装できるのはいいことですが、やっぱ裏側がちょっと気になって、調べて腑に落ちると感動しますね。
何か間違えたことがあれば教えていただけると幸いです。

参考

https://qiita.com/_kyamasan/items/940e575bc77e6b1f8ddb
https://pkg.go.dev/net/http

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0