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, ""
}