GoのHTTPサーバーの実装
はじめに
Goは標準パッケージとしてHTTPサーバが組み込まれており、net/http
パッケージを用いると簡単にHTTPサーバを動かすことができます。今回は net/http
パッケージの一部(HTTPサーバの内容)の実装を読むことで、HTTPサーバが動く裏側を見てみたいと思います。困ったら 公式ドキュメント を見ましょう。
なお、読んでいるコード Go1.13 のものです。
- はじめに
- Doc
- Examples
- 見通しを良くするために
- Implementation
- まとめ
Doc
まずざっと公式ドキュメントに書いてあるサンプルとドキュメントを眺めて、仕様をおさらいしておきます。
type Handler
ハンドラはHTTPリクエストに対してレスポンスを返します。
ServeHTTPはレスポンスヘッダーとデータをResponseWriterに書き込んでからreturnする必要があります。リクエストが終了したことを示すシグナルを返します。ServeHTTP呼び出しの完了後または完了と同時にResponseWriterを使用したり、Request.Bodyを読み取ることは無効です。
HTTPクライアントのソフトウェア、HTTPプロトコルバージョン、およびクライアントとGoで実装されたサーバー間のミドルウェアによっては、ResponseWriterに書き込んだ後にRequest.Bodyから読み取ることができない場合があります。 丁寧にハンドラを扱う場合は、最初にRequest.Bodyを読み取り、次にレスポンスを返す必要があります。
リクエストボディを読み取る場合を除き、ハンドラは提供されたリクエストを変更しないでください。
ServeHTTPがパニックになった場合、サーバー(ServeHTTPの呼び出し元)は、パニックの影響がアクティブなリクエストとは無関係であると仮定します。パニックを回復し、サーバーのエラーログにスタックトレースを記録し、HTTPプロトコルに応じてネットワーク接続を閉じるか、HTTP/2 RST_STREAMを送信します。ハンドラを中断して、クライアントには中断されたレスポンスが表示されますが、サーバーがエラーを記録しないようにするには、ErrAbortHandlerを用いてパニックを起こします。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
ServeHTTP(ResponseWriter, *Request)
を実装していれば Handler
になることができます。
type HandlerFunc
type HandlerFunc func(ResponseWriter, *Request)
HandlerFunc 型は通常の関数を HTTP ハンドラとして扱えるようにするためのアダプターです。fが適切なシグネチャを持っている関数であれば、HanderFunc(f) はハンドラと振る舞うことができ、関数fを呼び出します。
func (HandlerFunc) ServeHTTP
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
ServeHTTPはf(w, r)を呼び出します。
HandlerFunc
は ServeHTTP
のように自身の関数を呼び出すことで Handler
を満たしています。
type ServeMux
ServeMux
は HTTP リクエストの URL と対応するハンドラを保持するマルチプレクサです。HTTP リクエストが来たときに、対応するハンドラを呼び出します。以下は GoDoc の翻訳です。
デフォルトだと DefaultServeMux
という変数が ServeMux
型として定義されていて、このマルチプレクサが ListenAndServe
が呼ばれたときに用いられます。
ServeMuxはHTTPリクエストのマルチプレクサです。要求されたリクエストのURLと登録されているURLのパターンのリストと比較し、URLに最も一致するパターンのハンドラを呼び出します。
パターン名は「/favicon.ico」といったルート化されたパスや「/images/」(末尾のスラッシュに注意)といったルート化されたサブツリーとして指定されます。 パターンは短いパターンよりも長いパターンが優先されます。そのため「/images/」と「/images/thumbnails/」というパターンがハンドラとして登録されている場合、「/images/thumbnails/」から始まるリクエストパスに対しては、後者のハンドラが呼び出されます。前者は「/images/」というサブツリーに含まれる任意のパスに対して、ハンドラが呼び出されます。
スラッシュで終わるパターンはルートのサブツリーとして定まるため、「/」というパターンは単に「/」というURLのパスにマッチするだけでなく、他の登録されていないパターンとマッチしないすべてのパスとマッチします。
サブツリーが登録されており、末尾がスラッシュなしでサブツリーのルートを指定するリクエストを受信した場合、ServeMuxはそのリクエストをサブツリーのルートにリダイレクトします(末尾のスラッシュを追加します)。この動作は、末尾にスラッシュなしのパスを登録することで上書きできます。たとえば、「/images/」を登録すると、「/images」が別に登録されていない限り、ServeMuxは「/images」に対するリクエストを「/images/」にリダイレクトします。
パターンはオプションでホスト名で始まり、そのホスト上のURLのみに一致するように制限できます。ホスト名が指定されているパターンは一般的なパターンよりも優先されるため、ハンドラは「 http://www.google.com/ 」のリクエストを引き継ぐことなく、2つのパターン「/codesearch」と「codesearch.google.com/」を登録できます。
ServeMuxはURLリクエストパスやホストヘッダーのサニタイズ、ポート番号の除去、
.
や..
要素や繰り返されるスラッシュを含むリクエストのリダイレクト、URLの正規化も行います。
Examples
func ListenAndServe
func ListenAndServe(addr string, handler Handler) error
HTTP サーバを起動する関数です。以下は GoDoc の翻訳です。
ListenAndServeは、TCPネットワークアドレス
addr
をListenし、handler
でServeを呼び出して、リクエストされたコネクション要求を処理します。受け付けられたコネクションは、TCPキープアライブを有効にするように構成されています。ハンドラの引数は通常
nil
です。この場合、DefaultServeMux
が使用されます。
ListenAndServe
は常に、nil
以外のエラーを返します。
ListenAndServe
を使う実装例
package main import ( "io" "log" "net/http" ) func main() { // Hello world, the web server helloHandler := func(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "Hello, world!\n") } http.HandleFunc("/hello", helloHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
func Handle
func Handle(pattern string, handler Handler)
Handle
は、指定されたパターンとハンドラの対応をDefaultServeMux
に登録します。ServeMux
のドキュメントでは、パスに対応するパターンの選択方法について記載されています。
package main import ( "fmt" "log" "net/http" "sync" ) type countHandler struct { mu sync.Mutex // guards n n int } func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mu.Lock() defer h.mu.Unlock() h.n++ fmt.Fprintf(w, "count is %d\n", h.n) } func main() { http.Handle("/count", new(countHandler)) log.Fatal(http.ListenAndServe(":8080", nil)) }
func HandleFunc
func HandleFunc
は func Handle
のラッパーです。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc
は、指定されたパターンのハンドラをDefaultServeMux
に登録します。ServeMux
のドキュメントでは、パスに対応するパターンの選択方法について記載されています。
package main import ( "io" "log" "net/http" ) func main() { h1 := func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Hello from a HandleFunc #1!\n") } h2 := func(w http.ResponseWriter, _ *http.Request) { io.WriteString(w, "Hello from a HandleFunc #2!\n") } http.HandleFunc("/", h1) http.HandleFunc("/endpoint", h2) log.Fatal(http.ListenAndServe(":8080", nil)) }
見通しを良くするために
同じ名前でも、型の名前なのか、関数の名前なのか、型に紐づくメソッド名なのかに注意する必要があります。また Handle
なのか、Handler
なのか、注意が必要です。
上記の GoDoc の説明ではすでに登場していますが、HTTP サーバを利用するユーザとしては以下の 3 つの型を中心に考えると、構成が見やすくなりそうです。
type ServeMux
- URLと対応するハンドラを保持するマルチプレクサ。HTTPリクエストが来たときに、対応するハンドラを呼び出します。
- デフォルトで
DefaultServeMux
という変数が定義されていて、これを利用します。 NewServeMux()
でデフォルト以外のマルチプレクサを生成できます。
type Handler
- ハンドラです。
- 直感的には、リクエストに対してレスポンスを返す関数、と捉えて良いと思います。
- マルチプレクサにハンドラを登録します。
ServeHTTP
メソッドを満たす型(func(ResponseWriter, *Request)
型)がハンドラになることができます。
type HandlerFunc
type HandlerFunc func(ResponseWriter, *Request)
- パスに対してハンドラを紐付ける 型 です。
type Handler
と type HandlerFunc
の関係性というか、なぜ type HandlerFunc
が存在するか、という背景の理解は ruiu さんの記事が参考になりました。
Implementation
ここからは標準パッケージの実装を見てみます。
1. HandleFunc 関数
1-1. HandleFunc
まず HandleFunc
関数を見てみます。HandleFunc
関数はデフォルトでハンドラ関数を DefaultServeMux
変数に登録するのでした。実装は以下のようになっています。DefaultServeMux
変数の HandleFunc
へのラッパーになっています。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
DefaultServeMux
変数とはどのような構造体になっているか確認すると、以下の実装からわかるように *ServeMux
型の変数です。defaultServeMux
はゼロ値で初期化されていて DefaultServeMux
はその初期化された値へのポインタを保持しています。ServeMux
構造体はリクエストのパスとハンドラをマッピングする m
のフィールドが本質のように見えます。
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames } // DefaultServeMux is the default ServeMux used by Serve. var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux type muxEntry struct { h Handler pattern string }
1-2. ServeMux.HandleFunc
*ServeMux
型がレシーバの HandleFunc
関数は以下のようになっています。このメソッドも Handle
メソッドへのラッパーになっています。
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } // handler を HandlerFunc 型に変換して Handle を呼び出す // 変換(Conversions)の仕様は https://golang.org/ref/spec#Conversions を参照 mux.Handle(pattern, HandlerFunc(handler)) }
1-3. ServeMux.Handle
では ServeMux
型の Handle
メソッドの実装を見てみると、以下のようになっています。この実装で ServeMux
構造体のフィールドに値をセットしています。
// Handle registers the handler for the given pattern. // If a handler already exists for pattern, Handle panics. func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() if pattern == "" { panic("http: invalid pattern") } if handler == nil { panic("http: nil handler") } if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) } // パスとハンドラの構造体 muxEntry の値を map へ登録 e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e // TODO: 存在する理由がよくわからず if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) } // TODO: よくわかっていない if pattern[0] != '/' { mux.hosts = true } }
2. ListenAndServe 関数
次に ListenAndServe
を見てみます。
2-1. ListenAndServe
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
ここで生成している Server
構造体で以下で、HTTPサーバとして動作するときのパラメータを定義する構造体です。ListenAndServe
ではListenするアドレスとハンドラのみを初期化するようになっています。構造体のコメントにもあるように Handler
変数は nil
であれば http.DefaultServeMux
がセットされます。
// A Server defines parameters for running an HTTP server. // The zero value for Server is a valid configuration. type Server struct { Addr string // TCP address to listen on, ":http" if empty Handler Handler // handler to invoke, http.DefaultServeMux if nil TLSConfig *tls.Config ReadTimeout time.Duration ReadHeaderTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration MaxHeaderBytes int TLSNextProto map[string]func(*Server, *tls.Conn, Handler) ConnState func(net.Conn, ConnState) ErrorLog *log.Logger BaseContext func(net.Listener) context.Context ConnContext func(ctx context.Context, c net.Conn) context.Context disableKeepAlives int32 // accessed atomically. inShutdown int32 // accessed atomically (non-zero means we're in Shutdown) nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used mu sync.Mutex listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} doneChan chan struct{} onShutdown []func() }
2-2. Server.ListenAndServe
Server
の ListenAndServe
は以下です。
// ListenAndServe always returns a non-nil error. After Shutdown or Close, // the returned error is ErrServerClosed. func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } // TCP のリスナーを生成 ln, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(ln) }
2-3. Server.Serve
続いて srv.Serve(ln)
として実装されている Serve
メソッドを見てみます。
// Serve accepts incoming connections on the Listener l, creating a // new service goroutine for each. The service goroutines read requests and // then call srv.Handler to reply to them. // // HTTP/2 support is only enabled if the Listener returns *tls.Conn // connections and they were configured with "h2" in the TLS // Config.NextProtos. // // Serve always returns a non-nil error and closes l. // After Shutdown or Close, the returned error is ErrServerClosed. func (srv *Server) Serve(l net.Listener) error { if fn := testHookServerServe; fn != nil { fn(srv, l) // call hook with unwrapped listener } origListener := l l = &onceCloseListener{Listener: l} defer l.Close() if err := srv.setupHTTP2_Serve(); err != nil { return err } if !srv.trackListener(&l, true) { return ErrServerClosed } defer srv.trackListener(&l, false) var tempDelay time.Duration // how long to sleep on accept failure baseCtx := context.Background() if srv.BaseContext != nil { baseCtx = srv.BaseContext(origListener) if baseCtx == nil { panic("BaseContext returned a nil context") } } ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { // リスナーの接続要求を待つ(ブロッキング) rw, e := l.Accept() if e != nil { select { case <-srv.getDoneChan(): return ErrServerClosed default: } if ne, ok := e.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { tempDelay = 5 * time.Millisecond } else { tempDelay *= 2 } if max := 1 * time.Second; tempDelay > max { tempDelay = max } srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) time.Sleep(tempDelay) continue } return e } if cc := srv.ConnContext; cc != nil { ctx = cc(ctx, rw) if ctx == nil { panic("ConnContext returned nil") } } tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // before Serve can return // ゴルーチンで処理 go c.serve(ctx) } }
2-4. conn.serve
リクエストをAcceptすると、ゴルーチンとして動作する、リクエストを処理するメソッドを呼び出します。呼び出した後は再度 l.Accept()
として次のリクエストを待ち受けます。
リクエストを処理する serve
メソッドです。いろいろ処理があって長いのですが、わかりやすく色々省くと以下のように見ることができます。conn.readRequest
のメソッドを追っていくと RFC に沿って HTTP リクエストの構文を解析する処理が見れるのですが、(説明しきれないので)ここでは省きます。
func (c *conn) serve(ctx context.Context) { // ... for { w, err := c.readRequest(ctx) // ... serverHandler{c.server}.ServeHTTP(w, w.req) // ... } }
メソッドの完全な実装は以下です。長いです。
// Serve a new connection. func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) defer func() { if err := recover(); err != nil && err != ErrAbortHandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if !c.hijacked() { c.close() c.setState(c.rwc, StateClosed) } }() if tlsConn, ok := c.rwc.(*tls.Conn); ok { if d := c.server.ReadTimeout; d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) } if d := c.server.WriteTimeout; d != 0 { c.rwc.SetWriteDeadline(time.Now().Add(d)) } if err := tlsConn.Handshake(); err != nil { // If the handshake failed due to the client not speaking // TLS, assume they're speaking plaintext HTTP and write a // 400 response on the TLS conn's underlying net.Conn. if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) { io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n") re.Conn.Close() return } c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) return } c.tlsState = new(tls.ConnectionState) *c.tlsState = tlsConn.ConnectionState() if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) { if fn := c.server.TLSNextProto[proto]; fn != nil { h := initNPNRequest{ctx, tlsConn, serverHandler{c.server}} fn(c.server, tlsConn, h) } return } } // HTTP/1.x from here on. ctx, cancelCtx := context.WithCancel(ctx) c.cancelCtx = cancelCtx defer cancelCtx() c.r = &connReader{conn: c} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) for { w, err := c.readRequest(ctx) if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive) } if err != nil { const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n" switch { case err == errTooLarge: // Their HTTP client may or may not be // able to read this if we're // responding to them and hanging up // while they're still writing their // request. Undefined behavior. const publicErr = "431 Request Header Fields Too Large" fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr) c.closeWriteAndWait() return case isUnsupportedTEError(err): // Respond as per RFC 7230 Section 3.3.1 which says, // A server that receives a request message with a // transfer coding it does not understand SHOULD // respond with 501 (Unimplemented). code := StatusNotImplemented // We purposefully aren't echoing back the transfer-encoding's value, // so as to mitigate the risk of cross side scripting by an attacker. fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders) return case isCommonNetReadError(err): return // don't reply default: publicErr := "400 Bad Request" if v, ok := err.(badRequestError); ok { publicErr = publicErr + ": " + string(v) } fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr) return } } // Expect 100 Continue support req := w.req if req.expectsContinue() { if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 { // Wrap the Body reader with one that replies on the connection req.Body = &expectContinueReader{readCloser: req.Body, resp: w} } } else if req.Header.get("Expect") != "" { w.sendExpectationFailed() return } c.curReq.Store(w) if requestBodyRemains(req.Body) { registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead) } else { w.conn.r.startBackgroundRead() } // HTTP cannot have multiple simultaneous active requests.[*] // Until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. // But we're not going to implement HTTP pipelining because it // was never deployed in the wild and the answer is HTTP/2. // // serverHandler を生成して ServeHTTP を呼び出す serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() if !w.shouldReuseConnection() { if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle) c.curReq.Store((*response)(nil)) if !w.conn.server.doKeepAlives() { // We're in shutdown mode. We might've replied // to the user without "Connection: close" and // they might think they can send another // request, but such is life with HTTP/1.1. return } if d := c.server.idleTimeout(); d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) if _, err := c.bufr.Peek(4); err != nil { return } } c.rwc.SetReadDeadline(time.Time{}) } }
serverHandler
は Server
へのポインタを保持しているのみです。定義されているメソッドも ServeHTTP
だけです。実際の処理は *Server
型の srv
変数が実装している ServeHTTP
に移譲しています。
// serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server }
2-5. serverHandler.ServeHTTP
マルチプレクサ(指定しなかった場合は DefaultServeMux
)からリクエストに対応するハンドラを取得して、そのハンドラの ServeHTTP
を呼び出しています。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler // ここで Server のハンドラが nil の場合に DefaultServeMux をセットしている if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
2-5-1. ServeMux.ServeHTTP
serverHandler.ServeHTTP
の一番最後の部分の中身を確認します。
handler.ServeHTTP(rw, req)
少し戻って ServeMux
型は ServeHTTP
を実装しているので Handler
インターフェースを満たしています。
この ServeMux.ServeHTTP
メソッドでは *ServeMux
に登録されているハンドラをリクエストから取得して、そのハンドラの ServeHTTP
を呼び出します。
// ServeHTTP dispatches the request to the handler whose // pattern most closely matches the request URL. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
2-5-2. ServeMux.Handler
ServeMux
の Handler
メソッドは以下のようになっていて、Handler
インターフェースを返すメソッドです。ざっくり ServeMux
で保持しているハンドラを取得するためのメソッドです。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { // CONNECT requests are not canonicalized. if r.Method == "CONNECT" { // If r.URL.Path is /tree and its handler is not registered, // the /tree -> /tree/ redirect applies to CONNECT requests // but the path canonicalization does not. if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok { return RedirectHandler(u.String(), StatusMovedPermanently), u.Path } return mux.handler(r.Host, r.URL.Path) } // All other requests have any port stripped and path cleaned // before passing to mux.handler. host := stripHostPort(r.Host) path := cleanPath(r.URL.Path) // If the given path is /tree and its handler is not registered, // redirect for /tree/. if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok { return RedirectHandler(u.String(), StatusMovedPermanently), u.Path } if path != r.URL.Path { _, pattern = mux.handler(host, path) url := *r.URL url.Path = path return RedirectHandler(url.String(), StatusMovedPermanently), pattern } return mux.handler(host, r.URL.Path) }
2-5-3. ServeMux.handler
// handler is the main implementation of Handler. // The path is known to be in canonical form, except for CONNECT methods. func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones // // ServeMux が保持しているmap からパスに対応するハンドラを取得 if mux.hosts { h, pattern = mux.match(host + path) } if h == nil { h, pattern = mux.match(path) } if h == nil { h, pattern = NotFoundHandler(), "" } return }
2-5-4. ServeMux.match
上記のメソッドの中で呼び出されている match
メソッドです。パスからハンドラを探索して、呼び出し元に返します。
// Find a handler on a handler map given a path string. // Most-specific (longest) pattern wins. func (mux *ServeMux) match(path string) (h Handler, pattern string) { // Check for exact match first. v, ok := mux.m[path] if ok { return v.h, v.pattern } // Check for longest valid match. mux.es contains all patterns // that end in / sorted from longest to shortest. for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" }
まとめ
net/http
のHTTPサーバのリクエストを処理してハンドラを呼び出して、レスポンスを返す実装についてざっと見てみました。