post Image
GoでHTTPサーバを立てる

HTTPサーバ!?
何やってるのかよくわからんがすごそうだ!ということで作ってみました。

コード

http_server.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func hello_world(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Hello World")
}

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

    // クロージャを渡しても良い
    // http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    //  fmt.Fprintf(w, "Hello World")
    // })

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

実行

下記コマンドを実行した後に http://localhost:8080 にアクセスしてみるとHello Worldと表示されます。

$ go run http_server.go

はい、簡単でした おしまい。



っじゃなーい!

俺がやりたいのは、もっとこう…あるだろ!

GoがどうやってHTTPサーバを実装しているのかこっから見ていきます。
最低限HTTPサーバに必要なのは以下の二点かな…
1. 80ポート監視
2. リクエスト(URL)に応じて処理を行う

準備

git clone git@github.com:golang/go.git

net/httpを読む

コメントやあまり関係のない引数チェックなどの処理は省いています。

http.HandleFunc

src/net/http/server.go にHandleFuncが定義されているのでここから読みすすめて行く。

大雑把な理解だと渡されたパス(pattern)とハンドラ(処理)を紐付けている。

net/http/server.go 1903~1910付近
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

DefaultServeMuxを追っていくと ServeMux という構造体を返しているのがわかる。
ただHandleFuncを呼び出しているが構造体にそのような定義は無い。。。謎だ。
Goの構造体を調べていくとレシーバと関数を紐付けることができるようだ。
こういう感じで。

sample.go
package main

import (
    "fmt"
)

type User struct {
    name string
    age  int
}

// (u User)が重要
func (u User) hello() string {
    return "hello world"
}

func main() {
    user := User{"hoge", 20}
    fmt.Println(user.hello())
}

構造体を定義している箇所ではhelloのhの字も無いが実行すると確かにhello worldが出力されるのがわかる。
HandleFuncも同じ要領でServeMuxに関数が紐付けられているようでした。
つまり次に呼ばれるのは下記のHandleFunc

net/http/server.go 1895~1897付近
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

ここでURLパターンのチェックとpatternが/の時にリダイレクトハンドラをセット?している。

net/http/server.go 1857~1893付近
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    /* 省略 */

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

    if pattern[0] != '/' {
        mux.hosts = true
    }

    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
            // In pattern, at least the last character is a '/', so
            // strings.Index can't be -1.
            path = pattern[strings.Index(pattern, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

http.ListenAndServe

net/http/server.go 2025~2035付近
func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

net.Listen(“tcp”, addr)

ポートを監視するにはソケットを作成し、ソケットのファイルディスクリプタ経由で監視する。

net/dial.go 258~276付近
func Listen(net, laddr string) (Listener, error) {
    la, err := resolveAddr("listen", net, laddr, noDeadline)
    if err != nil {
        return nil, &OpError{Op: "listen", Net: net, Addr: nil, Err: err}
    }
    var l Listener
    switch la := la.toAddr().(type) {
    case *TCPAddr:
        l, err = ListenTCP(net, la)
    case *UnixAddr:
        l, err = ListenUnix(net, la)
    default:
        return nil, &OpError{Op: "listen", Net: net, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: laddr}}
    }
    if err != nil {
        return nil, err // l is non-nil interface containing nil pointer
    }
    return l, nil
}

listenPlan9->startPlan9->OpenFile OpenFileでOSのシステムコールを呼びファイルを読み書きしている。
ここではソケットファイルを作成し、TCPListenerが構造体として監視に必要な情報を返している。

net/tcpsock_plan9.go 184~198付近
func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) {
    switch net {
    case "tcp", "tcp4", "tcp6":
    default:
        return nil, &OpError{"listen", net, laddr, UnknownNetworkError(net)}
    }
    if laddr == nil {
        laddr = &TCPAddr{}
    }
    fd, err := listenPlan9(net, laddr)
    if err != nil {
        return nil, err
    }
    return &TCPListener{fd}, nil
}

srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})

forで無限ループさせてリクエストが来たらgoroutineで並行処理

net/http/serve.go 2043~2075付近
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure
    if err := srv.setupHTTP2(); err != nil {
        return err
    }
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(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", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve()
    }
}

go c.serve()

ぱーっと見ていくと見慣れたHTTPのヘッダを書き出していくところがある。
ここで謎の感動

ここで必要なのはリクエストの内容を読むことと、リクエストのURLに対応するhandlerの処理を行うこと。
リクエストの内容を読むのは readRequest()で行い。
handlerの選択は serverHandler{c.server}.ServeHTTP(w, w.req) で行っている

net/http/serve.go 1374~1447付近
func (c *conn) serve() {
    c.remoteAddr = c.rwc.RemoteAddr().String()

    /** 省略 HTTPSに関する対応? **/

    c.r = &connReader{r: c.rwc}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    for {
        w, err := c.readRequest()
        if c.r.remain != c.server.initialReadLimitSize() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        if err != nil {
            /** エラー処理 **/
        }

        // Expect 100 Continue support
        req := w.req
        if req.expectsContinue() {
            if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
                // Wrap the Body reader with one that replies on the connection
                req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
            }
            req.Header.Del("Expect")
        } else if req.Header.get("Expect") != "" {
            w.sendExpectationFailed()
            return
        }

        serverHandler{c.server}.ServeHTTP(w, w.req)
        if c.hijacked() {
            return
        }
        w.finishRequest()
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
    }
}

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

net/http/server.go 2025~2035付近
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)
}
handler.ServeHTTP(rw, req)
net/http/serve.go 1843~1853付近
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)
}

ここで最初にセットしたHandleFuncが呼び出される

net/http/serve.go 1560~1562付近
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

おしまい。
最初に立てた予想が大体あっていたのは嬉しかった。
もともとはWebサーバを自作したくて読んでいたので、これで足りないものがいくつかわかったので、今後はそこら辺調べたりしてみる予定っす。

参考にさせていただいたサイト

net/http の動きを少しだけ追ってみた – Golang
Goで簡単なWebサーバを立てる


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。