1. 程式人生 > >GO語言啟動web服務的實現方式

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功能,不過也可以作為一個實現路由的例子。