简单用例
mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("ok"))
})
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal("err=",err.Error())
}
打开浏览器,输入localhost:8080
会看到ok
聊聊 http.NewServeMux
http.NewServeMux 返回的类型是*http.ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
可以看到ServeMux 主要是保存路由信息(路径和处理函数),所以被称为多路复用器,类似路由
ServeMux 如何保存路由信息
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
mux.m[pattern] = muxEntry{h: handler, pattern: pattern}
...
}
当我们调用HandleFunc函数的时候,它会把pattern 和 handler 保存到 m 中,所以m 可以称为路由表
ServeMux 如何匹配路由
经过http协议的处理,最后的内容会保存到一个结构体中,后面会讲解具体内容,接收完请求之后,会调用ServeHTTP函数来处理相应的请求,所以路由匹配将会在这个函数中进行。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
...
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
...
return mux.handler(host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
...
h, pattern = mux.match(path)
...
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// Check for longest valid match.
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
从上面可以看到,真正的匹配是在match函数中进行匹配,也就是一开始先用map准确查找,如果找不到,就枚举查找最长符合的路径。
窥探 http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
...
ln, err := net.Listen("tcp", addr)
...
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
func (srv *Server) Serve(l net.Listener) error {
...
for {
rw, e := l.Accept()
...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
func (c *conn) serve(ctx context.Context) {
...
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
...
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
...
}
}
可以看到,每次接收一次连接请求,就会开启一个goroutine处理连接,经过一些配置,然后调用readRequest 开始读取请求内容,构造结构体,然后调用ServeHTTP 处理这个请求, 然后调用 finishRequest 进行响应。
再看 readRequest
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
...
req, err := readRequest(c.bufr, keepHostHeader)
...
w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
// We populate these ahead of time so we're not
// reading from req.Header after their Handler starts
// and maybe mutates it (Issue 14940)
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
...
return w, nil
}
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
tp := newTextprotoReader(b)
req = new(Request)
// First line: GET /index.html HTTP/1.0
var s string
if s, err = tp.ReadLine(); err != nil { // 读http起始行
return nil, err
}
...
// Subsequent lines: Key: value.
mimeHeader, err := tp.ReadMIMEHeader() // 读http首部
if err != nil {
return nil, err
}
req.Header = Header(mimeHeader)
...
err = readTransfer(req, b) // 获取http 主体body
if err != nil {
return nil, err
}
...
return req,nil
从其内部可以看出,读取一个请求是步骤先是tp.ReadLine 读取http起始行部分信息,获取 method 、uri 和 proto 三部分,然后ReadMIMEHeader 读取头部 ,处理头部信息,保存到map结构当中,最后 readTransfer 交接body。
再看 finishRequest()
func (w *response) finishRequest() {
...
w.conn.bufw.Flush() //将响应内容写到相应缓冲区中
...
}
可以看到,最后将响应内容写到缓冲区中,交由下层协议处理。
省略大部分实现细节,但可以很快了解其大致流程,而不会困顿于局部,有兴趣可以深入了解。