目次
目次
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 の登録をされないようにしています。
以下のようなコードを実行すると、/count
pattern が重複するため 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 (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)
}
詳しくはこちらの記事が分かりやすいです。
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
は次の処理をします。
- リクエストの body や URI, Method,Proto をパースし
Request
構造体に詰め替える Request
構造体を、response
構造体のreq
に含めるresponse
を返す
func (c *conn) serve(ctx context.Context)
req, err := readRequest(c.bufr)
Request
とresponse
構造体を引数に、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
が完全一致するものを探します。一致するものがあれば、muxEntry
の handler
とpattern
を返します。
次に、エントリーのスライスであるmux.es
から探します。一致するものがあれば、同様にhandler
とpattern
を返します。
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, ""
}