Go Web編程一: Go Web 基礎(chǔ)

原文鏈接 http://ironxu.com/779

Go Web 基礎(chǔ)概念與代碼閱讀

1. Go 搭建簡(jiǎn)單的web 服務(wù)

Go 語(yǔ)言里面提供了一個(gè)完善的 net/http 包,通過(guò)http 包可以很方便的就搭建起來(lái)一個(gè)可以運(yùn)行的Web服務(wù)。同時(shí)使用這個(gè)包能很簡(jiǎn)單地對(duì)Web的路由,靜態(tài)文件,模版,cookie等進(jìn)行設(shè)置和操作。

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

// http 包建立web 服務(wù)器
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 即可啟動(dòng)http 服務(wù),使用瀏覽器打開 http://localhost:9090 可以查看相應(yīng)輸出。

2. Go Web 服務(wù)講解

本節(jié)介紹 Go Web 服務(wù)底層實(shí)現(xiàn),包括注冊(cè)路由和請(qǐng)求處理

2.1 HTTP 包運(yùn)行機(jī)制

Go 實(shí)現(xiàn)Web 服務(wù)流程如下

  1. 創(chuàng)建Listen Socket, 監(jiān)聽指定的端口, 等待客戶端請(qǐng)求到來(lái)。
  2. Listen Socket 接受客戶端的請(qǐng)求, 得到Client Socket, 接下來(lái)通過(guò)Client Socket與客戶端通信。
  3. 處理客戶端的請(qǐng)求, 首先從Client Socket讀取HTTP請(qǐng)求, 然后交給相應(yīng)的handler 處理請(qǐng)求, 最后將handler處理完畢的數(shù)據(jù), 通過(guò)Client Socket寫給客戶端。

其中涉及服務(wù)器端的概念:

  • Request:用戶請(qǐng)求的信息,用來(lái)解析用戶的請(qǐng)求信息,包括post、get、cookie、url等信息
  • Conn:用戶的每次請(qǐng)求鏈接
  • Handler:處理請(qǐng)求和生成返回信息的處理邏輯
  • Response:服務(wù)器需要反饋給客戶端的信息

2.2 服務(wù)監(jiān)聽與請(qǐng)求處理過(guò)程

Go是通過(guò)一個(gè)ListenAndServe 監(jiān)聽服務(wù),底層處理:初始化一個(gè)server對(duì)象,然后調(diào)用 net.Listen("tcp", addr),監(jiān)控我們?cè)O(shè)置的端口。

監(jiān)控端口之后,調(diào)用 srv.Serve(net.Listener) 函數(shù),處理接收客戶端的請(qǐng)求信息。首先通過(guò)Listener 接收請(qǐng)求,其次創(chuàng)建一個(gè)Conn,最后單獨(dú)開了一個(gè)goroutine,把這個(gè)請(qǐng)求的數(shù)據(jù)當(dāng)做參數(shù)扔給這個(gè)conn去服務(wù)。go c.serve() 用戶的每一次請(qǐng)求都是在一個(gè)新的goroutine去服務(wù),相互不影響。

分配相應(yīng)的函數(shù)處理請(qǐng)求: conn 首先會(huì)解析 request:c.readRequest(), 然后獲取相應(yīng)的handler:handler := c.server.Handler,這個(gè)是調(diào)用函數(shù)ListenAndServe 時(shí)候的第二個(gè)參數(shù),例子傳遞的是nil,也就是為空,那么默認(rèn)獲取handler = DefaultServeMuxDefaultServeMux 是一個(gè)路由器,它用來(lái)匹配url跳轉(zhuǎn)到其相應(yīng)的handle函數(shù)

調(diào)用 http.HandleFunc("/", sayhelloName) 作用是注冊(cè)了請(qǐng)求/的路由規(guī)則,將url 和handle 函數(shù)注冊(cè)到DefaultServeMux 變量,最后調(diào)用DefaultServeMuxServeHTTP 方法,這個(gè)方法內(nèi)部調(diào)用handle 函數(shù)。

流程圖如下:

3. Web 服務(wù)代碼實(shí)現(xiàn)

3.1 路由注冊(cè)代碼

1 調(diào)用 http.HandleFunc("/", sayhelloName) 注冊(cè)路由

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

2 使用默認(rèn) ServeMux

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

3 注冊(cè)路由策略 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
    }

    ...
}

涉及數(shù)據(jù)結(jié)構(gòu)

// :1900 ServeMux 默認(rèn)實(shí)例是 DefaultServeMux
type ServeMux struct {
    mu    sync.RWMutex // 鎖,由于請(qǐng)求涉及到并發(fā)處理,因此這里需要一個(gè)鎖機(jī)制
    m     map[string]muxEntry // 路由規(guī)則,一個(gè)string對(duì)應(yīng)一個(gè)mux實(shí)體,這里的string就是注冊(cè)的路由表達(dá)式
    hosts bool // 是否在任意的規(guī)則中帶有host信息
}

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

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

