Skip to content

Go net/http パッケージ(server) Walkthrough

2023/02/26

Go

目次

目次

  1. net/http パッケージについて
  2. Handle の処理について
    1. バリデーション
    2. pattern と handler の登録
    3. Entries
  3. ListenAndServe の処理について
    1. ネットワークリスナー
    2. Serve
    3. リクエストパラメータの解決とレスポンス構造体
    4. Request Path と Handler の解決

net/http パッケージについて

net/http package は HTTP クライアントとサーバーの実装を提供しています。
本記事では server 側のコードに触れます。

$ gocloc --not-match=test net/http
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Go                              43           3030           7150          20945
CSS                              1              0              0              1
HTML                             1              0              0              1
-------------------------------------------------------------------------------
TOTAL                           45           3030           7150          20947
-------------------------------------------------------------------------------

インクリメントするシンプルな/countをエンドポイントにもつ handler と、そのサーバーのシンプルな実装から処理を追っていきます。

type countHandler struct {
	mu sync.Mutex
	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))
}

Handle の処理について

Handle 関数は引数に渡した Handler と pattern を ServerMux に追加します。

DefaultServeMuxは Serve 関数が用いるデフォルトのServeMuxですが、具体的な箇所はListenAndServeと合わせて後述します。

func Handle(pattern string, handler Handler)

func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

ServerMuxの Handle 関数では、pattern のバリデーションと、pattern と handler の関連付けを行います。

func (mux *ServeMux) Handle(pattern string, handler Handler)

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
	}
}

バリデーション

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)
}

mu sync.RwMutexにより、競合する pattern の登録をされないようにしています。

以下のようなコードを実行すると、/countpattern が重複するため panic が発生します。

for i := 0; i < 2; i++ {
    go func() {
        http.Handle("/count", new(countHandler))
    }()
}
// panic: multiple registrations for /count

pattern と handler の登録

mux.mは pattern をキーに、muxEntryをバリューに持つ連想配列です。

m     map[string]muxEntry
type muxEntry struct {
	h       Handler
	pattern string
}

バリデーションが通った handler と pattern の組み合わせをmux.mに追加します。

func (mux *ServeMux) Handle(pattern string, handler Handler)

e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e

Entries

mux.esはエントリーのスライスです。エントリーとは、/users/のように最後が/で終わる pattern を指します。

func (mux *ServeMux) Handle(pattern string, handler Handler)

if pattern[len(pattern)-1] == '/' {
	mux.es = appendSorted(mux.es, e)
}

pattern が長いものから短いものにソートされます。

func appendSorted(es []muxEntry, e muxEntry) []muxEntry

func appendSorted(es []muxEntry, e muxEntry) []muxEntry

エントリーの扱い方については、『Request Path と Handler の解決』にて後述します。

func TestMain(t *testing.T) {
	http.Handle("/count/", new(secondCountHandler))
	http.Handle("/count/total/", new(secondCountHandler))
	http.Handle("/test/", new(countHandler))
	http.Handle("/test/total/", new(countHandler))

}
// mtx.es[0] pattern: /count/total/
// mtx.es[1] pattern: /test/total/
// mtx.es[2] pattern: /count/
// mts.es[3] pattern: /test/

ListenAndServe の処理について

Server 構造体を初期化し、その ListenAndServe を呼びます。

func ListenAndServe(addr string, handler Handler) error

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

Server struct の ListenAndServe では、ネットワークリスナー の作成とリクエストの判別、handler を実行します。

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe() error {
	if srv.shuttingDown() {
		return ErrServerClosed
	}
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(ln)
}

ネットワークリスナー

ネットワークリスナーを用いて、クライアントとサーバのソケットを接続し通信します。

プロトコルにはrfc793で定められた TCP を使用します。

func Listen(network, address string) (Listener, error)

func Listen(network, address string) (Listener, error) {
	var lc ListenConfig
	return lc.Listen(context.Background(), network, address)
}

詳しくはこちらの記事が分かりやすいです。

Socket プログラミング

Serve

Serve 関数はネットワークリスナーから HTTP コネクションを待ち、conn.serveのゴルーチンを生成します。

func (srv *Server) Serve(l net.Listener) error

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)

	baseCtx := context.Background()
	if srv.BaseContext != nil {
		baseCtx = srv.BaseContext(origListener)
		if baseCtx == nil {
			panic("BaseContext returned a nil context")
		}
	}

	var tempDelay time.Duration // how long to sleep on accept failure

	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			if srv.shuttingDown() {
				return ErrServerClosed
			}
			if ne, ok := err.(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", err, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return err
		}
		connCtx := ctx
		if cc := srv.ConnContext; cc != nil {
			connCtx = cc(connCtx, rw)
			if connCtx == nil {
				panic("ConnContext returned nil")
			}
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew, runHooks) // before Serve can return
		go c.serve(connCtx)
	}
}

for でクライアントからの接続を待ちます。

func (srv *Server) Serve(l net.Listener) error

接続要求があったときに、net.Listener Accept関数がnet.Connを返します。

func (srv *Server) Serve(l net.Listener) error

rw, err := l.Accept()

リクエストパラメータの解決とレスポンス構造体

readRequestは次の処理をします。

func (c *conn) serve(ctx context.Context)

req, err := readRequest(c.bufr)

Requestresponse構造体を引数に、handler を実行する ServeHTTP を呼び出します。

func (c *conn) serve(ctx context.Context)

serverHandler{c.server}.ServeHTTP(w, w.req)

Request Path と Handler の解決

ServeMuxが指定されていない場合、DefaultServeMuxが利用されます。

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)

handler := sh.srv.Handler
if handler == nil {
    handler = DefaultServeMux
}

path/count かつ、その handler が登録されていない場合は/count/ にリダイレクトされます。

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)

if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
    return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}

はじめに、pathが完全一致するものを探します。一致するものがあれば、muxEntryhandlerpatternを返します。

次に、エントリーのスライスであるmux.esから探します。一致するものがあれば、同様にhandlerpatternを返します。

func (mux *ServeMux) match(path string) (h Handler, pattern string)

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, ""
}