GO語言啟動web服務的實現方式
1.首先看一個簡單的web服務
package main
import (
"io"
"net/http"
"log"
)
// hello world, the web server
func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func main() {
http.HandleFunc("/hello", HelloServer)
err := http.ListenAndServe(":12345" , nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
以上程式碼只依賴go的標準包,沒有依賴任何第三方包.
在瀏覽器中輸入\”http://localhost:12345/hello\”就可以看到“Hello world!”.
我們看到上面的程式碼,要編寫一個web伺服器很簡單,只要呼叫http包的兩個函式就可以
了.
2.分析如何啟動這一個服務有兩個方面,一個是脫離語言的Web的工作方式,另一個是Go語言的底層實現;Web的工作方式這裡不進行討論,這裡討論Go的實現。
Go的http包的執行流程:
(1) 建立Listen Socket, 監聽指定的埠, 等待客戶端請求到來。
(2) Listen Socket接受客戶端的請求, 得到Client Socket, 接下來通過Client Socket與客戶端通訊。
(3) 處理客戶端的請求, 首先從Client Socket讀取HTTP請求的協議頭, 如果是POST方法,還可能要讀取客戶端提交的資料,然後交給相應的handler處理請求,handler處理完畢準備好客戶端需要的資料,通過Client Socket寫給客戶端。
這整個的過程裡面我們只要瞭解清楚下面三個問題,也就知道Go是如何讓Web執行起來了
(1)如何監聽埠?
(2)如何接收客戶端請求?
(3)如何分配handler?
其實這三個功能在http.ListenAndServe(“:12345”, nil)這一個函式中就實現了,通過檢視Go的原始碼,這個函式實現如下:
// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections. Handler is typically nil,
// in which case the DefaultServeMux is used.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler : handler}
return server.ListenAndServe()
}
生成了一個Server物件,接著呼叫Server物件的ListenAndServe()方法
// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections. If
// srv.Addr is blank, ":http" is used.
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),也就是底層用TCP協議搭建了一個服務,然後監控我們設定的埠。(TCP協議我基本不知道,而且似乎不知道對本問題也沒有什麼影響)
再看Serve方法:
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
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, err := srv.newConn(rw)
if err != nil {
continue
}
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve()
}
}
該程式碼啟動一個for的死迴圈,首先通過Listener接收請求,其次建立一個Conn,最後單獨開了一個goroutine,把這個請求的資料當做引數扔給這個conn去服務:go c.serve()。這個就是高併發的體現了,使用者的每一次請求都是在一個新的goroutine去服務,相互不影響。for迴圈中的其他程式碼主要是實現了一旦接收請求出錯,隔一段時間之後繼續進入迴圈,不會因為一次出錯導致程式退出。
那麼如何具體分配到相應的函式來處理請求呢?conn首先會解析request:c.readRequest(),然後獲取相應的handler:handler := c.server.Handler,也就是我們剛才在呼叫函式ListenAndServe時候的第二個引數,我們前面例子傳遞的是nil,也就是為空,那麼預設handler=DefaultServeMux,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配url跳轉到其相應的handle函式。那麼這個我們有設定過嗎?有,我們呼叫的程式碼裡面第一句呼叫了http.HandleFunc(“/”, HelloServer),這個作用就是註冊了請求/的路由規則,當請求url為”/”,路由就會轉到函式HelloServer,DefaultServeMux會呼叫ServeHTTP方法,這個方法內部其實就是呼叫HelloServer本身,最後通過寫入response的資訊反饋到客戶端。注意,這只是一個路由器,用來分配handler,下面我們來看這個路由器是如何實現的。
3.路由器的實現
在這個例子中,http.HandleFunc(“/hello”, HelloServer) 這行程式碼註冊了指定路徑下的處理函式。
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
}
// Objects implementing the Handler interface can be
// registered to serve a particular path or subtree
// in the HTTP server.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished
// and that the HTTP server can move on to the next request on
// the connection.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and hangs up the connection.
//
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
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)
}
map[string]muxEntry,儲存了路徑名對應的方法,一個路徑對應一個方法;方法實體muxEntry中最關鍵的方法屬性Handler,
下面是Handler的定義,其中有一個方法:ServeHTTP(ResponseWriter,*Request)。這個方法就是我們傳入的sayhello方法,這個方法是我們真正要做的業務邏輯方法,他的引數只有兩個,一個是使用者的請求資訊,一個用來寫入我們處理完業務邏輯之後的結果。通過func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request),我們可以看到ServeMux也實現了Handler介面,但ServeMux是一個特殊的Handler介面,他並不處理實際的業務邏輯,而是在他內部儲存的Handler字典中根據路由資訊找到合適的Handler來處理。到這裡就解決了我們之前提的問題:c.serve()就是呼叫了ServeMux的ServeHTTP方法找到我們的HelloServer方法並執行。
需要說明的是,Go語言的預設路由是DefaultServeMux,當我們傳遞的是nil時,就會使用這個路由規則。這裡有人會好奇,這個函式的第二個引數應當是一個handler,但我們傳遞的只是一個普通的函式,又如何能夠識別?請看下面的程式碼:
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
該方法呼叫了ServeMux(DefaultServeMux就是ServeMux的一個例項)的HandleFunc方法,這個方法做了一個強制型別轉換:HandlerFunc(handler),把我們傳入的HelloServer方法轉換成HandlerFunc(注意HandleFunc和HandlerFunc不同,後者比前者多了一個r字母)。HandlerFunc實現了Handler介面,就可以被ServeMux找到並呼叫它的ServeHTTP方法,實際就是呼叫了HelloServer方法。
從結果來說,這個設計讓我們實現一個Web伺服器只需要一行程式碼,十分方便。但實現方式的確有點繞人。
4.自定義路由
回到最開始的ListenAndServe()函式,這裡的第二個引數是可以用來配置外部路由的,只要這個外部路由實現了Handler介面。
下面是一個《Go_web程式設計》裡面的例子:
package main
import (
"fmt"
"net/http"
)
type MyMux struct{}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
hello(w, r)
return
}
http.NotFound(w, r)
return
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
mux := &MyMux{}
http.ListenAndServe(":3030", mux)
}
在這個路由中,你訪問這個站點的所有路徑都會呼叫hello函式,也就是寫死了,沒有實現分配handler功能,不過也可以作為一個實現路由的例子。