HTTPサーバ!?
何やってるのかよくわからんがすごそうだ!ということで作ってみました。
コード
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サーバに必要なのは以下の二点かな...
- 80ポート監視
- リクエスト(URL)に応じて処理を行う
準備
git clone git@github.com:golang/go.git
net/httpを読む
コメントやあまり関係のない引数チェックなどの処理は省いています。
http.HandleFunc
src/net/http/server.go にHandleFuncが定義されているのでここから読みすすめて行く。
大雑把な理解だと渡されたパス(pattern)とハンドラ(処理)を紐付けている。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
DefaultServeMuxを追っていくと ServeMux という構造体を返しているのがわかる。
ただHandleFuncを呼び出しているが構造体にそのような定義は無い。。。謎だ。
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
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
ここでURLパターンのチェックとpatternが/の時にリダイレクトハンドラをセット?している。
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
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)
ポートを監視するにはソケットを作成し、ソケットのファイルディスクリプタ経由で監視する。
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が構造体として監視に必要な情報を返している。
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で並行処理
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) で行っている
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)
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)
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が呼び出される
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
おしまい。
最初に立てた予想が大体あっていたのは嬉しかった。
もともとはWebサーバを自作したくて読んでいたので、これで足りないものがいくつかわかったので、今後はそこら辺調べたりしてみる予定っす。