Go web開發初探

2017年的第一篇博客,也是第一次寫博客,寫的很差,請各位見諒。html

本人以前一直學習java、java web,最近開始學習Go語言,因此也想了解一下Go語言中web的開發方式以及運行機制。java

在《Go web編程》一書第三節中簡要的提到了Go語言中http的運行方式,我這裏是在這個的基礎上更加詳細的梳理一下。web

這裏先提一句,本文中展現的源代碼都是在Go安裝目錄下src/net/http/server.go文件中(除了本身寫的實例程序),若是各位還想理解的更詳細,能夠本身再去研究一下源代碼。編程

《Go web編程》3.4節中提到http有兩個核心功能:Conn, ServeMux , 可是我以爲還有一個Handler接口也挺重要的,後邊我們提到了再說。
瀏覽器

先從一個簡單的實例來看一下Go web開發的簡單流程:tomcat

package main

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

func sayHello(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Hello World!")

}
func main() {
	http.HandleFunc("/hello", sayHello)  //註冊URI路徑與相應的處理函數
	er := http.ListenAndServe(":9090", nil)  // 監聽9090端口,就跟javaweb中tomcat用的8080差很少一個意思吧
	if er != nil {
		log.Fatal("ListenAndServe: ", er)
	}
}

  在瀏覽器運行localhost:9090/hello   就會在命令行或者所用編輯器的輸出窗口 「Hello World!」 (這裏爲了簡便,就沒往網頁裏寫入信息)多線程

根據這個簡單的例子,一步一步的分析它是如何運行。tcp

首先是註冊URI與相應的處理函數,這個就跟SpringMVC中的Controller差很少。編輯器

 

http.HandleFunc("/hello", sayHello)

  來看一下他的源碼:函數

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

  裏邊實際是調用了DefaultServeMux的HandlerFunc方法,那麼這個DefaultServeMux是啥,HandleFunc又幹了啥呢?

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
	explicit bool
	h        Handler
	pattern  string
}


func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} }


var DefaultServeMux = NewServeMux() 

  事實上這個DefaultServeMux就是ServeMux結構的一個實例(好吧,看名字也看的出來),ServeMux是Go中默認的路由表,裏邊有個一map類型用於存儲URI與處理方法的對應的鍵值對(String,muxEntry),muxEntry中的Handler類型就是對應的方法。

再來看HandleFunc方法:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern " + pattern)
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if mux.m[pattern].explicit {
		panic("http: multiple registrations for " + pattern)
	}

	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}
	}
}

  HandleFunc中調用了ServeMux的handle方法,這個handle纔是真正的註冊處理函數,並且注意到調用handle方法是第二個參數進行了強制類型轉換(紅色加粗標註部分),將一個func(ResponseWriter, *Request)函數轉換成了HanderFunc(ResponseWriter, *Request)函數(注意這裏HandlerFunc比一開始調用的HandleFunc多了個r,別弄混了),下面看一下這個函數:

type HandlerFunc func(ResponseWriter, *Request)

  這個HandlerFunc和咱們以前寫的sayHello函數有相同的參數,因此能強制轉換。 而Handle方法的第二個參數是Handler類型,這就說明HandlerFunc函數也是一個Handler,下邊看一個Handler的定義:

  

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

  Handler是定義的是一個接口,裏邊只有一個ServeHTTP函數,根據Go裏邊的實現接口的規則,只要實現了ServeHTTP函數,都算是實現了Handler方法。HandlerFunc函數實現了ServeHTTP函數,只不過內部仍是調用的HandlerFunc函數。經過這個流程咱們能夠知道,咱們一個開始寫的一個普通方法sayHello方法最後被轉換成了一個Handler,當Handler調用ServeHTTP函數時就是調用了咱們的sayHello函數。

 到這差很少,這個註冊的過程就差很少了,若是想了解的更詳細,須要各位本身去細細的研究代碼了~~

下邊看一下查找相應的Handler是怎樣一個過程:

