多路复用器ServeMux和DefaultServeMux
当一个Http请求发送到服务端后,首先要做的就是多路复用器进行请求转发到相应的处理器。在Go中内置了ServeMux这个结构体,它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。而DefaultServeMux是net/http包中默认提供的一个多路复用器,本质是ServeMux的一个实例。
HandleFunc()函数
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))函数的含义如下:HandleFunc注册一个处理器函数handler和对应的模式pattern。具体代码如下:
func main() {
http.HandleFunc("/hello", hello)//传入hello这个函数作为处理器函数
http.ListenAndServe(":8080",nil)
}
func hello(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "通过HandleFunc()函数进行请求处理!")
}
传入的处理器函数,在方法内部会通过DefaultServeMux.HandleFunc(pattern, handler) 封装成处理器,具体源码如下:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
------------------------------------------------------------------------------------
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}//可以看到,传入的handler函数在最后一行代码被封装成了处理器
Handle()函数
通过handle()函数,可以来指定处理器,func (mux *ServeMux) Handle(pattern string, handler Handler)Handle注册HTTP处理器handler和对应的模式pattern。如果该模式已经注册有一个处理器,Handle会panic。具体代码如下:
type MyHandler struct{}
func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "通过自己创建的处理器处理请求!",r.URL.Path )
}
func main() {
myHandler := MyHandler{}
http.Handle("/hello", &myHandler)
http.ListenAndServe(":8080", nil)//创建路由
}
Go内置了handle结构,并且结构内部只有serveHttp()一个方法,而这个serveHttp()方法是GoWeb中十分重要的一个函数,能根据URL查找对应的处理器,移交请求,具体结构源码:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
所以我们自定义的处理器只要实现ServeHTTP()方法,就是实现了Handler接口。而在Handle()方法中,由于我们没有自定义多路复用器,会调用内置的默认多路复用器也就是DefaultServeMux进行处理,其他内容可参考 2 的源码部分。
自定义Server服务器结构
上面2中方法我们能发现,在http.ListenAndServe(":8080", nil)这行代码中,总是在第二个参数传入一个nil,深入查看可以发现,在这个方法内部是这样的:
源码:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
可以看到Go在内部自定义了一个Server结构体,并且最终还是通过调用这个结构体的无参ListenAndServe()方法进行操作,这个结构全部内容如下:
type Server struct {
Addr string // 监听的TCP地址,如果为空字符串会使用":http"
Handler Handler // 调用的处理器,如为nil会调用http.DefaultServeMux
ReadTimeout time.Duration // 请求的读取操作在超时前的最大持续时间
WriteTimeout time.Duration // 回复的写入操作在超时前的最大持续时间
MaxHeaderBytes int // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
TLSConfig *tls.Config // 可选的TLS配置,用于ListenAndServeTLS方法
// TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
// 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
// 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
// 连接在函数返回时会自动关闭。
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
// 参见ConnState类型和相关常数获取细节。
ConnState func(net.Conn, ConnState)
// ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
// 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
ErrorLog *log.Logger
// 内含隐藏或非导出字段
}
那么我们是不是也可以创建一个Server的 实例,直接调用无参ListenAndServe()函数呢?
type MyHandler struct{}
func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "通过自己创建的多路复用器处理请求!",r.URL.Path )
}
func main() {
myHandler := MyHandler{}
//创建Server结构,配置详细信息
server := http.Server{
Addr: ":8080",
Handler: &myHandler,
ReadTimeout: 2 * time.Second,
}
server.ListenAndServe()
}
自定义多路复用器
上述几种搭建服务器方法中,我们都用的是内置的默认多路复用器,从上面的源码中可以看到其实也可以自己定义一个多路复用器实例,直接调用多路复用器实例的mux.HandleFunc()方法即可。
func main() {
mux := http.NewServeMux()//创建一个多路复用器
mux.HandleFunc("/hello", handle)
http.ListenAndServe(":8080", mux)
}
func hello(writer http.ResponseWriter, request *http.Request) {
fmt.Fprint(writer, "通过HandleFunc()函数进行请求处理!")
}
serveMux的缺陷是无法使用实现URL模式匹配,可以由第三方HttpRouter包实现这个功能,具体在github上可以超找到相关路由包。