golang的net/http包提供了启动一个http服务简单的方法,我们直接从调用流程追踪代码查看http包是怎样提供http服务的,下面是一个简单的deamo:
package main
import (
"fmt"
"log"
"net/http"
)
func loginHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "login success")
}
func startServer(addr string) {
http.HandleFunc("/login", loginHandler)
log.Fatal(http.ListenAndServe(addr, nil))
}
func main() {
startServer(":10080")
}
因为http是建立在tcp传输协议之上的,所以启动http服务器本质上是启动一个tcp服务器,只是需要对http应用层的协议进行解析;对http request的处理是根据request路径来选择处理方式的,上面代码也就做了这两件事:
- 注册http request的处理函数,这里是注册了/login的处理函数loginHandler
- 启动一个server接收http request,并根据request path找到注册的处理函数
接下来跟代码,目的是搞清楚http.HandleFunc()注册的处理函数保存到哪里了?server接收到http request后又是怎样找到对应的处理函数的。
一. Handler/HandlerFunc
首先介绍下Handler类型,因为后面分析代码需要它,看看它的声明:
1. Handler
// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and either closes the network connection or sends an HTTP/2
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
// the client sees an interrupted response but the server doesn't log
// an error, panic with the value ErrAbortHandler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
只要实现了ServeHTTP(w http.ResponseWriter, r *http.Request)就是Handler类型,ServeHTTP用来对回复http请求。
3. HandlerFunc
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc本身是函数类型,实现了ServeHTTP()方法,同时ServeHTTP()内部调用的又是函数本身,HandlerFunc其实就是函数适配器。
二. DefaultServeMux
接下来看看是如何注册处理函数的
http.HandleFunc("/login", loginHandler)
调用的是DefaultServeMux的HandleFunc()函数
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
对handler进行判空再处理,注意这里把传入的handler参数强制转换成HandlerFunc类型,HandlerFunc又实现ServeHTTP()方法,所以本质是把一个处理函数最终转换成Handler类型。
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
从函数原型可以知道DefaultServeMux是ServeMux类型的实例,我们看下ServeMux的定义:
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
}
type muxEntry struct {
h Handler
pattern string
}
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
知道了DefaultServeMux是ServeMux类型的实例,接下来看看DefaultServeMux是如何保存这些pattern和处理函数的。
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
原来是把pattern和Handler保存到DefaultServeMux的成员m
中了,我们可以猜测匹配过程是通过pattern在m
中找到对应Handler去处理http request,那es
成员有什么用呢?这就要说道ServeMux的pattern匹配规则了
- 如果pattern不以
/
结尾,如:/index
,则只会匹配/index
的请求- 如果pattern以
/
结尾,如:/index/
,则该pattern能匹配/index/
,/index/info
,但是优先匹配长的路径- 所以pattern
/
能匹配所有不能被其他pattern匹配的路径- 如果pattern不以
/
结尾,如:/login
,如果匹配不到/login
,则会去匹配/login/
es
保存的就是以/
结尾的pattern,并且按字典从大到小排序,所以匹配时遍历es
就能优先遍历较长的路径。
到这里注册处理函数就结束了,主要是把处理函数强制转成Handler类型,并把pattern和Handler保存到DefaultServeMux中,我们可以预料在某个时刻http server会通过DefaultServeMux找到对用的Handler
三. ListenAndServe
log.Fatal(http.ListenAndServe(addr, nil))
创建一个Server,监听addr中的地址和端口,注意这里的handler参数传的是nil,看来是默认会使用DefaultServeMux中的Handler,只是要搞清楚它们是怎么关联上的。
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
接下来就是传统的server建立了,开始监听,接收连接,处理请求
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)
}
Serve函数比较长,主要是调用accept()创建连接,并启动一个goroutine去处理新连接的事件
func (srv *Server) Serve(l net.Listener) error {
......
......
......
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
....
....
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
这里accpet处理挺有意思,accept失败的时,如果不是致命错误,则依次休眠5ms,10ms,20ms....1s..1s..1s再进行accept()操作,想到的一个场景是文件句柄打开太多了导致accept()创建fd失败,sleep一段时间后系统回收一定的文件句柄,再次调用accpet()就能成功。
接下来看看goroutine是怎么处理http请求的,还是去除不相关代码
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
....
....
// HTTP/1.x from here on.
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
....
....
....
c.curReq.Store(w)
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
....
....
....
}
}
在for{}
循环中读取http请求,再把解析出来的数据保存到response对象w
中,response类型实现了ResponseWriter的方法,最终调用到serverHandler{c.server}.ServeHTTP(w, w.req),继续看看serverHandler是怎么实现ServeHTTP方法的
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
还记得我们启动server时传的参数吗?http.ListenAndServe(addr, nil)
handler参数传的就是nil,所以用DefaultServeMux作为handler,因为DefaultServeMux也实现了ServeHTTP方法,所以最终会调用到DefaultServeMux的ServeHTTP()方法中
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
最终会调用到DefaultServeMux的match()方法,并返回真正处理http请求的Handler
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. mux.es contains all patterns
// that end in / sorted from longest to shortest.
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
可以看到首先从m
中完全匹配pattern,如果匹配不到再到es
中去匹配。
实现Handler结构的类型有:
- HandlerFunc类型是个函数适配器,把处理函数转成Handler类型
- ServeMux是DefaultServeMux的类型,用来通过path找到对应的handler
- serverHandler类型只有一个Server成员,http服务器就是通过Server中handler成员去处理http请求的,如果handler成员为nil,则通过DefaultServeMux去处理
下面是http.HandleFunc(path, handler)的流程图:
下面是http.ListenAndServe(addr, nil)的流程图:
除了可以用默认的DefaultServeMux作为http复用器之外,还能自定义实现一个,只要实现ServeHTTP()方法就行,因为本质上是传入一个Handler实例到http.ListenAndServe(string, Handler)
package main
import (
"fmt"
"net/http"
)
type MyServeMux struct{}
func loginHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "loginHandler\n")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "indexHandler\n")
}
func defaultHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "defaultHandler\n")
}
func (m *MyServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/login":
loginHandler(w, r)
case "/index":
indexHandler(w, r)
default:
defaultHandler(w, r)
}
}
func startServer(addr string) {
http.ListenAndServe(addr, &MyServeMux{})
}
func main() {
startServer(":12345")
}
用curl测试结果如下:
$$ curl -X GET http://localhost:12345/login
loginHandler
$$:~$ curl -X GET http://localhost:12345/index
indexHandler
$$:~$ curl -X GET http://localhost:12345/
defaultHandler