37
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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サーバを立てる

37
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?