3
3

More than 1 year has passed since last update.

【Go】http.Handlerとか色々整理

Posted at

はじめに

Goを学習していると、HandleだとかHandlerだとか色々出てきて混乱するのでその整理。

早見表

名称 分類 概略
http.ListenAndServe 関数 プログラムをWebサーバーとして起動
http.Handler インターフェース(型) ServeHTTPメソッドを持つ
http.Handle 関数 Handler型の関数をパターンに応じて実行する
http.HandleFunc 関数 func(ResponseWriter, *Request)型の関数をパターンに応じて実行する
http.HandlerFunc ServeHTTPメソッドを持つ
(f HandlerFunc) ServeHTTP 関数 fを実行する

ハンドラーって何?

ドキュメント等を読んでいると頻繁にハンドラーという言葉が出てくるが、これはHTTPリクエストに応じてHTTPレスポンスを返す関数。
GoではServeHTTP(ResponseWriter, *Request)という関数のことである。

http.ListenAndServe

http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error

まずはListenAndServe関数の説明から。
指定したaddrでリッスンしてaddrにリクエストが来たらHandler型(後述)の関数を実行する。

main.go
import (
	"io"
	"log"
	"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!\n")
}

func main() {
    // サーバー起動
    log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(hello)))
}

第一引数はIPアドレスを省略すると、localhostで起動する。

$ curl http://localhost:8080/hello
Hello, world!

ただし、この場合だと「パターンに応じて関数を振り分ける」ということができない。

main.go

import (
	"io"
	"log"
	"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!\n")
}

+func byebye(w http.ResponseWriter, req *http.Request) {
+	io.WriteString(w, "Bye bye, world!\n")
+}

func main() {
    // サーバー起動
    log.Fatal(http.ListenAndServe(":8080", http.HandlerFunc(hello)))
}
$ curl http://localhost:8080/hello
Hello, world!

$ curl http://localhost:8080/byebye
Hello, world!

そのため、handlerにはnilを指定するのが一般的である。

package main

import (
	"io"
	"log"
	"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!\n")
}

func byebye(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Bye bye, world!\n")
}

func main() {

+	http.HandleFunc("/hello", hello)
+	http.HandleFunc("/byebye", byebye)
+	log.Fatal(http.ListenAndServe(":8080", nil))
}
$ curl http://localhost:8080/hello
Hello, world!

$ curl http://localhost:8080/byebye
Bye bye, world!

(http.HandleFuncが何なのかは後述)

http.Handler

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

インターフェース。

ねこにはマンチカン、ロシアンブルー...、いぬには秋田犬、ゴールデンレトリバー...とそれぞれ色々な種類があるが、「ニャー」と鳴く方をざっくりとねこ、「ワン」と鳴く方をざっくりと犬に分類できる。

同様にServeHTTP(ResponseWriter, *Request)という関数を持っていれば、それはHandler型として扱うことができる。

まず念頭に置いておきたい点はハンドラーとして機能する、つまりリクエストを受け取ってレスポンスを返す処理をしたいならば、ServeHTTP(ResponseWriter, *Request)という関数を何らかの形で持っていなくてはならないということである。
(前述のhttp.ListenAndServe()では第二引数がHandler型の関数であったことを思い出してほしい)

main.go
package main

import (
	"io"
	"log"
	"net/http"
)

type Chao struct{}
// ChaoにServeHTTP(w http.ResponseWriter, r *http.Request)を実装して
// Handler型として扱えるようにする
func (c Chao) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Chao, world!\n")
}

func main() {
    // Chao型の変数chaoを生成
	var chao Chao

	// chaoはServeHTTP(w http.ResponseWriter, r *http.Request)を持っているので
	// Handler型としても扱える
	log.Fatal(http.ListenAndServe(":8080", chao))
}
$ curl http://localhost:8080/
Chao, world!

ここでNihaoというHandler型の関数を作成し、先ほどと同様パスに応じて関数を振り分けたいとする。
そこで登場するのが、http.Handle関数である。

http.Handle

http.Handle.go
func Handle(pattern string, handler Handler)

役割

Handler型の関数、つまりServeHTTP(ResponseWriter, *Request)というメソッドをもつ関数をパターンに応じて実行できるようにする。

main.go

package main

import (
	"io"
	"log"
	"net/http"
)

type Chao struct{}

func (c Chao) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Chao, world!\n")
}

+type Nihao struct{}

+func (n Nihao) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	io.WriteString(w, "Nihao, world!\n")
+}

func main() {

	var chao Chao
+	var nihao Nihao
+	http.Handle("/chao", chao)
+	http.Handle("/nihao", nihao)
    // サーバー起動
	log.Fatal(http.ListenAndServe(":8080", nil))
}
$ curl http://localhost:8080/chao
Chao, world!

