🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

1回答

1971閲覧

なぜServeHTTPを実装しているだけで呼ばれるのかがわからない

anonyrabbit

総合スコア78

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

2クリップ

投稿2019/11/21 10:06

お世話になります。低レベルな質問かもしれませんが、解決したいので質問させてください。
こちらのコードでは、 main()の中でrootHandler(loginHandler)とすることで内部でServeHTTPが呼ばれているようです。
しかし、なぜ勝手に呼ばれるのでしょうか?どこがどうなって呼ばれる仕組みになっているのか教えていただきたいです。
よろしくお願いいたします。

Go

1 2// Use as a wrapper around the handler functions. 3type rootHandler func(http.ResponseWriter, *http.Request) error 4 5func loginHandler(w http.ResponseWriter, r *http.Request) error { 6 if r.Method != http.MethodPost { 7 return NewHTTPError(nil, 405, "Method not allowed.") 8 } 9 10 body, err := ioutil.ReadAll(r.Body) // Read request body. 11 if err != nil { 12 return fmt.Errorf("Request body read error : %v", err) 13 } 14 15 // Parse body as json. 16 var schema loginSchema 17 if err = json.Unmarshal(body, &schema); err != nil { 18 return NewHTTPError(err, 400, "Bad request : invalid JSON.") 19 } 20 21 ok, err := loginUser(schema.Username, schema.Password) 22 if err != nil { 23 return fmt.Errorf("loginUser DB error : %v", err) 24 } 25 26 if !ok { // Authentication failed. 27 return NewHTTPError(nil, 401, "Wrong password or username.") 28 } 29 w.WriteHeader(200) // Successfully authenticated. Return access token? 30 return nil 31} 32 33// rootHandler implements http.Handler interface. 34func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 35 err := fn(w, r) // Call handler function 36 if err == nil { 37 return 38 } 39 // This is where our error handling logic starts. 40 log.Printf("An error accured: %v", err) // Log the error. 41 42 clientError, ok := err.(ClientError) // Check if it is a ClientError. 43 if !ok { 44 // If the error is not ClientError, assume that it is ServerError. 45 w.WriteHeader(500) // return 500 Internal Server Error. 46 return 47 } 48 49 body, err := clientError.ResponseBody() // Try to get response body of ClientError. 50 if err != nil { 51 log.Printf("An error accured: %v", err) 52 w.WriteHeader(500) 53 return 54 } 55 status, headers := clientError.ResponseHeaders() // Get http status code and headers. 56 for k, v := range headers { 57 w.Header().Set(k, v) 58 } 59 w.WriteHeader(status) 60 w.Write(body) 61} 62 63func main() { 64 // Notice rootHandler. 65 http.Handle("/login/", rootHandler(loginHandler)) 66 log.Fatal(http.ListenAndServe(":8080", nil)) 67}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

Goではあらゆる非インターフェース型の定義に対しメソッドを追加することができます。

rootHandlerと名付けた「func(http.ResponseWriter, *http.Request) error」型(関数型)に
ServeHTTPというメソッドを追加しているのが上記のコードです。

http.Handleの第二引数は「http.Handler」というインターフェース型です。
その定義内容を見ると以下のように書かれています。

go

1type Handler interface{ 2 ServeHTTP(w ResponseWriter, r *Request) 3}

つまり、rootHandlerは「ServeHTTP(w ResponseWriter, r *Request)」メソッドを持つので、
http.Handlerインターフェースと互換があります。
なので「rootHandler(loginHandler)」はhttp.Handleの第二引数に引き渡すことができます。

ざっくり説明すると、Goのhttpサーバー実装はHTTPリクエストのURLにしたがってhttp.Handlerインターフェースと互換のあるインスタンスのServeHTTPメソッドを呼ぶ仕掛けになっています。

http.ListenAndServeの実装は以下のようになっていて、ここから順次掘り下げていけば、
ServeHTTPメソッドを呼ぶ箇所にたどり着けるとは思います。(だいぶ読み進める必要はありますが)

go

1func ListenAndServe(addr string, handler Handler) error { 2 server := &Server{Addr: addr, Handler: handler} 3 return server.ListenAndServe() 4}

また、ListenAndServeの第二引数にnilを渡した場合、http.DefaultServeMuxが代わりに使われます。
http.Handleを呼ぶとhttp.DefaultServeMuxにパスプレフィックスとハンドラインスタンスをセットで記録しています。
http.DefaultServeMuxもまたhttp.Handlerを満たすインスタンスでリクエストのたびにServeHTTPメソッドが呼ばれます。

これらが連携してハンドラのServeHTTPが呼ばれる仕掛けになっています。

いろいろ省略している記述を省略せずに書くと以下のようになります
https://play.golang.org/p/ZgQau4jhyc3

go

1package main 2 3import ( 4 "fmt" 5 "log" 6 "net/http" 7) 8 9type loginFunc func(w http.ResponseWriter, r *http.Request) error 10 11func login(w http.ResponseWriter, r *http.Request) error { 12 return nil 13} 14 15func (f loginFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { 16 if err := f(w, r); err != nil { 17 http.Error(w, "login failed", http.StatusForbidden) 18 return 19 } 20 fmt.Fprintf(w, "hello") 21} 22 23func main() { 24 mux := http.NewServeMux() 25 mux.Handle("/", loginFunc(login)) 26 server := &http.Server{Addr: ":8080", Handler: mux} 27 if err := server.ListenAndServe(); err != nil { 28 log.Fatal(err) 29 } 30}

投稿2019/11/21 12:02

編集2019/11/22 00:41
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問