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

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

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

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

Go 語言里面提供了一個完善的 net/http 包,通過http 包可以很方便的就搭建起來一個可以運(yùn)行的Web服務(wù)。同時使用這個包能很簡單地對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 即可啟動http 服務(wù),使用瀏覽器打開 http://localhost:9090 可以查看相應(yīng)輸出。

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

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

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

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

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

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

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

2.2 服務(wù)監(jiān)聽與請求處理過程

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

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

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

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

流程圖如下:

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

3.1 路由注冊代碼

1 調(diào)用 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 使用默認(rèn) 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
    }

    ...
}

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

// :1900 ServeMux 默認(rèn)實(shí)例是 DefaultServeMux
type ServeMux struct {
    mu    sync.RWMutex // 鎖,由于請求涉及到并發(fā)處理,因此這里需要一個鎖機(jī)制
    m     map[string]muxEntry // 路由規(guī)則,一個string對應(yīng)一個mux實(shí)體,這里的string就是注冊的路由表達(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)建一個 Server 對象,并調(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 接收請求

// :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. 創(chuàng)建 *conn
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx) // 3. 新啟一個goroutine,將請求數(shù)據(jù)做為參數(shù)傳給 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. 獲取請求數(shù)據(jù)
        ...
        serverHandler{c.server}.ServeHTTP(w, w.req) // 2. 處理請求 serverHandler, 對應(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 處理請求

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中增加對應(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. 啟動一個for循環(huán),在循環(huán)體中Accept請求
  5. 對每個請求實(shí)例化一個Conn,并且開啟一個goroutine為這個請求進(jìn)行服務(wù)go c.serve()
  6. 讀取每個請求的內(nèi)容w, err := c.readRequest()
  7. 判斷handler是否為空,如果沒有設(shè)置handler(這個例子就沒有設(shè)置handler),handler就設(shè)置為DefaultServeMux
  8. 調(diào)用handler的ServeHttp
  9. 在這個例子中,下面就進(jìn)入到DefaultServeMux.ServeHttp
  10. 根據(jù)request選擇handler,并且進(jìn)入到這個handler的ServeHTTP mux.handler(r).ServeHTTP(w, r)
  11. 選擇handler:
    A 判斷是否有路由能滿足這個request(循環(huán)遍歷ServerMux的muxEntry)
    B 如果有路由滿足,調(diào)用這個路由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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容