#http服務 ##引子,http的hello world瀏覽器
若是要搜索「go http helloworld」的話,多半會搜索到如下代碼函數
package main import ( "io" "net/http" ) func main() { http.HandleFunc("/", sayhello) http.ListenAndServe(":8080", nil) } func sayhello(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello world") }
這時,若是用瀏覽器訪問localhost:8080的話,能夠看到「hello world」
main裏的內容解釋:網站
##最簡單的http服務 如今開始,我由簡入深的一步一步介紹net/http包
首先,請先忘記引子裏的http.HandleFunc("/", sayhello)
,這個要到很後面才提到code
其實要使用http包,一句話就能夠了,代碼以下接口
package main import "net/http" func main() { http.ListenAndServe(":8080", nil) }
訪問網頁後會發現,提示的不是「沒法訪問」,而是」頁面沒找到「,說明http已經開始服務了,只是沒有找到頁面
由此能夠看出,訪問什麼路徑顯示什麼網頁 這件事情,和ListenAndServe的第2個參數有關ip
查詢ListenAndServe的文檔可知,第2個參數是一個Hander
Hander是啥呢,它是一個接口。這個接口很簡單,只要某個struct有ServeHTTP(http.ResponseWriter, *http.Request)
這個方法,那這個struct就自動實現了Hander接口資源
顯示什麼網頁取決於第二個參數Hander,Hander又只有1個ServeHTTP
因此能夠證實,顯示什麼網頁取決於ServeHTTP路由
那就ServeHTTP方法,他須要2個參數,一個是http.ResponseWriter,另外一個是*http.Request
往http.ResponseWriter寫入什麼內容,瀏覽器的網頁源碼就是什麼內容
*http.Request裏面是封裝了,瀏覽器發過來的請求(包含路徑、瀏覽器類型等等)文檔
##認識http.ResponseWriterget
把上面「網頁未找到」的代碼改一下,讓你們認識一下http.ResponseWriter
看清楚了,ServeHTTP方法是本身寫的啊,本身去實現
package main import ( "io" "net/http" ) type a struct{} func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello world version 1.") } func main() { http.ListenAndServe(":8080", &a{})//第2個參數須要實現Hander的struct,a知足 }
如今
訪問localhost:8080的話,能夠看到「hello world version 1.」
訪問localhost:8080/abc的話,能夠看到「hello world version 1.」
訪問localhost:8080/123的話,能夠看到「hello world version 1.」
事實上訪問任何路徑都是「hello world version 1.」
哦,原來是這樣,當http.ListenAndServe(":8080", &a{})
後,開始等待有訪問請求
一旦有訪問請求過來,http包幫咱們處理了一系列動做後,最後他會去調用a的ServeHTTP
這個方法,並把本身已經處理好的http.ResponseWriter, *http.Request
傳進去
而a的ServeHTTP
這個方法,拿到*http.ResponseWriter
後,並往裏面寫東西,客戶端的網頁就顯示出來了
##認識*http.Request
如今把上面的代碼再改一下,讓你們認識一下*http.Request
package main import ( "io" "net/http" ) type a struct{} func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.String() //得到訪問的路徑 io.WriteString(w, path) } func main() { http.ListenAndServe(":8080", &a{})//第2個參數須要實現Hander接口的struct,a知足 }
如今
訪問localhost:8080的話,能夠看到「/」
訪問localhost:8080/abc的話,能夠看到「/abc」
訪問localhost:8080/123的話,能夠看到「/123」
##最傻的網站
若是再加上一些判斷的話,一個最傻的可運行網站就出來了,以下
package main import ( "io" "net/http" ) type a struct{} func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) { path := r.URL.String() switch path { case "/": io.WriteString(w, "<h1>root</h1><a href=\"abc\">abc</a>") case "/abc": io.WriteString(w, "<h1>abc</h1><a href=\"/\">root</a>") } } func main() { http.ListenAndServe(":8080", &a{})//第2個參數須要實現Hander接口的struct,a知足 }
運行後,能夠看出,一個case就是一個頁面
若是一個網站有上百個頁面,那是否要上百個case?
很不幸,是的
那管理起來豈不是要累死?
要累死,不過,還好有ServeMux
##用ServeMux拯救最傻的網站
如今來介紹ServeMux
ServeMux大體做用是,他有一張map表,map裏的key記錄的是r.URL.String(),而value記錄的是一個方法,這個方法和ServeHTTP是同樣的,這個方法有一個別名,叫HandlerFunc
ServeMux還有一個方法名字是Handle,他是用來註冊HandlerFunc 的
ServeMux還有另外一個方法名字是ServeHTTP,這樣ServeMux是實現Handler接口的,否者沒法當http.ListenAndServe的第二個參數傳輸
代碼來了
package main import ( "net/http" "io" ) type b struct{} func (*b) ServeHTTP(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello") } func main() { mux := http.NewServeMux() mux.Handle("/h", &b{}) http.ListenAndServe(":8080", mux) }
解釋一下
mux := http.NewServeMux()
:新建一個ServeMux。
mux.Handle("/", &b{})
:註冊路由,把"/"註冊給b這個實現Handler接口的struct,註冊到map表中。
http.ListenAndServe(":8080", mux)
第二個參數是mux。
運行時,由於第二個參數是mux,因此http會調用mux的ServeHTTP方法。
ServeHTTP方法執行時,會檢查map表(表裏有一條數據,key是「/h」,value是&b{}的ServeHTTP方法)
若是用戶訪問/h
的話,mux由於匹配上了,mux的ServeHTTP方法會去調用&b{}的 ServeHTTP方法,從而打印hello
若是用戶訪問/abc
的話,mux由於沒有匹配上,從而打印404 page not found
ServeMux就是個二傳手!
##ServeMux的HandleFunc方法
發現了沒有,b這個struct僅僅是爲了裝一個ServeHTTP而存在,因此可否跳過b呢,ServeMux說:能夠 mux.HandleFunc是用來註冊func到map表中的
package main import ( "net/http" "io" ) func main() { mux := http.NewServeMux() mux.HandleFunc("/h", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello") }) mux.HandleFunc("/bye", func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "byebye") }) mux.HandleFunc("/hello", sayhello) http.ListenAndServe(":8080", mux) } func sayhello(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello world") }
##ServeMux的白話解釋
若是把http服務想象成一個公司的話 沒有ServeMux的話,就像這個公司只有1我的--老闆,什麼事情都是由老闆來作(本身寫switch) 包括作業務(「/sales」),作賬(「/account」),內勤(「/management」),掃地(「/cleaner」)
後來老闆招了4我的,負責上面的4件事情,那老闆要作的就是根據狀況轉發就是了,好比作業務的事情,本身就不用去跑客戶了,交給銷售經理去作,並給銷售經理一些資源(包括客戶名稱地址什麼的)
上面代碼裏的type b struct{}
就是個銷售經理,資源就是w http.ResponseWriter, r *http.Request
,老闆要工做的就是ServeMux要作的工做
以上的代碼就作了一層轉發,老闆給銷售經理事情後,銷售經理去跑客戶了,也就結束了
實際生活中,銷售經理不會本身去跑客戶的,他也會把接來的活轉給更下面的業務員
那時,銷售經理本身也變成一個ServeMux了
因此爲了能夠ServeMux套ServeMux,須要改造一下ServeMux的ServeHTTP,改完後能夠嵌套無限層,把要作的事情不斷的細化
到最後要真正作事情的人,只須要關心本身要作的事情就能夠了,本身作本身分內的事,一個大公司就運做起來了
##回到開頭
回到開頭,有讓你們先忘掉http.HandleFunc("/", sayhello)
請先忘記引子裏的http.HandleFunc("/", sayhello)
,這個要到很後面才提到
當http.ListenAndServe(":8080", nil)
的第2個參數是nil時 http內部會本身創建一個叫DefaultServeMux的ServeMux,由於這個ServeMux是http本身維護的,若是要向這個ServeMux註冊的話,就要用http.HandleFunc這個方法啦,如今看很簡單吧