go标准库net/http服务端的实现示例
作者:StarSky-yuan
1、http简单使用
go的http标准库非常强大,调用了两个函数就能够实现一个简单的http服务:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func ListenAndServe(addr string, handler Handler) error
handleFunc注册一个路由和相应的处理函数,第一个参数表示注册的路由,第二个参数表示注册路由对应的处理函数;ListenAndServe用来启动http服务并监听,第一个参数是服务器地址,第二个参数表示使用的处理器。
下面是用这两个函数实现的简单的http服务:注册了一个“/”路由的处理函数,并在8080端口启动http服务,ListenAndServe第二个参数为空表示使用标准库默认的处理器,也可使用自定义处理器,传参即可。处理器的概念在下面标准库分析中进行介绍。
import ( "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // TODO }) http.ListenAndServe(":8080", nil) }
2、http标准库分析
根据上面的两个函数来对http标准库展开分析
2.1、服务端数据结构
首先介绍下这两个函数涉及到的数据类型
(1)服务器对象,其中最核心的是Handler成员,表示整个http服务的路由器,存储路由路径对应到处理函数的映射,可自定义,例如第1小姐中的案例,没有自定义路由器对象,就会使用标准库提供的默认对象DefaultServeMux
type Server struct { Addr string // 地址 host:port Handler Handler // 处理器对象或路由器 // ... }
(2)Handler是一个接口,提供了ServeHTTP方法,用来将路由映射到相应的处理函数上
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
(3)路由器对象ServeMux,用来存储路由到处理函数的映射关系,该对象就是Handler接口的具体实现。
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 }
(4)muxEntry就是一个映射关系单元
type muxEntry struct { h Handler pattern string }
2.2、HandleFunc流程
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // TODO })
根据上面的函数来分析标准库的执行流程,首先看HandleFunc相关的实现:使用默认的DefaultServeMux路由器对象,调用ServeMux的HandleFunc,最后路由的注册是在mux.handle中实现,其中mux.Handle(pattern, HandlerFunc(handler))中对处理器做了类型转换,HandlerFunc 类型实现了ServeHTTP方法,所以被该类型转换后的函数都是Handler对象的实例
var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } 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)) } func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.handle(pattern, handler) }
进入到mux.handle中,会创建一个路由单元muxEntry对象,存储相应的路由和处理函数,其中对于根路径的存储需要做出特殊处理,在muxEntry中通过es存储,并按照顺序存储在muxEntry切片中,到此,已经完成了路由注册
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 } } func appendSorted(es []muxEntry, e muxEntry) []muxEntry { n := len(es) i := sort.Search(n, func(i int) bool { return len(es[i].pattern) < len(e.pattern) }) if i == n { return append(es, e) } es = append(es, muxEntry{}) copy(es[i+1:], es[i:]) es[i] = e return es }
2.3、ListenAndServe流程
ListenAndServe先初始化一个Server对象,并绑定地址和路由器,调用Server的ListenAndServe方法,其中net.Listen("tcp", addr)用于创建一个监听套接字并开始监听指定网络地址上的连接,返回一个实现了Listener接口的对象。关键是srv.Serve()
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) }
在srv.Serve(ln)中使用onceCloseListener 对Listener进行封装,防止被多次关闭,context.WithValue用来将srv服务器对象信息存储在context中,并使用for循环轮询等待连接,l.Accept()会阻塞等待,直到连接到达,并执行conn.serve函数。
type onceCloseListener struct { net.Listener once sync.Once closeErr error } type contextKey struct { name string } ServerContextKey = &contextKey{"http-server"} func (srv *Server) Serve(l net.Listener) error { l = &onceCloseListener{Listener: l} defer l.Close() // ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, err := l.Accept() // ... connCtx := ctx // ... c := srv.newConn(rw) // ... go c.serve(connCtx) } }
其中newConn会将Accept的返回的net.Conn封装成一个conn对象,对每个请求都会创建一个线程来处理,在conn.serve中会针对conn对象创建读写器并将内容置入缓冲区,在for中调用readRequest函数传入上下文,在readRequest中读取请求体req,并返回一个ResponseWriter的接口对象,用于向请求方返回响应,并在调用serverHandler的ServeHTTP方法
type conn struct { server *Server rwc net.Conn // ... } func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) 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, _ := c.readRequest(ctx) serverHandler{c.server}.ServeHTTP(w, w.req) w.finishRequest() ... } }
serverHandler的ServeHTTP方法用来根据路由分配handler,如果Server的Handler为空就是用默认的DefaultServerMux,对应上了文章一开始调用ListenAndServe的第二个参数,如果为空就使用默认路由器对象,最后调用路由器的ServeHTTP函数。
type serverHandler struct { srv *Server } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
接下来流程如下,依次调用返回命中的handler,如果没有命中,则采用模糊匹配命中,最后调用handler的ServeHTTP函数,因为注册路由时候的函数在注册时候被强转成HandleFunc函数类型,该类型是实现ServeHTTP方法的,所以执行handler的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) { mux.mu.RLock() defer mux.mu.RUnlock() return mux.match(path) } func (mux *ServeMux) match(path string) (h Handler, pattern string) { v, ok := mux.m[path] if ok { return v.h, v.pattern } for _, e := range mux.es { if strings.HasPrefix(path, e.pattern) { return e.h, e.pattern } } return nil, "" }
到此这篇关于go标准库net/http服务端的实现示例的文章就介绍到这了,更多相关go net/http服务端内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!