一個典型的 Go Web 程序結構以下,摘自《Go Web 編程》:golang
本文介紹如何建立多路複用器,如何註冊處理器,最後再簡單介紹一下 URL 匹配。咱們以上一篇文章中的"Hello World"程序做爲基礎。數據庫
package main
import (
"fmt"
"log"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", hello)
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
複製代碼
net/http 包爲了方便咱們使用,內置了一個默認的多路複用器DefaultServeMux
。定義以下:編程
// src/net/http/server.go
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
複製代碼
這裏給你們介紹一下 Go 標準庫代碼的組織方式,便於你們對照。瀏覽器
C:\Go
,即GOROOT
;GOROOT
下有一個 src 目錄,標庫庫的代碼都在這個目錄中;src/fmt
目錄中;src/net/http
目錄中。net/http 包中不少方法都在內部調用DefaultServeMux
的對應方法,如HandleFunc
。咱們知道,HandleFunc
是爲指定的 URL 註冊一個處理器(準確來講,hello
是處理器函數,見下文)。其內部實現以下:服務器
// src/net/http/server.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
複製代碼
實際上,http.HandleFunc
方法是將處理器註冊到DefaultServeMux
中的。函數
另外,咱們使用 ":8080" 和 nil
做爲參數調用http.ListenAndServe
時,會建立一個默認的服務器:ui
// src/net/http/server.go
func ListenAndServe(addr string, handler Handler) {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
複製代碼
這個服務器默認使用DefaultServeMux
來處理器請求:spa
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
handler.ServeHTTP(rw, req)
}
複製代碼
服務器收到的每一個請求會調用對應多路複用器(即ServeMux
)的ServeHTTP
方法。在ServeMux
的ServeHTTP
方法中,根據 URL 查找咱們註冊的處理器,而後將請求交由它處理。code
雖然默認的多路複用器使用起來很方便,可是在生產環境中不建議使用。因爲DefaultServeMux
是一個全局變量,全部代碼,包括第三方代碼均可以修改它。 有些第三方代碼會在DefaultServeMux
註冊一些處理器,這可能與咱們註冊的處理器衝突。cdn
比較推薦的作法是本身建立多路複用器。
建立多路複用器也比較簡單,直接調用http.NewServeMux
方法便可。而後,在新建立的多路複用器上註冊處理器:
package main
import (
"fmt"
"log"
"net/http"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
複製代碼
上面代碼的功能與 "Hello World" 程序相同。這裏咱們還本身建立了服務器對象。經過指定服務器的參數,咱們能夠建立定製化的服務器。
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
}
複製代碼
在上面代碼,咱們建立了一個讀超時和寫超時均爲 1s 的服務器。
上文中提到,服務器收到請求後,會根據其 URL 將請求交給相應的處理器處理。處理器是實現了Handler
接口的結構,Handler
接口定義在 net/http 包中:
// src/net/http/server.go
type Handler interface {
func ServeHTTP(w Response.Writer, r *Request) } 複製代碼
咱們能夠定義一個實現該接口的結構,註冊這個結構類型的對象到多路複用器中:
package main
import (
"fmt"
"log"
"net/http"
)
type GreetingHandler struct {
Language string
}
func (h GreetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", h.Language)
}
func main() {
mux := http.NewServeMux()
mux.Handle("/chinese", GreetingHandler{Language: "你好"})
mux.Handle("/english", GreetingHandler{Language: "Hello"})
server := &http.Server {
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
複製代碼
與前面的代碼有所不一樣,上段代碼中,定義了一個實現Handler
接口的結構GreetingHandler
。而後,建立該結構的兩個對象,分別將它註冊到多路複用器的/hello
和/world
路徑上。注意,這裏註冊使用的是Handle
方法,注意與HandleFunc
方法對比。
啓動服務器以後,在瀏覽器的地址欄中輸入localhost:8080/chinese
,瀏覽器中將顯示你好
,輸入localhost:8080/english
將顯示Hello
。
雖然,自定義處理器這種方式比較靈活,強大,可是須要定義一個新的結構,實現ServeHTTP
方法,仍是比較繁瑣的。爲了方便使用,net/http 包提供了以函數的方式註冊處理器,即便用HandleFunc
註冊。函數必須知足簽名:func (w http.ResponseWriter, r *http.Request)
。 咱們稱這個函數爲處理器函數。咱們的 "Hello World" 程序中使用的就是這種方式。HandleFunc
方法內部,會將傳入的處理器函數轉換爲HandlerFunc
類型。
// src/net/http/server.go
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
複製代碼
HandlerFunc
是底層類型爲func (w ResponseWriter, r *Request)
的新類型,它能夠自定義其方法。因爲HandlerFunc
類型實現了Handler
接口,因此它也是一個處理器類型,最終使用Handle
註冊。
// src/net/http/server.go
type HandlerFunc func(w *ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
複製代碼
注意,這幾個接口和方法名很容易混淆,這裏再強調一下:
Handler
:處理器接口,定義在 net/http 包中。實現該接口的類型,其對象能夠註冊到多路複用器中;Handle
:註冊處理器的方法;HandleFunc
:註冊處理器函數的方法;HandlerFunc
:底層類型爲func (w ResponseWriter, r *Request)
的新類型,實現了Handler
接口。它鏈接了處理器函數與處理器。通常的 Web 服務器有很是多的 URL 綁定,不一樣的 URL 對應不一樣的處理器。可是服務器是怎麼決定使用哪一個處理器的呢?例如,咱們如今綁定了 3 個 URL,/
和/hello
和/hello/world
。
顯然,若是請求的 URL 爲/
,則調用/
對應的處理器。若是請求的 URL 爲/hello
,則調用/hello
對應的處理器。若是請求的 URL 爲/hello/world
,則調用/hello/world
對應的處理器。 可是,若是請求的是/hello/others
,那麼使用哪個處理器呢? 匹配遵循如下規則:
/hello/others
對應的處理器。若是有,則查找結束。若是沒有,執行下一步;/hello/
對應的處理器。若是有,則查找結束。若是沒有,繼續執行這一步。即查找/
對應的處理器。這裏有一個注意點,若是註冊的 URL 不是以/
結尾的,那麼它只能精確匹配請求的 URL。反之,即便請求的 URL 只有前綴與被綁定的 URL 相同,ServeMux
也認爲它們是匹配的。
這也是爲何上面步驟進行到/hello/
時,不能匹配/hello
的緣由。由於/hello
不以/
結尾,必需要精確匹配。 若是,咱們綁定的 URL 爲/hello/
,那麼當服務器找不到與/hello/others
徹底匹配的處理器時,就會退而求其次,開始尋找可以與/hello/
匹配的處理器。
看下面的代碼:
package main
import (
"fmt"
"log"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is the index page")
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is the hello page")
}
func worldHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is the world page")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/hello", helloHandler)
mux.HandleFunc("/hello/world", worldHandler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
複製代碼
localhost:8080/
將返回"This is the index page"
,由於/
精確匹配;localhost:8080/hello
將返回"This is the hello page"
,由於/hello
精確匹配;localhost:8080/hello/
將返回"This is the index page"
。注意這裏不是hello
,由於綁定的/hello
須要精確匹配,而請求的/hello/
不能與之精確匹配。故而向上查找到/
;localhost:8080/hello/world
將返回"This is the world page"
,由於/hello/world
精確匹配;localhost:8080/hello/world/
將返回"This is the index page"
,查找步驟爲/hello/world/
(不能與/hello/world
精確匹配)-> /hello/
(不能與/hello/
精確匹配)-> /
;localhost:8080/hello/other
將返回"This is the index page"
,查找步驟爲/hello/others
-> /hello/
(不能與/hello
精確匹配)-> /
;若是註冊時,將/hello
改成/hello/
,那麼請求localhost:8080/hello/
和localhost:8080/hello/world/
都將返回"This is the hello page"
。本身試試吧!
思考: 使用/hello/
註冊處理器時,localhost:8080/hello/
返回什麼?
本文介紹了 Go Web 程序的基本結構。Go Web 的基本形式以下:
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World")
}
type greetingHandler struct {
Name string
}
func (h greetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s", h.Name)
}
func main() {
mux := http.NewServeMux()
// 註冊處理器函數
mux.HandleFunc("/hello", helloHandler)
// 註冊處理器
mux.Handle("/greeting/golang", greetingHandler{Name: "Golang"})
server := &http.Server {
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
複製代碼
後續文章中大部分程序只是在此基礎上增長處理器或處理器函數並註冊到相應的 URL 中而已。處理器和處理器函數能夠只使用一種或二者都使用。注意,爲了方便,命名中我都加上了Handler
。