源代碼閱讀
閱讀源代碼是學習Go語言的必備技能。我們知道,用Go語言,只需要幾行代碼就可以實現一個功能強大的http服務器。背後依賴的是強大的net/http庫,下面我們來了解以下net/http庫源碼的實現原理吧。
net/http庫源碼分析
HTTP
我們知道,HTTP網絡中有客戶端(clinet)和服務端(server),分別用於發送請求(request)和作出迴應(response)。在這一過程中,路由器(router)發揮重要的作用,路由器中由Multiplexer器實現路由選擇,它的目標是找到合適的處理器(handler),並由處理器對消息進行處理、構建response。
- 客戶端發送一條請求的流程如下:
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet
可見,理解http服務的關鍵在於理解Multiplexer和handler。
Go語言中的Multiplexer基於serverMux結構,同時實現了Handler接口。
Handler
Golang中定義如下的接口去聲明具有簽名函數的Handler處理器:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
實現了ServerHTTP方法的結構稱之爲handler對象
ServeMux
ServeMux的源碼如下:
type ServeMux struct {
mu sync.RWMutex //併發機制的鎖
m map[string]muxEntry
hosts bool //是否帶有host信息
}
type muxEntry struct {
explicit bool //表示是否精確匹配
h Handler //該路由表達式對應的handler
pattern string //匹配字符串
}
m是一個map,其中key是url模式,value是muxEntry結構(存儲了具體的url模式和handler)
Server
Server的結構如下:
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
Server結構存儲了服務器處理請求常見的字段,其中Handler字段保留了Handler接口。
創建HTTP服務
創建一個http服務,大致需要兩個步驟:一是註冊路由(即提供url模式和handler函數的映射);二是實例化一個server對象,並開啓對客戶端的監聽。
註冊路由
gohttp的註冊路由代碼:
http.HandleFunc("/", indexHandler)
http.HandleFunc選取DefautServeMux作爲multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
也可以通過NewServeMux方法創建一個ServeMux實例:
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
HandlerFunc是一個函數類型,同時實現了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數,其目的就是爲了讓這個函數也實現ServeHTTP方法:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
開啓監聽
註冊好路由後,啓動web服務還需要開啓服務器監聽:
http.ListenAndServe("127.0.0.1:8000", nil)
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler} //創建Server對象
return server.ListenAndServe() //調用Server對象的ListenAndServe方法
}
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)})
}
處理請求
監聽開啓後,go程序就可以處理客戶端的請求。主要的處理邏輯在ServeHTTP{c.server}.ServeHTTP(w, w.req)中:
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)
}
路由ServeMux的ServeHTTP方法會根據當前請求的信息來查找最匹配的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) //規範化請求的路徑格式,查找最匹配的Handler
h.ServeHTTP(w, r)
}
開發web服務程序
框架選擇
本次選擇的web開發框架是Martini,使用Go的net/http接口開發
代碼
- main.go 基本沿用潘老師博客中的代碼,依次完成綁定端口、解析端口、啓動server
package main
import (
"os"
"cloudgo/service"
flag "github.com/spf13/pflag"
)
const (
//使用端口9000
PORT string = "9000"
)
func main() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = PORT
}
//端口解析
pPort := flag.StringP("port", "p", PORT, "PORT for httpd listening")
flag.Parse()
if len(*pPort) != 0 {
port = *pPort
}
//啓動server
service.NewServer(port)
}
- server.go使用martini框架中的格式,具體定義了啓動server後的操作:
package service
import (
"github.com/go-martini/martini"
)
func NewServer(port string) {
m := martini.Classic()
m.Get("/", func(params martini.Params) string {
return "hello world"
})
m.RunOnAddr(":"+port)
}
服務器測試
- go run 運行main.go
- 在瀏覽器中訪問 localhost:9000
curl測試
curl -v http://localhost: 9000
測試:
ab抗壓測試
./ab -n 1000 -c 100 http://localhost: 9000/
測試:
程序源代碼
參考博客:
Golang構建HTTP服務(一)— net/http庫源碼筆記
【服務計算】開發web服務程序