3.2 服務(wù)監(jiān)聽代碼

1 調(diào)用 err := http.ListenAndServe(":9090", nil) 監(jiān)聽端口

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

創(chuàng)建一個(gè) Server 對(duì)象,并調(diào)用 Server 的 ListenAndServe()

2 監(jiān)聽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 接收請(qǐng)求

// :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 接收請(qǐng)求
        if e != nil {
            ...
        }
        tempDelay = 0
        c := srv.newConn(rw) // 2. 創(chuàng)建 *conn
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) // 3. 新啟一個(gè)goroutine,將請(qǐng)求數(shù)據(jù)做為參數(shù)傳給 conn,由這個(gè)新的goroutine 來(lái)處理這次請(qǐng)求
    }
}

4 goroutine 處理請(qǐng)求

// 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. 獲取請(qǐng)求數(shù)據(jù)
        ...
        serverHandler{c.server}.ServeHTTP(w, w.req) // 2. 處理請(qǐng)求 serverHandler, 對(duì)應(yīng)下面第5步
        w.cancelCtx()
        if c.hijacked() {
            return
        }
        w.finishRequest() // 3. 返回響應(yīng)結(jié)果
        if !w.shouldReuseConnection() {
            if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
                c.closeWriteAndWait()
            }
            return
        }
        c.setState(c.rwc, StateIdle)
    }
}

5 處理請(qǐng)求

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 執(zhí)行處理

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

涉及的數(shù)據(jù)類型

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 代碼的執(zhí)行流程

調(diào)用Http.HandleFunc,按順序做了幾件事:

  1. 調(diào)用了DefaultServeMux的HandleFunc
  2. 調(diào)用了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增加對(duì)應(yīng)的handler和路由規(guī)則

調(diào)用http.ListenAndServe(":9090", nil),按順序做了幾件事情:

  1. 實(shí)例化Server
  2. 調(diào)用Server的ListenAndServe()
  3. 調(diào)用net.Listen("tcp", addr)監(jiān)聽端口
  4. 啟動(dòng)一個(gè)for循環(huán),在循環(huán)體中Accept請(qǐng)求
  5. 對(duì)每個(gè)請(qǐng)求實(shí)例化一個(gè)Conn,并且開啟一個(gè)goroutine為這個(gè)請(qǐng)求進(jìn)行服務(wù)go c.serve()
  6. 讀取每個(gè)請(qǐng)求的內(nèi)容w, err := c.readRequest()
  7. 判斷handler是否為空,如果沒有設(shè)置handler(這個(gè)例子就沒有設(shè)置handler),handler就設(shè)置為DefaultServeMux
  8. 調(diào)用handler的ServeHttp
  9. 在這個(gè)例子中,下面就進(jìn)入到DefaultServeMux.ServeHttp
  10. 根據(jù)request選擇handler,并且進(jìn)入到這個(gè)handler的ServeHTTP mux.handler(r).ServeHTTP(w, r)
  11. 選擇handler:
    A 判斷是否有路由能滿足這個(gè)request(循環(huán)遍歷ServerMux的muxEntry)
    B 如果有路由滿足,調(diào)用這個(gè)路由handler的ServeHttp
    C 如果沒有路由滿足,調(diào)用NotFoundHandler的ServeHttp

4. 自定義路由實(shí)現(xiàn)

定義的類型實(shí)現(xiàn)ServeHTTP 方法,即可實(shí)現(xiàn)自定義路由

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

參考

可以關(guān)注我的微博了解更多信息: @剛剛小碼農(nóng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,948評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,353評(píng)論 25 708
  • package net/http是Go語(yǔ)言的主要應(yīng)用場(chǎng)景之一web應(yīng)用的基礎(chǔ),從中可以學(xué)習(xí)到大量前文提到的io,以...
    納達(dá)丶無(wú)忌閱讀 4,602評(píng)論 2 12
  • 在Go中使用及其簡(jiǎn)單的代碼即可開啟一個(gè)web服務(wù)。如下: 在使用ListenAndServe這個(gè)方法時(shí),系統(tǒng)就會(huì)給...
    fou7閱讀 2,391評(píng)論 0 9
  • 今天要講一個(gè)真實(shí)的故事,故事的主角是兩個(gè)女人,她們是親姐妹,要不然也不會(huì)在一起,而選擇在一起的代價(jià)只有妹妹知道,故...
    正齊讀道閱讀 642評(píng)論 0 1