簡易HTTP服務(wù)器的實現(xiàn)
先看一個使用net/http
包實現(xiàn)的簡單服務(wù)器程序示例绰垂。
package main
import (
"io"
"log"
"net/http"
)
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world!")
}
func main() {
// 設(shè)置路由
http.Handle("/", &myHandler{})
http.HandleFunc("/hello", sayHello)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
運行程序,打開瀏覽器劲装,訪問http://localhost:8080
,可以看到瀏覽器上顯示I'm home page
绒怨,訪問http://localhost:8080/hello
谦疾,可以看到hello world!
。就是如此簡單餐蔬,但是程序背后做了哪些事呢?
分析
http.Handle
主要是設(shè)置路由規(guī)則仗考,先看它的具體實現(xiàn)吧:
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
可以看到兩個不認識的東西词爬,Handler
和DefaultServeMux
。繼續(xù)查看定義:
// Handler是一個接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ServeMux是一個自定義類型
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry //模式字符串與處理器的映射關(guān)系
hosts bool // whether any patterns contain hostnames
}
// muxEntry 是一個自定義類型锅锨,包括一個模式字符串和對應(yīng)的Handler接口
type muxEntry struct {
h Handler
pattern string
}
var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
DefaultServeMux
是*ServeMux
類型恋沃,指向defaultServeMux
這個內(nèi)部變量。ServeMux
結(jié)構(gòu)體的含義是路由器囊咏,它的成員m
比較重要,存儲的是模式字符串與處理器的映射關(guān)系霜第。*ServeMux
實現(xiàn)了Handle
函數(shù)户辞,主要用途就是填充成員m
的,具體代碼如下:
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 錯誤判斷刃榨,模式字符串為空、處理起handler為空喇澡、對應(yīng)的模式字符串的處理器已經(jīng)存在
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
// 如果沒有實例化,就make一下
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
if pattern[0] != '/' {
mux.hosts = true
}
}
可以看到前面有三個錯誤判斷读存,這個不影響理解呕屎,重要的是mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
,將傳遞進來的處理器handler
和模式字符串pattern
組合成muxEntry
結(jié)構(gòu)體秀睛,使用map
結(jié)構(gòu)綁定pattern
和muxEntry
之間的關(guān)系。
綜上所述椭迎,http.Handle
干了什么呢田盈?主要就是操作DefaultServeMux
這個結(jié)構(gòu)體變量,填充它的成員m
(map[string]muxEntry
類型)允瞧,建立路由規(guī)則。通俗一點痹升,就是為每個url
模式串注冊了一個回調(diào)函數(shù)(go語言中稱之為處理器Handler)畦韭!
接下來的http.ListenAndServe
函數(shù),具體實現(xiàn)如下:
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
這一段代碼不追究細節(jié)還是很好理解的据过,第一個參數(shù)是端口號,第二個參數(shù)是具體的處理器(該示例中傳入nil
),這個函數(shù)就是先創(chuàng)建一個具體的Server
對象西饵,然后調(diào)用ListenAndServe()
,不看具體實現(xiàn)期虾,也能猜到,就是先listen
(監(jiān)聽)镶苞,再建立一個事件循環(huán),監(jiān)聽到了連接事件或者讀寫事件發(fā)生壕鹉,就啟動一個協(xié)程進行處理聋涨。
問題是,傳遞進去的handler
的ServeHTTP
方法再什么時候調(diào)用的呢牍白?繼續(xù)翻源碼,server.ListenAndServe()
主要分為兩個步驟狸涌,1.處理新連接最岗,即調(diào)用Accpet
函數(shù)。2.處理其他事件仑性,代碼中主要體現(xiàn)在go c.serve(ctx)
。而在c.serve
函數(shù)中找到了這句話:serverHandler{c.server}.ServeHTTP(w, w.req)
诊杆。serverHandler
類型其實就是重新包裝了*Server
類型歼捐,如下:
// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// 重點, 取出內(nèi)部的Handler接口變量
handler := sh.srv.Handler
// 如果是空晨汹,就是用默認的Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
// 調(diào)用 ServeHTTP 方法豹储!
handler.ServeHTTP(rw, req)
}
serverHandler
的ServeHTTP
方法中調(diào)用了它的成員Handler
(最開始由你傳遞進來的)的ServeHTTP
方法(這就是你自定義的處理器實現(xiàn)的ServeHTTP
的作用所在了)。本示例中的傳入的Handler
是nil
淘这,所以最后使用的是DefaultServeMux
的ServeHTTP
方法剥扣,而DefaultServeMux
是*ServeMux
類型,它的ServeHTTP
方法實現(xiàn)如下:
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}
// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
}
mux.Handler
函數(shù)中調(diào)用了mux.match
函數(shù)铝穷,mux.match
函數(shù)主要是在mux.m
這個之前注冊好的map
(字典)中查找url
的對應(yīng)處理器。根據(jù)之前定義的路由規(guī)則找到了對應(yīng)的處理器之后曙聂,就調(diào)用處理器的ServeHTTP
方法晦炊。
所以,在訪問http://localhost:8080
時,執(zhí)行的是myHandler
自定義類型的ServeHTTP
方法断国,返回瀏覽器I'm home page
字符串贤姆,并在瀏覽器上顯示出來。
http.HandleFunc
的用法和http.Handle
一樣稳衬,但是第二個參數(shù)是func(ResponseWriter, *Request)
這個函數(shù)類型而不是Handler
接口霞捡,由上面的分析可知,ServeMux
的成員m
存儲要求是個Handler
接口薄疚,那么傳入個函數(shù)怎么處理呢碧信?見源碼:
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
DefaultServeMux
使用了HandleFunc
方法,該方法將函數(shù)類型的handler
強行轉(zhuǎn)換成了自定義類型HandlerFunc
類型输涕,該類型實現(xiàn)了Handler
接口音婶,也就使得傳入的函數(shù)也實現(xiàn)了Handler
接口,在ServeHTTP
方法中莱坎,直接調(diào)用自身代表的函數(shù)衣式。很是巧妙!
這些就是net/http
包背后默默幫你做的事情檐什。
自定義路由器
下一個問題碴卧,如果不使用DefaultServeMux
呢,能否自己定義一個ServeMux
(路由器)乃正?答案是可以的住册,將簡單的HTTP服務(wù)器修改一下:
package main
import (
"io"
"log"
"net/http"
)
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
func main() {
mux := http.NewServeMux() // new出一個新的ServeMux對象
mux.Handle("/", &myHandler{})
mux.HandleFunc("/hello", sayHello)
err := http.ListenAndServe(":8080", mux) //傳入自定義的路由器(mux)
if err != nil {
log.Fatal(err)
}
}
與文章開頭不同的地方在于,使用http.NewServeMux()
創(chuàng)建了一個ServeMux
對象瓮具,然后調(diào)用mux.Handle
注冊路由荧飞,最后在http.ListenAndServe
中傳入自定義的路由器。
更進一步名党,能否不使用ServeMux
結(jié)構(gòu)體呢叹阔?我們可以直接使用http.ListenAndServe
中使用的Server
結(jié)構(gòu),并且自己處理路由分發(fā)(自己定義字典存儲路由規(guī)則)传睹。
package main
import (
"io"
"log"
"net/http"
"time"
)
// 用字典實現(xiàn)路由器
var mux map[string]func(http.ResponseWriter, *http.Request)
// 自己實現(xiàn)的Handler
type myHandler struct{}
func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 根據(jù)url選擇對應(yīng)的處理函數(shù)
if h, ok := mux[r.URL.String()]; ok {
h(w, r)
return
}
io.WriteString(w, "sorry, error happened!")
}
func sayHello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
func sayHome(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "I'm home page")
}
func main() {
server := http.Server{
Addr: ":8080",
Handler: &myHandler{},
ReadTimeout: 5 * time.Second, //設(shè)置超時時間
}
mux = make(map[string]func(http.ResponseWriter, *http.Request))
mux["/"] = sayHome
mux["/hello"] = sayHello
err := server.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}