开启一个HTTP服务,有以下两种方式:
// 方式一:http.HandleFunc
func main() {
http.HandleFunc("/", test)
http.ListenAndServe(":8090", nil)
}
func test(w http.ResponseWriter, r *http.Request) {}
// 方式二:http.Handle
type indexHandler struct {}
func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// dosomething
}
func main() {
http.Handle("/", &indexHandler{})
http.ListenAndServe(":8001", nil)
}
路由注册
HandleFunc
注意:
http.HandleFunc是一个函数, http.HanderFunc是一个类型声明。(名称相差一个字母r)。
http.Handle是一个函数,Hander是一个接口。(名称相差一个字母r)
HeadleFunc函数源码如下。
由上图可见,HandleFunc会使用HeadlerFunc类型转化的方式,使得传入的参数handler实现了ServeHTTP的方法。
Handle
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 隐藏了一些参数验证的代码
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
// 根据url的长度,查找新的路由方法应该插入到那里
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
服务监听
源码速览
func (srv *Server) Serve(l net.Listener) error {
for {
rw, err := l.Accept()
if err != nil {
...
}
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx) // 开启一个线程处理请求,但是一个tcp连接只能同一时间只能处理一个请求
}
}
func (c *conn) serve(ctx context.Context) {
// HTTP/1.x from here on.
for {
w, err := c.readRequest(ctx)
if err != nil {
...
}
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
// http.ListenAndServe(":8001", nil),所有handle为空
if handler == nil {
handler = DefaultServeMux // 使用默认路由,也就是http.HandleFunc注册的路由
}
handler.ServeHTTP(rw, req)
}
流程分析
1 实例化Server
2 调用Server的ListenAndServe()
3 调用net.Listen(“tcp”, addr)监听端口
4 启动一个for循环,在循环体中Accept请求
5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
6 读取每个请求的内容w, err := c.readRequest()
7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
8 调用handler的ServeHttp
9 在这个例子中,下面就进入到DefaultServeMux.ServeHttp
10 根据request选择handler,并且进入到这个handler的ServeHTTP
mux.handler(r).ServeHTTP(w, r)
11 选择handler:
A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)
B 如果有路由满足,调用这个路由handler的ServeHTTP
C 如果没有路由满足,调用NotFoundHandler的ServeHTTP
路由匹配
当注册路由 /abc/时,会默认注册/abc,若/abc已经存在则会被覆盖。此外/abc/可以匹配到/adc/1。
但/abc不能匹配/abc/。
前文有说,路由注册的时候,mux.es中会根据url长度进行存储,所以,此处匹配时,会优先匹配到最长的字符串。
例如: /abc/1/2 会匹配到 /abc/1/ 而不是 /abc/
http问题
1、不能匹配路径参数
2、 不支持正则路由