一个典型的 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
时,会建立一个默认的服务器:spa
// src/net/http/server.go func ListenAndServe(addr string, handler Handler) { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
这个服务器默认使用DefaultServeMux
来处理器请求:code
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 查找咱们注册的处理器,而后将请求交由它处理。server
虽然默认的多路复用器使用起来很方便,可是在生产环境中不建议使用。因为DefaultServeMux
是一个全局变量,全部代码,包括第三方代码均可以修改它。
有些第三方代码会在DefaultServeMux
注册一些处理器,这可能与咱们注册的处理器冲突。对象
比较推荐的作法是本身建立多路复用器。
建立多路复用器也比较简单,直接调用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
。