###### tags: `golang` `Goプログラミング実践入門` # Goプログラミング実践入門3章<br>net/httpライブラリを使ったリクエスト この章でやること * サーバの起動 * ハンドラを作成 * マルチプレクサにハンドラを登録 * ハンドラのチェイン ![図2.9](https://i.imgur.com/85R82Gg.png) ## 3.1 GO言語のnet/httpライブラリ `net/http`ライブラリは次の2つに分かれている。 書籍では、主にサーバ側の機能について解説されている。 :gem: ドキュメント:[net/http](https://golang.org/pkg/net/http/) ![図3.1](https://i.imgur.com/IDF7HKX.png) ### 3.2.1 Go言語によるWebサーバ --- `net/http`ライブラリはHTTPサーバを起動する機能[ListenAndServe](https://golang.org/src/net/http/server.go?s=96538:96593#L3074)を提供している。 ```go ListenAndServe("ネットワークアドレス",リクエストを処理するハンドラ) ``` リスト3.1で、最も単純なWebサーバを構築する。 ネットワークアドレスとリクエストを処理するハンドラが指定されていないため、[Server型](https://golang.org/pkg/net/http/#Server)のデフォルト値([DefaultServeMux](https://golang.org/src/net/http/server.go?s=68423:68451#L2207))が設定される。 <summary>リスト3.1 最も単純なWebサーバ</summary> ```go package main import ( "net/http" ) func main() { http.ListenAndServe("", nil) } ``` [ListenAndServe](https://golang.org/src/net/http/server.go?s=96538:96593#L3074)は、構造体`Server`を初期化し、[Server.ListenAndServe](https://golang.org/src/net/http/server.go?s=96538:96593#L2810)メソッドを返している。そのため、サーバの設定をする場合、以下のようにできる。 <summary>リスト3.3 設定を加えたWebサーバ</summary> ```go package main import ( "net/http" ) func main() { server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } server.ListenAndServe() } ``` ## 3.3 ハンドラとハンドラ関数 サーバを起動するだけでは何も起きない。 そこでこの節で、リクエストに対する処理を行うハンドラを学習する。 ### 3.3.1 リクエストの処理 --- ハンドラとは具体的には何か、を確認する。 ハンドラ([Handler](https://golang.org/pkg/net/http/#Handler))とは、以下のメソッドを持つインターフェース。 ```go ServeHTTP(ResponseWriter, *Request) ``` ```mermaid classDiagram Handler |>.. HandlerFunc : implements Handler |>.. ServeMux : implements class Handler{ << interface >> + ServeHTTP(ResponseWriter, *Request) } class HandlerFunc{ + ServeHTTP(ResponseWriter, *Request) } class ServeMux{ - mu sync.RWMutex - m map[string]muxEntry - es []muxEntry - hosts bool +NewServeMux() *ServeMux + Handle(string, Handler) + Handler(*Request) (Handler, string) + ServeHTTP(ResponseWriter, *Request) } ``` マルチプレクサは`ServeMux.ServeHTTP`を持つため、`Handler`を実装している。 つまり、`Handler`を`ServeHTTP`にセットすれば良いので、マルチプレクサ([ServeMux](https://golang.org/pkg/net/http/#ServeMux))の代わりにハンドラをセットしても動作する。ただし、 以下URLにアクセスしても同じレスポンスが返ってくる。 * http://localhost:8080/ * http://localhost:8080/anything/at/all <summary>リスト3.6 リクエストの処理(コード)</summary> ```go package main import ( "fmt" "net/http" ) type MyHandler struct{} func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") } func main() { handler := MyHandler{} server := http.Server{ Addr: "127.0.0.1:8080", Handler: &handler, } server.ListenAndServe() } ``` ### 3.3.2 複数のハンドラ --- URLごとに異なるハンドラで処理させるには、構造体[Server.Handler](https://golang.org/pkg/net/http/#Server)にマルチプレクサ([DefaultServeMux](https://golang.org/src/net/http/server.go?s=68423:68451#L2207))をセットすれば良い。そのため、`ListenAndServe`関数にハンドラを設定する必要はない。以下の例では、URLごとに違ったメッセージが表示される。 * http://localhost:8080/hello * http://localhost:8080/world HelloHandlerの例: ```mermaid graph LR subgraph C[サーバの起動] ServeMux --> Server["Server.ListenAndServe()"] end subgraph B[Handlerをマルチプレクサに登録] Handler -->|Handle| ServeMux.m end subgraph A[Handler型を実装] HelloHandler -->|HTTPServe メソッドをもたせる| Hander end ``` <summary>リスト3.7 複数のハンドラによるリクエストの処理</summary> ```go package main import ( "fmt" "net/http" ) type HelloHandler struct{} func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } type WorldHandler struct{} func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "World!") } func main() { hello := HelloHandler{} // helloはハンドラ(http.Handler)。ServeHTTPを持っているので world := WorldHandler{} server := http.Server{ Addr: "127.0.0.1:8080", // Handlerは指定しない -> DefaultServeMuxをハンドラとして利用 } http.Handle("/hello", &hello) // ハンドラhelloをDefaultServeMuxに登録 http.Handle("/world", &world) server.ListenAndServe() } ``` ### 3.3.3 ハンドラ関数 --- ハンドラとハンドラ関数の違いについて。 * ハンドラ メソッド`ServeHTTP`を持つインターフェースのこと。 ```go type Handler interface { ServeHTTP(ResponseWriter, *Request) } ``` * [ハンドラ関数](https://golang.org/pkg/net/http/#HandleFunc) `DefaultServeMux`(マルチプレクサ)にハンドラを登録している。 ```go func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } ``` [HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc)関数型とは違うので注意。 `HandlerFunc`は関数を`Handler`型に変換する。 <summary>リスト3.8 ハンドラ関数によるリクエストの処理</summary> ```go package main import ( "fmt" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func world(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "World!") } func main() { server := http.Server{ Addr: "127.0.0.1:8080", // Handlerは指定しない -> DefaultServeMuxをハンドラとして利用 } http.HandleFunc("/hello", hello) // 関数をハンドラに変換して、DefaultServeMuxに登録 http.HandleFunc("/world", world) server.ListenAndServe() } ``` ```mermaid graph LR subgraph B[サーバの起動] ServeMux --> Server["Server.ListenAndServe()"] end subgraph A["Handler型を実装、マルチプレクサに登録"] func["hello(w, r)"] -->|HandleFunc| ServeMux.m asd["world(w, r)"] -->|HandleFunc| ServeMux.m end ``` `Handlefunc`と`Handle`の使い分けは、設計次第。 下記のリンクを見ると、ここまでの関係性がまとめられている。 :gem: 参考: [【Go】net/httpパッケージを読んでhttp.HandleFuncが実行される仕組み](https://qiita.com/shoichiimamura/items/1d1c64d05f7e72e31a98) ### 3.3.4 ハンドラとハンドラ関数 --- Go言語は無名関数、関数型、クロージャなど関数型言語といくつか共通する機能を持っている。これを利用して、関数を他の関数に渡すことができる。下記では、f1をf2に渡すことで、f2->f1という順序で動作する。 ![図3.3](https://i.imgur.com/GQOApMF.png) 実際に、ハンドラが実行されたときにログ出力されるコードを確認する。 関数`log`は無名関数を返し、`hello`がチェインしている。 <summary>リスト3.10 2つのハンドラ関数のチェイン</summary> ```go package main import ( "fmt" "net/http" "reflect" "runtime" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func log(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name() fmt.Println("ハンドラ関数が呼び出されました - " + name) h(w, r) } } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } http.HandleFunc("/hello", log(hello)) server.ListenAndServe() } ``` :gem: 再掲: [HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc) 3つ以上も可能。 下記の例では、ハンドラのチェインを行っている。 無名関数を返す際に、`HandlerFunc`->`Handler`インターフェースを実装している。 ![図3.4](https://i.imgur.com/ACDIewI.png) <summary>リスト3.11 ハンドラのチェイン</summary> ```go package main import ( "fmt" "net/http" ) type HelloHandler struct{} func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") } func log(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Printf("ハンドラが呼び出されました - %T\n", h) h.ServeHTTP(w, r) }) } func protect(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // some code to make sure the user is authorized h.ServeHTTP(w, r) }) } func main() { server := http.Server{ Addr: "127.0.0.1:8080", } hello := HelloHandler{} http.Handle("/hello", protect(log(hello))) server.ListenAndServe() } ``` ### 3.3.5 ServeMuxとDefaultServeMux --- ServeMuxは、HTTPリクエストのマルチプレクサ(多重通信入口)の役割を果たしている。 ![図3.5](https://i.imgur.com/QvsePBf.png) [ServeMux](https://golang.org/pkg/net/http/#ServeMux)はエントリを持った構造体であり、エントリはURLをハンドラに対応付けたもの。また`ServeHTTP`メソッドを持っているので、ハンドラでもある。 ```mermaid classDiagram class ServeMux{ - mu sync.RWMutex - m map[string]muxEntry - es []muxEntry - hosts bool +NewServeMux() *ServeMux + Handle(string, Handler) + Handler(*Request) (Handler, string) + ServeHTTP(ResponseWriter, *Request) } ``` ServeMuxのServeHTTPは、要求されたURLに最も近いURLを見つけて、対応するハンドラのServeHTTPを呼び出す。 ![図3.6](https://i.imgur.com/Jr2c13Z.png) * [DefaultServeMux](https://golang.org/src/net/http/server.go?s=68423:68451#L2207)について DefaultServeMuxは、`net/http`ライブラリをインポートしたアプリケーションが、一般的に利用できるServeMuxのこと。また、構造体Server内にハンドラが指定されていない場合にも使われるServeMuxのインスタンス。 * 図3.6のURLpathのハンドリングについて * 「/random」の場合、一致するものがないため「/」が呼ばれる * 「/hello/there」の場合、一致するものがないため「/」が呼ばれる * 原因は「/hello/」と登録していないため * URL「/hello/」が登録された場合、「/hello/there」が要求されたときに完全一致するpathがないので、「/hello/」を次に探す ### 3.3.6 その他のマルチプレクサ ServeMuxでは、「/thread/123」の123という部分を取り出す場合、[Go言語でTDDする勉強会](https://gist.github.com/tukkyr/9ac2e847f425480439af251f38bcd459#httpsgithubcomquiilearn-go-with-testsblobmasterhttp-servermd)で紹介されているように、「/thread/」という部分をトリムなど、前処理が必要。 URLとのパターンマッチングで変数を使えないという点が不満。 サードパーティ製のライブラリでは、このような制約の一部が克服されている。 * [gorilla/mux](https://github.com/gorilla/mux) * [httprouter](https://github.com/julienschmidt/httprouter) ## 3.5 まとめ * Go言語には、Webアプリケーション構築のため、`net/http`と`html/template`がある * 優れたWebフレームワークは使いやすく、時間を節約できることが多いが、利用する前にWebプログラミングの基礎を学習した方が良い * `net/http`を使えばHTTPSを実現できる * Go言語のハンドラとは、`ServeHTTP`というメソッドを持った任意の構造体のこと。引数は`HTTPResponseWriter`と`Request`のポインタ * ハンドラ関数とは、ハンドラのように動作する関数のこと。`ServeHTTP`と同じシグネイチャであり、リクエストの処理に利用される * ハンドラやハンドラ関数は、チェインすることで、リクエスト処理をモジュール化できる * マルチプレクサ(ServeMux)もハンドラ。HTTPリクエストを受けて、リクエスト内のURLに応じて適切なハンドラに転送する * DefaultServeMuxはServeMucのインスタンスであり、一般に利用できるようになっている。デフォルトのマルチプレクサとして使われる