Go web開發初探


2017年的第一篇博客,也是第一次寫博客,寫的不好,請各位見諒。

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

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

這里先提一句,本文中展示的源代碼都是在Go安裝目錄下src/net/http/server.go文件中(除了自己寫的實例程序),如果各位還想理解的更詳細,可以自己再去研究一下源代碼。

《Go web編程》3.4節中提到http有兩個核心功能:Conn, ServeMux , 但是我覺得還有一個Handler接口也挺重要的,后邊咱們提到了再說。

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

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!” (這里為了簡便,就沒往網頁里寫入信息)

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

首先是注冊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語言需要我們一步一步的去學習。有什么講解的不對的地方,請各位指出來,方便大家相處進步。

 

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM