dshimizu/blog

アルファ版

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

Go の 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!")
    })
}

環境

% go version
go version go1.14.2 darwin/amd64

http.Handle()

URLのパターンをServeMuxに登録するための関数。 ServeMux 構造体に Handle メソッドが定義されているので、mux.Handle("パス(ex /hello)", ServeHTTP() を実装した構造体 (ex apiHandler{})) みたいな形式でこれが呼び出される。 func (mux *ServeMux) 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.HandleFunc()

特定のパターンを処理するためのハンドラ関数を DefaultServeMux の変数に登録する関数。 HandleFunc() 関数内で、 DefaultServeMux(http.ServeMux型)のHandleFunc メソッドを呼び出している。

  • src/net/http/server.go - go - Git at Google
  • // 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.ServeMux.HandleFuncメソッドでは、ServeMux がある場合は、第2引数の handlerHandlerFunc に変換して、自身の 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))
    }
    

http.Handler

ServeHTTP メソッドだけを持つ Interface。 ServeHTTP(ResponseWriter, *Request) を実装していれば Handler を満たす。 この ServeHTTP メソッドはHTTPリクエストを受けてHTTPレスポンスを返す役割を持っている。

  • 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.HandlerFunc

HandlerFunc()func(ResponseWriter, *Request) 関数の別名として定義されているもの。 f(w, r) の形で ServeHTTP(w ResponseWriter, r *Request) が呼び出される。

  • 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 へ格納されている。 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
    }
    
    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
    

DefaultServeMux を使用したルーティング処理は、ServeHTTP メソッドで行われる。 メソッドに応じて分かれているように思われたが、http.Handler でリクエストを取得して、ServeHTTP メソッドが呼び出されているように思われる。

  • src/net/http/server.go - go - Git at Google
  • // 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)
    }
    
  • 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.ServeMux.Handler は下記で、今度は handler メソッドが呼ばれている。

  • src/net/http/server.go - go - Git at Google
  • // Handler returns the handler to use for the given request,
    // consulting r.Method, r.Host, and r.URL.Path. It always returns
    // a non-nil handler. If the path is not in its canonical form, the
    // handler will be an internally-generated handler that redirects
    // to the canonical path. If the host contains a port, it is ignored
    // when matching handlers.
    //
    // The path and host are used unchanged for CONNECT requests.
    //
    // Handler also returns the registered pattern that matches the
    // request or, in the case of internally-generated redirects,
    // the pattern that will match after following the redirect.
    //
    // If there is no registered handler that applies to the request,
    // Handler returns a ``page not found'' handler and an empty pattern.
    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)
    }
    

http.ServeMux.match メソッドでハンドラを取得している模様。

  • src/net/http/server.go - go - Git at Google
  • // 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
        if mux.hosts {
            h, pattern = mux.match(host + path)
        }
        if h == nil {
            h, pattern = mux.match(path)
        }
        if h == nil {
            h, pattern = NotFoundHandler(), ""
        }
        return
    }
    

    • src/net/http/server.go - go - Git at Google
    • // 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, ""
      }
      

    NewServeMux

    NewServeMux()ServeMux 構造体を初期化するコンストラクタの役割となっているもの。

    • 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 {
          explicit bool
          h        Handler
          pattern  string
      }
      :
      :
      // NewServeMux allocates and returns a new ServeMux.
      func NewServeMux() *ServeMux { return new(ServeMux) }
      :
      :
      var DefaultServeMux = NewServeMux()
      

    まとめ

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

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

    参考