简介
net/http 作为 Go 语言标准库,用来编写 Web服务非常方便,但为了更深入理解其运行原理,还需要进行源码分析。
1、基础使用
//用户自定义实现功能处理
func (g greeting) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, g)
}
//用户自定义实现功能处理
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
}
func TestInitialWeb(t *testing.T) {
//1.基本使用方式,使用默认对象 DefaultServeMux(不安全,不推荐)
//两种方式注册 处理器/函数 到默认对象 DefaultServeMux
http.HandleFunc("/", index)
http.Handle("/greeting/", greeting("Welcome ,dj"))
http.ListenAndServe(":8080", nil)
}
通过 http.HandleFunc、http.Handle 注册处理器函数到指定的路径上,这两种处理方式对应接口如下,发现底层实际上都是通过 DefautltServeMux 这个对象的相关方法来处理。
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)) //这里mux指 DefaultServeMux
}
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
DefaultServeMux 是 ServeMux 结构体的对象,ServeMux 结构体主要包含了互斥锁对象、普通的map对象(map的value为muxEntry结构体,而muxEntry结构体包含路径pattern和对应的Handler处理函数)、muxEntry结构体数组。那么就可以猜测到在处理时通过 muxEntry结构体数组查询请求路径所匹配到的pattern,然后获取对应的 handler处理器进行处理。
type ServeMux struct {
mu sync.RWMutex //互斥锁
m map[string]muxEntry //注册handler、pattern
es []muxEntry //前缀匹配
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
2、HandlerFunc
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler)) //这里将handler转化为HandlerFunc类型
}
//HandlerFunc类型是函数类型
type HandlerFunc func(ResponseWriter, *Request)
//Handler接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
//实现了ServeHTTP接口
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
//两种不同注册方法的最终调用
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
}
}
继续深入可以发现 HandlerFunc 类型为函数类型,该类型实现了 Handler接口的 ServerHTTP 方法。http.HandleFunc("/",index) 方法在内部将handler 转化为 HandlerFunc 类型,此时和 http.Handle("/greeting", greeting("xxxx")) 方法最终调用相同的处理函数。也就是说 HandlerFunc 类型只是为了方便注册函数处理器。
3、Handle
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
}
}
Handle 方法主要在进行路径和处理函数的键值对注册,会把生成的 muxEntry 对象存储到 ServerMux 结构体中。
注意这里在注册完毕后还查看了路径 pattern是否以 '/' 结尾,如果是则将根路径处理注册到 ServerMux 结构体。它保证了在请求访问时先进行精准匹配,如果不能匹配到再根据注册的根路径处理进行最长前缀匹配。
4、ListenAndServe
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) //listen建立socket
if err != nil {
return err
}
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept() //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
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx) //处理
}
}
ListenAndServe 方法按照 socket 的建立流程进行,通过 net.Listen("tcp", addr) 创建socket;然后通过 l.Accept() 阻塞地等待客户端连接,连接成功后通过 c.serve(connCtx) 进行请求处理。
注意如果连接失败不会立刻放弃本次请求,需要判断错误是不是临时性地,如果是临时错误睡眠一段时间后重试。通过指数退避策略,默认第一次睡眠 5毫秒,每次睡眠时间翻倍,最多睡眠 1秒。
5、回调接口 ServeHTTP
func (c *conn) serve(ctx context.Context) {
// ... 省略代码
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
// ... 省略代码
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil { //handler为ListenAndServe方法第二个参数,传递为nil
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
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)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path) //handler匹配
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
// Check for exact match first.
v, ok := mux.m[path] //先查找 mux.m
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) { //后查找 mux.es
return e.h, e.pattern
}
}
return nil, ""
}
回调函数serveHTTP中对路径进行了匹配,先进行精准匹配,随后进行最长前缀匹配。这个匹配规则过于简单,所以 Gin 等其它框架对这里进行了更详细的优化处理。
所以整体的请求处理流程整理如下:
- ln, err := net.Listen(“tcp”, addr)做了初试化了socket, bind, listen的操作.
- rw, e := l.Accept()进行accept, 等待客户端进行连接
- go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作
- h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端