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ù)流程如下
- 創(chuàng)建
Listen Socket
, 監(jiān)聽指定的端口, 等待客戶端請(qǐng)求到來(lái)。 -
Listen Socket
接受客戶端的請(qǐng)求, 得到Client Socket
, 接下來(lái)通過(guò)Client Socket
與客戶端通信。 - 處理客戶端的請(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 = DefaultServeMux
。DefaultServeMux
是一個(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)用DefaultServeMux
的ServeHTTP
方法,這個(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
,按順序做了幾件事:
- 調(diào)用了DefaultServeMux的HandleFunc
- 調(diào)用了DefaultServeMux的Handle
- 往DefaultServeMux的map[string]muxEntry中增加對(duì)應(yīng)的handler和路由規(guī)則
調(diào)用http.ListenAndServe(":9090", nil)
,按順序做了幾件事情:
- 實(shí)例化Server
- 調(diào)用Server的ListenAndServe()
- 調(diào)用net.Listen("tcp", addr)監(jiān)聽端口
- 啟動(dòng)一個(gè)for循環(huán),在循環(huán)體中Accept請(qǐng)求
- 對(duì)每個(gè)請(qǐng)求實(shí)例化一個(gè)Conn,并且開啟一個(gè)goroutine為這個(gè)請(qǐng)求進(jìn)行服務(wù)go c.serve()
- 讀取每個(gè)請(qǐng)求的內(nèi)容w, err := c.readRequest()
- 判斷handler是否為空,如果沒有設(shè)置handler(這個(gè)例子就沒有設(shè)置handler),handler就設(shè)置為DefaultServeMux
- 調(diào)用handler的ServeHttp
- 在這個(gè)例子中,下面就進(jìn)入到DefaultServeMux.ServeHttp
- 根據(jù)request選擇handler,并且進(jìn)入到這個(gè)handler的ServeHTTP mux.handler(r).ServeHTTP(w, r)
- 選擇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)