目次
目次
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 (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, ""
}