1. 程式人生 > >Go Web程式設計一: Go Web 基礎

Go Web程式設計一: Go Web 基礎

Go Web 基礎概念與程式碼閱讀

1. Go 搭建簡單的web 服務

Go 語言裡面提供了一個完善的 net/http 包,通過http 包可以很方便的就搭建起來一個可以執行的Web服務。同時使用這個包能很簡單地對Web的路由,靜態檔案,模版,cookie等進行設定和操作。

$GOPATH/src/github.com/ironxu/go_note/web/basic/server.go 原始碼如下:

// http 包建立web 伺服器
package main

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

func sayhelloName(w http.ResponseWriter
, r *http.Request) { r.ParseForm() fmt.Println("path:", r.URL.Path) fmt.Fprintf(w, "hello go") } func main() { http.HandleFunc("/", sayhelloName) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServer: ", err) } }

go run server.go 即可啟動http 服務,使用瀏覽器開啟

http://localhost:9090 可以檢視相應輸出。

2. Go Web 服務講解

本節介紹 Go Web 服務底層實現,包括註冊路由和請求處理

2.1 HTTP 包執行機制

Go 實現Web 服務流程如下

  1. 建立Listen Socket, 監聽指定的埠, 等待客戶端請求到來。
  2. Listen Socket 接受客戶端的請求, 得到Client Socket, 接下來通過Client Socket與客戶端通訊。
  3. 處理客戶端的請求, 首先從Client Socket讀取HTTP請求, 然後交給相應的handler 處理請求, 最後將handler處理完畢的資料, 通過Client Socket
    寫給客戶端。

其中涉及伺服器端的概念:

  • Request:使用者請求的資訊,用來解析使用者的請求資訊,包括post、get、cookie、url等資訊
  • Conn:使用者的每次請求連結
  • Handler:處理請求和生成返回資訊的處理邏輯
  • Response:伺服器需要反饋給客戶端的資訊

2.2 服務監聽與請求處理過程

Go是通過一個ListenAndServe 監聽服務,底層處理:初始化一個server物件,然後呼叫 net.Listen("tcp", addr),監控我們設定的埠。

監控埠之後,呼叫 srv.Serve(net.Listener) 函式,處理接收客戶端的請求資訊。首先通過Listener 接收請求,其次建立一個Conn,最後單獨開了一個goroutine,把這個請求的資料當做引數扔給這個conn去服務。go c.serve() 使用者的每一次請求都是在一個新的goroutine去服務,相互不影響。

分配相應的函式處理請求: conn 首先會解析 request:c.readRequest(), 然後獲取相應的handler:handler := c.server.Handler,這個是呼叫函式ListenAndServe 時候的第二個引數,例子傳遞的是nil,也就是為空,那麼預設獲取handler = DefaultServeMuxDefaultServeMux 是一個路由器,它用來匹配url跳轉到其相應的handle函式

呼叫 http.HandleFunc("/", sayhelloName) 作用是註冊了請求/的路由規則,將url 和handle 函式註冊到DefaultServeMux 變數,最後呼叫DefaultServeMuxServeHTTP 方法,這個方法內部呼叫handle 函式。

流程圖如下:

3. Web 服務程式碼實現

3.1 路由註冊程式碼

1 呼叫 http.HandleFunc(“/”, sayhelloName) 註冊路由

// /usr/local/go/src/net/http/server.go:2081
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler) // DefaultServeMux 型別為 *ServeMux
}

2 使用預設 ServeMux

// :2027
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

3 註冊路由策略 DefaultServeMux

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
    }

    ...
}

涉及資料結構

// :1900 ServeMux 預設例項是 DefaultServeMux
type ServeMux struct {
    mu    sync.RWMutex // 鎖,由於請求涉及到併發處理,因此這裡需要一個鎖機制
    m     map[string]muxEntry // 路由規則,一個string對應一個mux實體,這裡的string就是註冊的路由表示式
    hosts bool // 是否在任意的規則中帶有host資訊
}

