浅析Golang中的net/http路由注册与请求处理
注册路由
初学web时,我们常用http.HandleFunc()来注册路由,然后用http.ListenAndServe()来启动服务,接下来让我们分析一下http包内部是如何注册路由的。
除了常用的http.HandleFunc()可以注册路由,还有http.Handle可以注册,先看一下源码。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
对比一下两个函数的不同:
1、分别调用了DefaultServeMux的Handle和HandleFunc方法。
2、handler参数类型分别为http.Handler接口,和func(ResponseWriter, *Request)类型。说明一下,DefaultServerMux是http包的全局变量,如果不使用默认的复用器
接下来看一下Handle和HandleFunc的主要源码,和Handler接口
// 注册路由 func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() ... if mux.m == nil { mux.m = make(map[string]muxEntry) } e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e ... } // 调用Handle注册路由 func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) } // 实现了Handler的函数类型 type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
可以看到ServeMux.Handle最终实现了路由的注册,mux.m记录了路由与处理器的映射;而ServeMux.HandleFunc将handler参数转换成了HandlerFunc类型,然后调用ServeMux.Handle。
那么问题来了,ServeMux.Handle的handler参数是Handler接口类型,我们调用http.HandleFunc()传入的处理器函数签名是func(ResponseWriter, *Request),我们传入的函数咋就实现了Handler接口?
答案就在于HandlerFunc类型,它实现了Handler接口。我们传入的处理器函数与HandlerFunc类型函数签名是一致的,如果没有HandlerFunc,要注册函数的话,我们就要自己定义结构体,写ServeHTTP方法,实现Handler接口,而有了HandlerFunc我们就可以把这一步省去了,在设计模式中,这叫装饰器模式。
处理请求
ServerMux
使用http.HandleFunc和http.Handle注册的路由都注册到了DefaultServerMux,它也实现了handler接口,那让我们来看一下ServerMux的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) }
mux.Handler()中会调用mux.match(),从ServeMux.m(map[string]muxEntry类型),获取路由和对应处理器函数(我们传入的),然后就可以调用h.ServeHTTP(w,r)来处理对应的请求。
现在已得知我们传入的处理函数是被ServeMux.ServeHTTP()调用,那ServerMus.ServeHTTP()又是怎么被调用的呢?接下来跟踪一下http.ListenAndServe()的源码。
Server
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
用http.ListenAndServe()启动服务的话,handler参数一般传nil,使用DefaultServerMux做处理器,下面的分析都以此为前提。
在函数内部帮我们创建了一个Server结构体,如果想更灵活地使用,可以自己创建Server结构体,调用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) }
Server.ListenAndServe()中完成了监听地址的绑定,然后再调用Server.Serve()
func (srv *Server) Serve(l net.Listener) error { ... ctx := context.WithValue(baseCtx, ServerContextKey, srv) for { rw, err := l.Accept() ... connCtx := ctx if cc := srv.ConnContext; cc != nil { connCtx = cc(connCtx, rw) if connCtx == nil { panic("ConnContext returned nil") } } c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) go c.serve(connCtx) } }
Server.Serve中开启了一个for循环来接收连接,并为每一个连接创建contexxt,开一个协程继续处理。
func (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) var inFlightResponse *response defer func() { if err := recover(); err != nil && err != ErrAbortHandler { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) } if inFlightResponse != nil { inFlightResponse.cancelCtx() } if !c.hijacked() { if inFlightResponse != nil { inFlightResponse.conn.r.abortPendingRead() inFlightResponse.reqBody.Close() } c.close() c.setState(c.rwc, StateClosed, runHooks) } }() ... for { w, err := c.readRequest(ctx) ... inFlightResponse = w serverHandler{c.server}.ServeHTTP(w, w.req) // 这里并不是Handler接口的ServeHTTP方法 inFlightResponse = nil w.cancelCtx() if c.hijacked() { return } ... } ... }
在http包server.go 1991行,嵌套了Server结构体的serverHandler结构体调用ServeHTTP方法,需要注意的是这并不是Handler接口的ServeHTTP方法,我们传入的处理器函数的调用还在其内部。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux // 如果在创建Server时,Handler参数为nil,就使用DefaultServeMux // 通过http.ListenAndServe开启服务一般都用DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") { var allowQuerySemicolonsInUse int32 req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() { atomic.StoreInt32(&allowQuerySemicolonsInUse, 1) })) defer func() { if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 { sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192") } }() } handler.ServeHTTP(rw, req) // 调用DefaultServeMux的ServeHTTP }
最后一行调用DefaultServeMux的ServeHTTP,上文已介绍过,它的作用就是获取到请求对应的处理器函数并执行。
延伸
gin框架是基于http包封装的,gin匹配路由的方式不同于原生包,使用前缀路由树来匹配路由,通过engin.Run启动服务,其内部也是调用的http.ListenAndServe(),那gin是如何应用的自定义匹配方式呢?其实很简单,上文提到,调用http.ListenAndServe()时第二个参数handler是nil的话,会使用DefaultServeMux当做复用器,那engin.Run()中调用时传入gin定义的复用器就好了。
func (engine *Engine) Run(addr ...string) (err error) { defer func() { debugPrintError(err) }() if engine.isUnsafeTrustedProxies() { debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" + "Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.") } address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n", address) err = http.ListenAndServe(address, engine.Handler()) // engin.Handler()返回gin的自定义处理器 return }
以上就是浅析Golang中的net/http路由注册与请求处理的详细内容,更多关于go net/http的资料请关注脚本之家其它相关文章!
相关文章
Go strconv包实现字符串和基本数据类型转换的实例详解
在Go语言(Golang)的编程实践中,strconv包是一个非常重要的标准库,它提供了在基本数据类型(如整型、浮点型、布尔型)和字符串之间的转换功能,本文给大家介绍了关于Go语言字符串转换strconv,需要的朋友可以参考下2024-09-09golang中channel+error来做异步错误处理有多香
官方推荐golang中错误处理当做值处理, 既然是值那就可以在channel中传输,这篇文章主要介绍了golang 错误处理channel+error真的香,需要的朋友可以参考下2023-01-01
最新评论