GolangのHTTPサーバーのコードリーディング

Golang net/http

プログラミング

2019-11-03 11:03:15

概要

GolangでHTTPサーバーを立てるコードの詳細を追ってコードリーディングする。

参考実装

コードリーディングしていく実装はこちら。

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    handler := new(HelloHandler)
    mux.Handle("/", handler)

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }
    s.ListenAndServe()
}

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

冗長に書いているこのコードを一行ずつ追ってコードを簡略化しつつ、リーディングしていく。

ServeHttp(w ResponseWriter, r *Request)

まずは、

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

この部分から見ていく。

ServeHTTP(w ResponseWriter, r *Request)Handlerインターフェースの実装になる。

// url: https://golang.org/src/net/http/server.go?s=61586:61646#L1996
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)
}
// url: https://golang.org/src/net/http/server.go?s=61586:61646#L79
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

参考実装では、ServeHTTP(w ResponseWriter, r *Request)のためにHelloHandler構造体を用意しているが、
HandlerFuncを利用することでより簡潔に書き直すことができる。

// url: https://golang.org/src/net/http/server.go?s=61509:61556#L1993
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

参考実装を書き直すとこんな感じ。

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(hello))

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }
    s.ListenAndServe()
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

ServeHTTP(w ResponseWriter, r *Request)を使っていた部分を書き換えることができた。

ちなみにmux.Handleの中身はこんな実装になっている。

// url: https://golang.org/src/net/http/server.go?s=75321:75365#L2390
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
    }
}

ServeMux

先程短くした部分を更に見ていく。

    mux := http.NewServeMux()
    mux.Handle("/", http.HandlerFunc(hello))

    s := http.Server{
        Addr:    ":3000",
        Handler: mux,
    }

mux.Handle("/", http.HandlerFunc(hello))の部分はHandleFuncを使うと一部を内部的に処理させることができるので、
より短く書くことができる。

url: https://golang.org/src/net/http/server.go?s=75575:75646#L2448
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
url: https://golang.org/src/net/http/server.go?s=75575:75646#L2435
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

上記を加味して書き直すとこんな感じになる。

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)

    s := http.Server{
        Addr:    ":3000",
        Handler: http.DefaultServeMux,
    }
    s.ListenAndServe()
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

DefaultServeMuxは、内部的にはServeMux構造体のポインタが格納された変数になる。
HandleFuncDefaultServeMuxへのurlパターンマッチの登録ができるメソッドになっている。

url: https://golang.org/src/net/http/server.go?s=75575:75646#L2207
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
url: https://golang.org/src/net/http/server.go?s=68149:68351#L2182
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
}

Server

最後に見ていくのはこの部分。

    s := http.Server{
        Addr:    ":3000",
        Handler: http.DefaultServeMux,
    }
    s.ListenAndServe()

s.ListenAndServe()の中身。

url: https://golang.org/src/net/http/server.go?s=68149:68351#L3093
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)
}

Serverに細かい設定値を与える必要がないときはListenAndServe()を使うことで短く書くことができる。
Serverの設定値についてはgolang.org - server.goを参照。

url: https://golang.org/src/net/http/server.go?s=68149:68351#L3071
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

短く書くとこんな感じ。

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", hello)

    http.ListenAndServe(":3000", nil)
}

func hello(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World"))
}

無名関数を使って使うとこんな感じ。

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World"))
    })

    http.ListenAndServe(":3000", nil)
}

所感

golangでhttp routerのパッケージを自作しようとしていて、net/httpの内部的な実装に触れておく必要があったので軽く調べてみた。
見た感じ拡張しやすそうなので自作はしやすいイメージがある。

参考

About the author

Image

bmf san @bmf_san
A web developer in Japan.