type muxEntry struct {
    explicit bool
    h        Handler // 路由處理器
    pattern  string  // url 匹配正則
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

3.2 服務監聽程式碼

1 呼叫 err := http.ListenAndServe(“:9090”, nil) 監聽埠

// /usr/local/go/src/net/http/server.go:2349
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler} // handler 為空
    return server.ListenAndServe()
}

建立一個 Server 物件,並呼叫 Server 的 ListenAndServe()

2 監聽TCP埠

// :2210
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)})
}

3 接收請求

// :2256
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()

    ...

    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept() // 1. Listener 接收請求
        if e != nil {
            ...
        }
        tempDelay = 0
        c := srv.newConn(rw) // 2. 建立 *conn
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) // 3. 新啟一個goroutine,將請求資料做為引數傳給 conn,由這個新的goroutine 來處理這次請求
    }
}

4 goroutine 處理請求

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    ...
    // HTTP/1.x from here on.

    c.r = &connReader{r: c.rwc}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    ctx, cancelCtx := context.WithCancel(ctx)
    defer cancelCtx()

    for {
        w, err := c.readRequest(ctx) // 1. 獲取請求資料
        ...
        serverHandler{c.server}.ServeHTTP(w, w.req) // 2. 處理請求 serverHandler, 對應下面第5步
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest() // 3. 返回響應結果
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
    }
}

5 處理請求

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux // ServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

5.1 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) // HandlerFunc, Handler
    h.ServeHTTP(w, r)
}

5.2 執行處理

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

涉及的資料型別

type Server struct {
    Addr         string        // TCP address to listen on, ":http" if empty
    Handler      Handler       // handler to invoke, http.DefaultServeMux if nil
    ReadTimeout  time.Duration // maximum duration before timing out read of the request
    WriteTimeout time.Duration // maximum duration before timing out write of the response
    ...
}

type conn struct {
    server *Server // server is the server on which the connection arrived.
    rwc net.Conn // rwc is the underlying network connection. It is usually of type *net.TCPConn or *tls.Conn.
    remoteAddr string // This is the value of a Handler's (*Request).RemoteAddr.
    mu sync.Mutex // mu guards hijackedv, use of bufr, (*response).closeNotifyCh.
    ...
}

type serverHandler struct {
    srv *Server
}

3.3 Go 程式碼的執行流程

呼叫Http.HandleFunc,按順序做了幾件事:

  1. 呼叫了DefaultServeMux的HandleFunc
  2. 呼叫了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則

呼叫http.ListenAndServe(":9090", nil),按順序做了幾件事情:

  1. 例項化Server
  2. 呼叫Server的ListenAndServe()
  3. 呼叫net.Listen(“tcp”, addr)監聽埠
  4. 啟動一個for迴圈,在迴圈體中Accept請求
  5. 對每個請求例項化一個Conn,並且開啟一個goroutine為這個請求進行服務go c.serve()
  6. 讀取每個請求的內容w, err := c.readRequest()
  7. 判斷handler是否為空,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux
  8. 呼叫handler的ServeHttp
  9. 在這個例子中,下面就進入到DefaultServeMux.ServeHttp
  10. 根據request選擇handler,並且進入到這個handler的ServeHTTP mux.handler(r).ServeHTTP(w, r)
  11. 選擇handler:
    A 判斷是否有路由能滿足這個request(迴圈遍歷ServerMux的muxEntry)
    B 如果有路由滿足,呼叫這個路由handler的ServeHttp
    C 如果沒有路由滿足,呼叫NotFoundHandler的ServeHttp

4. 自定義路由實現

定義的型別實現ServeHTTP 方法,即可實現自定義路由

package main

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

type MyMux struct {}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/" {
        sayhelloName(w, r)
        return
    }

    http.NotFound(w, r)
    return
}


func sayhelloName(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    fmt.Println("path:", r.URL.Path)
    fmt.Fprintf(w, "hello go")
}

func main() {
    mux := &MyMux{}
    err := http.ListenAndServe(":9090", mux)
    if err != nil {
        log.Fatal("ListenAndServer: ", err)
    }
}

參考

可以關注我的微博瞭解更多資訊: @剛剛小碼農