Golang の http パッケージの Handle や HandleFunc に関するコードを何となく読んでみた時のメモ

Golang の http パッケージを使って HTTP Server を作るに当たっていろいろな書き方があるけど、例えば以下のようなコードを書いたとき、登場するの関数が何をしているのかあんまりわかっていなかったので調べた。

package main

import (
	"fmt"
	"net/http"
)

type apiHandler struct{}

func (apiHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}

func main() {
	mux := http.NewServeMux()
	mux.Handle("/api/", apiHandler{}) 
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		// The "/" pattern matches everything, so we need to check
		// that we're at the root here.
		if req.URL.Path != "/" {
			http.NotFound(w, req)
			return
		}
		fmt.Fprintf(w, "Welcome to the home page!")
	})
}

NewServeMux

NewServeMux は ServeMux のインスタンスを生成している。
ServeMux は、受け付けた HTTP リクエストの URL とそれに対応するハンドラを登録するために利用される構造体。

  • src/net/http/server.go – go – Git at Google
  • // ServeMux is an HTTP request multiplexer.
    // It matches the URL of each incoming request against a list of registered
    // patterns and calls the handler for the pattern that
    // most closely matches the URL.
    //
    // Patterns name fixed, rooted paths, like "/favicon.ico",
    // or rooted subtrees, like "/images/" (note the trailing slash).
    // Longer patterns take precedence over shorter ones, so that
    // if there are handlers registered for both "/images/"
    // and "/images/thumbnails/", the latter handler will be
    // called for paths beginning "/images/thumbnails/" and the
    // former will receive requests for any other paths in the
    // "/images/" subtree.
    //
    // Note that since a pattern ending in a slash names a rooted subtree,
    // the pattern "/" matches all paths not matched by other registered
    // patterns, not just the URL with Path == "/".
    //
    // If a subtree has been registered and a request is received naming the
    // subtree root without its trailing slash, ServeMux redirects that
    // request to the subtree root (adding the trailing slash). This behavior can
    // be overridden with a separate registration for the path without
    // the trailing slash. For example, registering "/images/" causes ServeMux
    // to redirect a request for "/images" to "/images/", unless "/images" has
    // been registered separately.
    //
    // Patterns may optionally begin with a host name, restricting matches to
    // URLs on that host only. Host-specific patterns take precedence over
    // general patterns, so that a handler might register for the two patterns
    // "/codesearch" and "codesearch.google.com/" without also taking over
    // requests for "http://www.google.com/".
    //
    // ServeMux also takes care of sanitizing the URL request path and the Host
    // header, stripping the port number and redirecting any request containing . or
    // .. elements or repeated slashes to an equivalent, cleaner URL.
    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
    }
    
    :
    
    // NewServeMux allocates and returns a new ServeMux.
    func NewServeMux() *ServeMux { return new(ServeMux) }
    

http.Handle

URLのパターンをServeMuxに登録するための関数。
ServeMux に Handle メソッドが定義されているので、mux.Handle("/api/", apiHandler{}) でこれが呼び出される。
Handle(pattern string, handler Handler) の第二引数で Handler が定義されている。

  • src/net/http/server.go – go – Git at Google
  • // 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)
    	}
    	e := muxEntry{h: handler, pattern: pattern}
    	mux.m[pattern] = e
    	if pattern[len(pattern)-1] == '/' {
    		mux.es = appendSorted(mux.es, e)
    	}
    	if pattern[0] != '/' {
    		mux.hosts = true
    	}
    }
    
    :
    
    // Handle registers the handler for the given pattern
    // in the DefaultServeMux.
    // The documentation for ServeMux explains how patterns are matched.
    func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
    

http.Handler


http.Handler は、ServeHTTP メソッドを持った Interface。
これは HTTPResponseWriter という Interface と、Request 構造体のポインタを引数に取っている。
Handler は ServeHTTP という名前が付けられたもので、ServeHTTP(ResponseWriter, *Request)というメソッドを持つもの全てがハンドラとなる模様。

  • src/net/http/server.go – go – Git at Google
  • // A Handler responds to an HTTP request.
    //
    // ServeHTTP should write reply headers and data to the ResponseWriter
    // and then return. Returning signals that the request is finished; it
    // is not valid to use the ResponseWriter or read from the
    // Request.Body after or concurrently with the completion of the
    // ServeHTTP call.
    //
    // Depending on the HTTP client software, HTTP protocol version, and
    // any intermediaries between the client and the Go server, it may not
    // be possible to read from the Request.Body after writing to the
    // ResponseWriter. Cautious handlers should read the Request.Body
    // first, and then reply.
    //
    // Except for reading the body, handlers should not modify the
    // provided Request.
    //
    // If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
    // that the effect of the panic was isolated to the active request.
    // It recovers the panic, logs a stack trace to the server error log,
    // and either closes the network connection or sends an HTTP/2
    // RST_STREAM, depending on the HTTP protocol. To abort a handler so
    // the client sees an interrupted response but the server doesn't log
    // an error, panic with the value ErrAbortHandler.
    type Handler interface {
    	ServeHTTP(ResponseWriter, *Request)
    }
    

http.HandleFunc

特定のパターンを処理するためのハンドラ関数を DefaultServeMux に登録する関数。

mux.HandleFunc() では以下のメソッドが呼び出される。

