golang 構建HTTP服務
一個go最簡單的Http伺服器程式
package main
import (
"fmt"
"net/http"
)
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello world")
}
func main() {
http.HandleFunc("/", IndexHandler)
http.ListenAndServe("127.0.0.0:8000", nil)
}
HTTP除去細節,理解HTTP構建的網路應用只要關注兩個端--客戶端(client)和服務端(server),兩個端的互動來自client的request,以及server端的response。所謂的http伺服器,主要在於如何接受client的request,並向client返回response。
接收request的過程中,最重要的莫過於路由(router),即實現一個Multiplexer器。Go中既可以使用內建的mutilplexer--DefaultServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函式(hander),後者將對request進行處理,同時構建response。
簡單總結就是這個流程:
因此,理解go中的http服務,,最重要的就是要理解Multiplexer和hander,Golang中的Multiplexer基於ServerMux結構,同時也實現了Handler介面。
·hander函式:具有func(w http.ResponseWriter, r *http.Requests)簽名的函式
·handler處理器(函式):經過HanderFunc結構包裝的handler函式,它實現了ServeHTTP介面方法的函式。呼叫handler處理器的ServeHTTP方法時,即呼叫handler函式本身。
·handler物件:實現了Hander介面ServeHTTP方法的結構。
Golang的http處理流程可以用下面一張圖表示,後面內容是針對圖進行說明:
Handler
Golang沒有繼承,類多型的方法可以通過介面實現。所謂介面則是定義聲明瞭函式簽名,任何結構只要實現了與介面函式簽名相同的方法,就等同於實現了介面。go的http服務都是基於handler進行處理的。
任何結構體,只要實現了ServeHTTP方法,這個結構就可以稱之為handler物件。ServeMux會使用handler並呼叫其ServeHTTP方法處理請求並返回響應。type Handler interface { ServeHTTP(ResponseWriter, *Request) }
ServeMux
瞭解了Handler之後,再看ServeMux。ServeMux原始碼很簡單:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
ServeMux結構中最重要的欄位為m,這是一個map,key是一些url模式,value是一個muxEntry結構,後者裡定義儲存了具體的url模式和handler。當然,所謂的ServeMux也實現了ServeHTTP介面,也算是一個handler,不過ServeMux的ServeHTTP方法不是用來處理request和respone,而是用來找到路由註冊的handler。
Server
除了ServeMux和Handler,還有一個結構Server需要了解。從http.ListenAndServe的原始碼可以看出,它建立了一個server物件,並呼叫server物件的ListenAndServe方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
檢視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欄位也保留Hander介面。如果Server介面沒有提供Handler結構物件,那麼會使用DefaultServeMux做Multiplexer。建立HTTP服務
建立一個http服務,大致需要經歷兩個過程,首先需要註冊路由,即提供url模式和handler函式的對映,其次就是例項化一個server物件,並開啟對客戶端的監聽。
http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
或者:
server := &Server{Addr: addr, Handler: handler}
server.ListenAndServe()
http註冊路由
net/http包暴露的註冊路由的api很簡單,http.HandleFunc選取了DefaultServeMux作為multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
實際上,DefaultServeMux是ServeMux的一個例項。當然http包也提供了NewServeMux方法建立一個ServeMux例項,預設則建立一個DefaultServeMux:// 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
注意,go建立例項的過程中,也可以使用指標方式,即type Server struct{}
server := Server{}
和下面的一樣都可以建立Server的例項
var DefaultServer Server
var server = &DefalutServer
因此DefaultServeMux的HandleFunc(pattern,handler)方法實際是定義在ServeMux下的:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
上述程式碼中,HandlerFunc是一個函式型別。同時實現了Handler介面的ServeHTTP方法。使用HandlerFunc型別包裝一下路由定義的indexHandler函式,其目的就是為了讓這個函式也實現ServeHTTP方法,即轉變成一個handler處理器(函式)。type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
一旦這樣做了,就意味著我們的indexHandler函式也有了ServeHTTP方法。此外,ServeMux的Handle方法,將會對pattern和handler函式做一個map對映:
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
path := pattern
if pattern[0] != '/' {
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}
由此可見,Handle函式的主要目的在於把handler和pattern模式繫結到map[string]muxEntry的map上,其中muxEntry儲存了更多pattern和handler的資訊。Server的m欄位就是map[string]muxEntry這樣一個map。此時,pattern和handler的路由註冊完成。
開啟監聽
註冊好路由之後,啟動web服務還需要開啟伺服器監聽。http的ListenAndServer方法中可以看到建立了一個Server物件,並呼叫了Server物件的同名方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return 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)})
}
Server的ListenAndServer方法中,會初始化監聽地址Addr,同時呼叫Listen方法設定監聽。最後將監聽的TCP物件傳入Serve方法: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()
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
處理請求監聽開啟之後,一旦客戶端請求到底,go就開啟一個協程處理請求,主要邏輯都在server方法之中。
serve方法比較長,其主要職能就是,建立一個上下文物件,然後呼叫Listener的Accept方法用來獲取連線資料並使用newConn方法建立連線物件。最後使用goroutein協程的方式處理連線請求。因此每一個連線都開啟了一個協程,請求的上下文都不同,同時又保證了go的高併發。serve也是一個長長的方法:
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if !c.hijacked() {
c.close()
c.setState(c.rwc, StateClosed)
}
}()
...
for {
w, err := c.readRequest(ctx)
if c.r.remain != c.server.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive)
}
...
}
...
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle)
}
}
儘管serve很長,裡面的結構和邏輯還是很清晰的,使用defer定義了函式退出時,連線關閉相關的處理。然後就是讀取連線的網路資料,並處理讀取完畢時候的狀態。接下來就是呼叫serverHandler{c.server}.ServeHTTP(w,w.req)方法處理請求了。最後就是請求處理完畢的邏輯。serverHandler是一個重要的結構,它僅有一個欄位,即Server結構,同時它也實現了Hander介面方法ServeHTTP,同時它也實現了Handler介面方法ServeHTTP,並在該介面方法中做了一個重要的事情,初始化multiplexer路由多路複用器。如果server物件沒有制定Handler,則使用預設的DefaultServeMux作為路由Multiplexer。並呼叫初始化Handler的ServeHTTP方法。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)
}
這裡DefaultServeMux的ServeHTTP方法其實也是定義在ServeMux結構中的,相關程式碼如下:func (mux *ServeMux) (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)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.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
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
mux的ServeHTTP方法通過呼叫其Handler方法尋找註冊到路由上的handler函式,並呼叫該函式的ServeHTTP方法。 mux的Handler方法對URL簡單的處理,然後呼叫handler方法,後者會建立一個鎖,同時呼叫match方法返回一個handler和pattern。
在match方法中,mux的m欄位是map[string]muxEntry,後者儲存了pattern和handler處理器函式,因此通過迭代m尋找出註冊路由的pattern模式與實際url匹配的handler函式並返回。
返回的結構一直傳遞到mux的ServeHTTP方法,接下來呼叫handler函式的ServeHTTP方法,即IndexHandler函式,然後把reponse寫到http.RequestWirter物件返回給客戶端。
上述函式執行結束即serverHandler{c.server}.ServeHTTP(w, w.req)執行結束。接下來就是對請求處理完畢之後上希望和連線斷開的相關邏輯。
至此,Golang中一個完整的http服務介紹完畢,包括註冊路由,開啟監聽,處理連線,路由處理函式。