$ curl http://localhost:8080/nihao
Nihao, world!

パターンに応じて指定したHandler型の関数を実行することができた。

ただし、http.Handleでは毎回ServeHTTP(ResponseWriter, *Request)というメソッドを作成する必要がある。
ListenAndServeの項で作成したhello関数はfunc(w http.ResponseWriter, req *http.Request)型なので、使うことはできない。

(http.Handleにhelloを指定すると、「ServeHTTPメソッドがありません」と言われる。)

そのため、もしhelloを使いたい場合はchaoやnihaoのように構造体を作成し、それにServeHTTP(ResponseWriter, *Request)を実装する必要があるが、これは面倒である。

そこで次のhttp.HandleFuncが登場する。

http.HandleFunc

http.HandleFunc.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

func(ResponseWriter, *Request)型の関数、つまり、"引数にResponseWriterとRequestのポインタを取る関数"をパターンに応じて実行できるようにする。

main.go
import (
	"io"
	"log"
	"net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Hello, world!\n")
}

func byebye(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "Bye bye, world!\n")
}

func main() {

	http.HandleFunc("/hello", hello)
	http.HandleFunc("/byebye", byebye)

    // サーバー起動
    log.Fatal(http.ListenAndServe(":8080", nil))
}

(ListenAndServeの項で作成したものと同じ)

$ curl http://localhost:8080/hello
Hello, world!

$ curl http://localhost:8080/byebye
Bye bye, world!

だが、ここで疑問が生まれる。
最初に述べた

サーバーとして機能する、つまりリクエストを受け取ってレスポンスを返す処理をしたいならば、ServeHTTP(ResponseWriter, *Request)を何らかの形で持っていなくてはならないということである。

という説明と矛盾しているのである。
だが、心配はいらない。
なぜなら、HandleFuncは内部でServeHTTP(ResponseWriter, *Request)を持つように変換してくれるからである。
より正確に言うと、内部でHandlerFunc型に変換してくれる(後述)。

・http.HandleFuncは関数
・http.HandlerFuncは型

一旦整理

・http.Handle関数に渡すのはHandler型の関数(ServeHTTP(ResponseWriter, *Request)という関数)
・http.HandleFuncに渡すのはfunc(ResponseWriter, *Request)型の関数

http.HandlerFunc

http.HandlerFunc.go
type HandlerFunc func(ResponseWriter, *Request)

http.HandleFuncの項では、helloはServeHTTPServeHTTP(ResponseWriter, *Request)を持っていないのに、http.HandleFunc("/hello", hello)とすることでハンドラーとして機能することができた。
これは内部でhelloをHandlerFunc型にキャストしているからである。
なぜHandlerFunc型であればServeHTTP(ResponseWriter, *Request)を持っていなくても良いのか。
それはHandlerFunc型がServeHTTP(ResponseWriter, *Request)メソッドを持っているからである。

hello.go
func (f HandlerFunc) ServeHTTP (w ResponseWriter, r *Request)

そのため、HandlerFunc型であればHandler型としても扱えることになる。

  1. func(ResponseWriter, *Request)型のhelloを用意
  2. helloをhttp.HandleFunc関数に渡す
  3. 内部でHandlerFunc型にキャスト
  4. HandlerFunc型はServeHTTP(ResponseWriter, *Request)メソッドを持っている
  5. helloはHandler型として"も"扱える

(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

ServeHTTP.go
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

では次に(f HandlerFunc) ServeHTTPについて見てみたい。
この実装は次のようになっている。

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

何をやっているかというと自分自身を呼び出して実行している。
helloの例に当てはめてみると次のようになる。

ServeHTTP.go
func (hello HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	hello(w, r)
}

つまり、ServeHTTP(w ResponseWriter, r *Request)でラッピングして実行することで、ハンドラーとして機能できるようになっている。

おさらい

・http.HandlerはServeHTTP(w ResponseWriter, r *Request)メソッドをもつインターフェース。つまり、ServeHTTP(w ResponseWriter, r *Request)メソッドを持っていればHandler型となる。

・http.Handleは"引数のパターンに応じて、引数のHandler型の関数を実行する"関数

・http.HandleFuncは"引数のパターンに応じて、引数のfunc(ResponseWriter, *Request)型の関数を実行する"関数

・http.HandlerFuncは"func(ResponseWriter, *Request)型"の型。ServeHTTP(w ResponseWriter, r *Request)メソッドを持っているため、HandlerFunc型でキャストすることでHandler型となる。

3
3
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
3
3