最简模式
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world\n")
}
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8080", nil)
}
重点是记住这两点:
func hello(w http.ResponseWriter, req *http.Request)
http.HandleFunc
注册一个函数到指定路径上
ListenAndServe
流程分析
https://github.com/golang/go/blob/master/src/net/http/server.go#L3240
https://github.com/golang/go/blob/master/src/net/http/server.go#L2976
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
主要的逻辑:
net.Listen("tcp", addr)
->net.Listener
- for 循环:
net.Listener.Accept()
->net.Conn
http.Server.newConn(conn)
->http.Conn
go http.Conn.serve(ctx)
- goroutine 中又是一个 for 循环:
http.Conn.readRequest(ctx)
->http.response
serverHandler{c.server}.ServeHTTP(w, w.req)
- 最后这个
serverHandler.ServeHTTP
就比较关键了:
优先采用http.Server
上的 Handler;
如果没有,则用默认的http.DefaultServeMux
实现简单的 URL 路由。
这也是为什么ListenAndServe
传了个nil
进来,srv.Handler
为nil
就用DefaultServeMux
。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
func (mux *ServeMux) match(path string) (h Handler, pattern string)
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool)
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
DefaultServeMux
// ==================================================================
// 封装 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")
}
// https://github.com/golang/go/blob/master/src/net/http/server.go#L2505
// func (mux *ServeMux) Handle(pattern string, handler Handler)
// 把 handler 注册到 mux.m (map[string]muxEntry) 上
// muxEntry{h: handler, pattern: pattern}
mux.Handle(pattern, HandlerFunc(handler))
}
// 封装普通函数为 Handler
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ==================================================================
// 路由匹配 =========================================================
// ==================================================================
ServeHTTP -> Handler -> handler -> match
-> redirectToPathSlash -> shouldRedirectRLocked -> 跳转(http.RedirectHandler)
默认路由规则:
- 优先完整匹配
- URL 前缀匹配,最长的路由规则优先
另一种方式 (http.Handle
)
不用 http.HandleFunc
,而是传入一个 Handler(实现 ServeHTTP
方法的结构体)。
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct {
content string
}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, h.content)
}
func main() {
http.Handle("/", &HelloHandler{"hello world\n"})
http.ListenAndServe(":8080", nil)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
http.Handle
和 http.HandleFunc
都只是给默认路由(DefaultServeMux)上注册一个规则,知道这一点就可以了。
重点是 mux.Handle
和 Handler
接口。
还可以这样
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct {
content string
}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, h.content)
}
func main() {
http.ListenAndServe(":8080", &HelloHandler{"hello world\n"})
}
知道背后的流程就豁然开朗了。
当然,这样也是可以的:
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world\n")
}
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(hello))
}
整理总结
- http.Conn
- http.Server
- 到处都是 Hander(接口),路由(mux)也是 Handler
func(w http.ResponseWriter, req *http.Request)
实现正则路由
GitHub 上一搜索 golang mux
golang router
就有,这是几个可以参考的方案:
我自己实现一个简单的路由机制,参考 Django:
<name>
OR <converter:name>
其中:converter
内置支持 str
, int
, slug
, uuid
, path
, 可以自定义,默认是 str
; name
可以是合法的 Go 变量名。
中间件
通过 Handler 套娃实现中间件:
type LogMiddleware struct {
handler Handler
}
func (this LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
this.handler.ServeHTTP(w, r)
fmt.Printf("%s %s %v\n", r.Method, r.URL.RequestURI(), time.Since(start))
}
mux := ...
http.ListenAndServe(":8000", LogMiddleware{mux})
系统内置 Handler:
grep -E "func.+ServeHTTP" src/net/http -R | grep -Fv "_test.go"
src/net/http/cgi/host.go:func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/fs.go:func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/httputil/reverseproxy.go:func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/pprof/pprof.go:func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
src/net/http/server.go:func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/server.go:func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (h initALPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/triv.go:func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
src/net/http/triv.go:func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
有待分析。
优雅重启
参考资料与拓展阅读
- Eli Bendersky's website, Life of an HTTP request in a Go server
- 通过分析gin、beego源码,读懂web框架对http请求处理流程的本质