er := http.ListenAndServe(":9090", nil)

  

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.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)})
}

  ListenAndServe中生成了一個Server的實例,並最終調用了它的Serve方法。把Serve方法單獨放出來,以避免貼的代碼太長,你們看不下去。

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()
	}
}

  這個方法就比較重要了,裏邊的有一個for循環,不停的監聽端口來的請求,go c.serve()爲每個來的請求建立一個線程去出去該請求(這裏咱們也看到了Go處理多線程的方便性),這裏的c就是一個conn類型。

func (c *conn) serve() {
	c.remoteAddr = c.rwc.RemoteAddr().String()
	defer func() {
		if err := recover(); err != nil {
			const size = 64 << 10
			buf := make([]byte, size)
			buf = buf[:runtime.Stack(buf, false)]
			c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
		}
		if !c.hijacked() {
			c.close()
			c.setState(c.rwc, StateClosed)
		}
	}()

	if tlsConn, ok := c.rwc.(*tls.Conn); ok {
		if d := c.server.ReadTimeout; d != 0 {
			c.rwc.SetReadDeadline(time.Now().Add(d))
		}
		if d := c.server.WriteTimeout; d != 0 {
			c.rwc.SetWriteDeadline(time.Now().Add(d))
		}
		if err := tlsConn.Handshake(); err != nil {
			c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
			return
		}
		c.tlsState = new(tls.ConnectionState)
		*c.tlsState = tlsConn.ConnectionState()
		if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
			if fn := c.server.TLSNextProto[proto]; fn != nil {
				h := initNPNRequest{tlsConn, serverHandler{c.server}}
				fn(c.server, tlsConn, h)
			}
			return
		}
	}

	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 {
			if err == errTooLarge {
				// Their HTTP client may or may not be
				// able to read this if we're
				// responding to them and hanging up
				// while they're still writing their
				// request.  Undefined behavior.
				io.WriteString(c.rwc, "HTTP/1.1 431 Request Header Fields Too Large\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n431 Request Header Fields Too Large")
				c.closeWriteAndWait()
				return
			}
			if err == io.EOF {
				return // don't reply
			}
			if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
				return // don't reply
			}
			var publicErr string
			if v, ok := err.(badRequestError); ok {
				publicErr = ": " + string(v)
			}
			io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request"+publicErr)
			return
		}

		// 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}
			}
		} else if req.Header.get("Expect") != "" {
			w.sendExpectationFailed()
			return
		}

		// HTTP cannot have multiple simultaneous active requests.[*]
		// Until the server replies to this request, it can't read another,
		// so we might as well run the handler in this goroutine.
		// [*] Not strictly true: HTTP pipelining.  We could let them all process
		// in parallel even if their responses need to be serialized.
		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)
	}
}

  這個方法稍微有點長,其餘的先無論,上邊紅色加粗標註的代碼就是查找相應Handler的部分,這裏用的是一個serverHandler,並調用了它的ServeHTTP函數。

type serverHandler struct {
	srv *Server
}

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爲空時,handler被設置爲DefaultServeMux,就是一開始註冊時使用的路由表。若是一層一層的往上翻,就會看到sh.srv.Handler在ListenAndServe函數中的第二個參數,而這個參數咱們傳入的就是一個nil空值,因此咱們使用的路由表就是這個DefaultServeMux。固然咱們也能夠本身傳入一個自定義的ServMux,可是後續的查找過程都是同樣的,具體的例子能夠參考Go-HTTP。到這裏又出現了跟上邊同樣的狀況,雖然實際用的時候是按照Handler使用的,但其實是一個ServeMux,因此最後調用的ServeHTTP函數,咱們仍是得看ServeMux的具體實現。

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)
}

  具體的實現就是根據傳入的Request,解析出URI來,而後從其內部的map中找到相應的Handler並返回,最後調用ServeHTTP,也就是上邊提到的咱們註冊時傳入的sayHello方法(上邊也提過,ServeHTTP的具體實現,就是調用了sayHello)。

到這裏,整個的大致流程就差很少了,從註冊到請求來時的處理方法查找。

本文所述的過程仍是一個比較表面的過程,很淺顯,可是凡事都是由淺入深的,慢慢來吧,Go語言須要咱們一步一步的去學習。有什麼講解的不對的地方,請各位指出來,方便你們相處進步。

相關文章
相關標籤/搜索