一、go中HTTP服務處理流程
超文本傳輸協議(HTTP,Hyper Text Transfer Protocol)是互聯網上應用最為廣泛的一種網絡傳輸協議,所有的WWW文件都必須遵守這個標準。設計HTTP最初的目的是為了提供一種發布和接收HTML頁面的方法。
HTTP 協議從誕生到現在,發展從1.0,1.1到2.0也不斷在進步。除去細節,理解 HTTP 構建的網絡應用只要關注兩個端——客戶端(client)和服務端(server),兩個端的交互來自 client 的 request,以及server端的response。所謂的http服務器,主要在于如何接受 client 的 request,并向client返回response。接收request的過程中,最重要的莫過于路由(router),即實現一個Multiplexer器。
Go中既可以使用內置的 multiplexer - DefaultServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(handler),后者將對request進行處理,同時構建response。
二、構建一個簡單的http服務
代碼示例
package main
import "net/http"
func main() {
// 1.設置路由
// 訪問"/",調用indexHandleFunc函數處理
http.HandleFunc("/", indexHandleFunc)
// 訪問"/home",調用homeHandleFunc函數處理
http.HandleFunc("/home", homeHandleFunc)
// 2.開啟監聽
http.ListenAndServe(":8080", nil)
}
func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("index"))
}
func homeHandleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("home"))
}
運行程序
訪問http://localhost:8080/
,頁面上顯示index
訪問http://localhost:8080/home/
,頁面上顯示home
三、深入net/http包理解go語言http
1、http.Server
HTTP 服務器在 Go 語言中是由 http.Server 結構體對象實現的。參考 http.ListenAndServe() 的實現:
// src/net/http/server.go
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
可見過程是先實例化 Server 對象,再完成 ListenAndServe 。其中 Serve 對象就是表示 HTTP 服務器的對象。其結構如下 :
// src/net/http/server.go
type Server struct {
Addr string // TCP 監聽地址, 留空為:":http"
Handler Handler // 調用的 handler(路由處理器), 設為 nil 表示 http.DefaultServeMux
ReadTimeout time.Duration // 請求超時時長
WriteTimeout time.Duration // 響應超時時長
...
}
實例化了 Server
對象后,調用其 func (srv *Server) ListenAndServe() error
方法。該方法會監聽 srv.Addr
指定的 TCP 地址,并通過 func (srv *Server) Serve(l net.Listener) error
方法接收瀏覽器端連接請求。Serve
方法會接收監聽器 l
收到的每一個連接,并為每一個連接創建一個新的服務協程goroutine。
該 go 協程會讀取請求,然后調用 srv.Handler
處理并響應。srv.Handler
通常會是 nil,這意味著需要調用 http.DefaultServeMux
來處理請求,這個 DefaultServeMux
是默認的路由,我們使用 http.HandleFunc
就是在 http.DefaultServeMux
上注冊方法。
看一下詳細過程(不完全摘錄go源碼):
step1:
初始化一個Server結構體實例后, 執行Server.ListenAndServer函數(其中主要調用net.Listen 和 Server.Serve函數)
func (srv *Server) ListenAndServe() error {
// 調用net.Listen方法啟用監聽
ln, err := net.Listen("tcp", addr)
// 調用Server對象的Serve方法,將監聽對象作為參數傳入
return srv.Serve(ln)
}
step2:
接著進入Server.Serve
部分的代碼,看看詳細的處理過程,可以看到主要是在主goroutine中用for循環阻塞,不斷通過Accept
函數讀取連接請求,然后調用Server.newConn()
函數,創建一個連接實例c
,然后每個連接實例啟動一個新的goroutine去執行處理,從這里也能看出, 如果并發請求很高的時候,會創建出海量的goroutine來并發處理請求。
這一步,調用newConn
函數,會創建一個conn
結構體的連接實例,其中的server
成員變量是Server
實例,即每個connection
實例都保留了指向server
的信息。
func (srv *Server) Serve(l net.Listener) error {
for {
// 循環讀取連接請求
rw, err := l.Accept()
// 創建一個新的連接實例 c := &conn{server: srv, rwc: rw}
c := srv.newConn(rw)
// 啟動一個新的goroutine去執行處理
go c.serve(connCtx)
}
}
step3
接著進入每個connection
處理的goroutine,看具體做了什么工作:先調用connection
的readRequest
方法構造一個response
對象, 然后執行serverHandler{c.server}.ServeHTTP(w, w.req)
進行實際的請求處理。
func (c *conn) serve(ctx context.Context) {
for{
...
// 調用connection的readRequest函數構造一個response對象
w, err := c.readRequest(ctx)
...
req := w.req
...
// serverHandler{c.server}.ServeHTTP(w, w.req)進行實際的請求處理
serverHandler{c.server}.ServeHTTP(w, w.req)
}
}
step4
serverHandler
是一個結構體,有一個成員屬性srv *Server
,結合step 3可以看出,serverHandler{c.server}.ServeHTTP(w, w.req)
就是實例化了一個serverHandler
結構體,然后執行其成員方法 ServeHTTP
,在ServeHTTP
成員方法的定義中,可以看到,主要是調用了初始化server
時的Handler
成員方法,執行handler.ServeHTTP(rw, req)
進行調用。
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
2、http.Handler
// Handler 接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
任何結構體,只要實現了ServeHTTP方法,這個結構就可以稱之為Handler對象。
代碼示例:
package main
import (
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func main() {
// 從go的源碼中可以發現函數的第二個參數是一個Handler對象,a實現了Handler的方法
// a就可以看做是一個Handler對象傳入。
http.ListenAndServe(":8080", &a{})
}
運行程序
訪問http://localhost:8080/
,頁面上顯示hello world
3、http.HandleFunc
http.HandleFunc注冊路由,從源碼中可以看到,http.HandleFunc的功能是向http包的DefaultServeMux加入新的處理路由。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
4、http.DefaultServeMux
DefaultServeMux
是一個 ServeMux
結構體的實例。
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
5、http.ServeMux
ServeMux
是一個結構體,其中有個m
屬性是一個map
類型,存放了不同 URL pattern
的處理Handler
。同時ServeMux
也實現了 ServeHTTP
函數,是 http.Handler
接口的實現。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
// ServeMux實現了Handler接口
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)
}
DefaultServeMux
調用 ServeMux
的 HandleFunc
方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
接著調用 ServeMux
的 Handle
方法向 ServeMux
結構體的 m
字段寫入信息 map[pattern] = muxEntry{h: handler, pattern: pattern}
。
func (mux *ServeMux) Handle(pattern string, handler Handler) {}
四、go語言http的其他實現方式
1、自定義server
創建一個Server對象,可以設置server的其他參數。
package main
import (
"net/http"
"time"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func main() {
// 創建一個新的Server對象
s := http.Server{
// 監聽端口
Addr: "localhost:8080",
// 處理路由
Handler: &a{},
// 設置超時
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
s.ListenAndServe()
}
2、使用自定義的serveMux
package main
import (
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func index(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("index"))
}
func page(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("page"))
}
func main() {
mux := http.NewServeMux()
// 通過Handle將a這個實現Handler接口的結構體,注冊到map表中
mux.Handle("/", &a{})
//通過HandleFunc方法去向mux中handler中注冊處理函數
//其底層仍然是通過mux.Handle方法實現的
mux.HandleFunc("/index", index)
// 直接調用Handle方法去注冊處理函數,
// 這里的http.HandleFunc 是一個函數類型,這里將page轉換為一個Handler接口的實現
mux.Handle("/page", http.HandlerFunc(page))
mux.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("home"))
})
http.ListenAndServe("localhost:8080", mux)
}
五、http的執行過程
1、傳入自己實現Handler接口的對象
http.ListenAndServe("localhost:8080", &a{})
第二個參數傳入的是自己實現Handler接口的對象,http的server會調用這個對象的 ServeHTTP(w, r)
成員方法,實現 request
和 response
示例代碼:
package main
import (
"fmt"
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path)
switch r.URL.Path {
case "/":
w.Write([]byte("<h1>hello world</h1>"))
case "/index":
w.Write([]byte("<h1>index</h1>"))
case "/home":
w.Write([]byte(`<div style="color:red;font-size: 20px;">This is home page</div>`))
default:
w.Write([]byte(`<h1 style="color:red">404 NOT FOUND</h1>`))
}
}
func main() {
http.ListenAndServe("localhost:8080", &a{})
}
2、傳入自定義的ServeMux或默認的DefaultServeMux
http.ListenAndServe("localhost:8080", mux)
第二個參數傳入的是自定義的ServeMux或默認的DefaultServeMux(就是傳入 nil
),http的server會調用ServeMux的 ServeHTTP(w, r)
成員方法,實現 request
和 response
,詳細步驟如下(不完全摘錄go源碼):
step1:
調用 ServeHTTP(w, r){}
實現 request
和 response
,先調用 mux.Handler(r)
方法返回一個Handler對象,調用其 ServeHTTP(w, r)
方法
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
step2:在調用 mux.Handler(r *Request)
方法時,是接著調用
mux.handler(host, r.URL.Path)
方法
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
return mux.handler(host, r.URL.Path)
}
step3:在調用 mux.handler(host, path string)
方法,是接著調用mux.match(path)
方法
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
step4: 在調用 mux.match(path)
方法時,返回 mux.m[path].h
一個Handler對象,其實就是注冊的路由器處理函數,并且和 r.URL.Path
相匹配,返回的 mux.m[path].pattern
就是路由地址 r.URL.Path
。
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
return nil, ""
}
step5:回到step1調用 mux.m[path].h
的 ServeHTTP(w, r)
方法。
參考文章
https://my.oschina.net/u/943306/blog/151293
https://studygolang.com/articles/16179