ServeMux がある場合は、第2引数の handler を HandlerFunc にキャストして、自身の Handle() (DefaultServeMux.Handle(pattern, handler)) を呼び出して DefaultServeMux に追加して、、ServeMux がない場合は、DefaultServeMux.HandleFunc() を呼び出して URL が登録される模様。

  • src/net/http/server.go – go – Git at Google
  • // 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")
    	}
    	mux.Handle(pattern, HandlerFunc(handler))
    }
    
    :
    
    // HandleFunc registers the handler function for the given pattern
    // in the DefaultServeMux.
    // The documentation for ServeMux explains how patterns are matched.
    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    	DefaultServeMux.HandleFunc(pattern, handler)
    }
    

http.HandlerFunc

HandlerFunc は func(ResponseWriter, *Request) 関数を型として定義されたもの。関数ではなく型のこと。
ServeHTTP メソッドが実装されている。
適切な値を持った関数 f() を メソッド f を持つハンドラに変換する。

  • src/net/http/server.go – go – Git at Google
  • // The HandlerFunc type is an adapter to allow the use of
    // ordinary functions as HTTP handlers. If f is a function
    // with the appropriate signature, HandlerFunc(f) is a
    // Handler that calls f.
    type HandlerFunc func(ResponseWriter, *Request)
    
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    	f(w, r)
    }
    

DefaultServeMux/ServeMux

DefaultServeMux 変数は *ServeMux 型の変数で、 defaultServeMux のポインタが DefaultServeMux へ格納されている。
コメントを見ると、リクエストのURLを登録済みパターンのリストと照合し、URLに最も近いパターンの handler を呼び出す、とされている。
前述のコードでは、ServeMux.Handleメソッドを呼び出して以下のようにURLのPathとHandlerを登録しており、HTTPリクエストのURLが /api/ の場合、apiHandlerのServeHTTPメソッドが呼び出される。

  • src/net/http/server.go – go – Git at Google
  • // ServeMux is an HTTP request multiplexer.
    // It matches the URL of each incoming request against a list of registered
    // patterns and calls the handler for the pattern that
    // most closely matches the URL.
    //
    // Patterns name fixed, rooted paths, like "/favicon.ico",
    // or rooted subtrees, like "/images/" (note the trailing slash).
    // Longer patterns take precedence over shorter ones, so that
    // if there are handlers registered for both "/images/"
    // and "/images/thumbnails/", the latter handler will be
    // called for paths beginning "/images/thumbnails/" and the
    // former will receive requests for any other paths in the
    // "/images/" subtree.
    //
    // Note that since a pattern ending in a slash names a rooted subtree,
    // the pattern "/" matches all paths not matched by other registered
    // patterns, not just the URL with Path == "/".
    //
    // If a subtree has been registered and a request is received naming the
    // subtree root without its trailing slash, ServeMux redirects that
    // request to the subtree root (adding the trailing slash). This behavior can
    // be overridden with a separate registration for the path without
    // the trailing slash. For example, registering "/images/" causes ServeMux
    // to redirect a request for "/images" to "/images/", unless "/images" has
    // been registered separately.
    //
    // Patterns may optionally begin with a host name, restricting matches to
    // URLs on that host only. Host-specific patterns take precedence over
    // general patterns, so that a handler might register for the two patterns
    // "/codesearch" and "codesearch.google.com/" without also taking over
    // requests for "http://www.google.com/".
    //
    // ServeMux also takes care of sanitizing the URL request path and the Host
    // header, stripping the port number and redirecting any request containing . or
    // .. elements or repeated slashes to an equivalent, cleaner URL.
    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
    }
    
    type muxEntry struct {
    	h       Handler
    	pattern string
    }
    
    // DefaultServeMux is the default ServeMux used by Serve.
    var DefaultServeMux = &defaultServeMux
    
    var defaultServeMux ServeMux
    
  • src/net/http/server.go – go – Git at Google
  • // A Server defines parameters for running an HTTP server.
    // The zero value for Server is a valid configuration.
    type Server struct {
    	// Addr optionally specifies the TCP address for the server to listen on,
    	// in the form "host:port". If empty, ":http" (port 80) is used.
    	// The service names are defined in RFC 6335 and assigned by IANA.
    	// See net.Dial for details of the address format.
    	Addr string
    	Handler Handler // handler to invoke, http.DefaultServeMux if nil
    
  • src/net/http/server.go – go – Git at Google
  • // serverHandler delegates to either the server's Handler or
    // DefaultServeMux and also handles "OPTIONS *" requests.
    type serverHandler struct {
    	srv *Server
    }
    
    func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    	handler := sh.srv.Handler
    	if handler == nil {
    		handler = DefaultServeMux
    	}
    	if req.RequestURI == "*" && req.Method == "OPTIONS" {
    		handler = globalOptionsHandler{}
    	}
    	handler.ServeHTTP(rw, req)
    }
    

まとめ

とりあえず全体的に http.Handler の ServeHTTP(ResponseWriter, *Request) で処理されることになるっぽい。

Handle などの関数は http パッケージの関数であり、ServeMux のメソッドでもある模様。実際には DefaultServeMux が持っている関数を呼び出すためのもので、例えば、http.Handle を呼び出すコードは、DefaultServeMux のメソッド Handle を呼び出すことになるようだった。

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です