两学一做晋中市网站,网站制作cms,营销数据网站,专业网站是什么意思要想学好 gin 框架#xff0c;首先要学习 net/http 服务#xff0c;而二者的关系又是重中之重。 本文所要做的任务就是将二者“连接” 起来#xff0c;让读者掌握其中之精髓。 一、Golang HTTP 标准库示例
使用 golang 启动 http 服务非常简单#xff0c;就是一个标准的 C… 要想学好 gin 框架首先要学习 net/http 服务而二者的关系又是重中之重。 本文所要做的任务就是将二者“连接” 起来让读者掌握其中之精髓。 一、Golang HTTP 标准库示例
使用 golang 启动 http 服务非常简单就是一个标准的 C/S 架构服务代码
package mainimport (fmtnet/http
)func pingHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, Hello, net/http! v2\n)
}
func main() {http.HandleFunc(/ping, pingHandler)http.ListenAndServe(:8091, nil)
}这段代码主要完成了两件事
通过 http.HandleFunc 方法注册里 处理函数启动 指定端口的 http 服务。
那背后隐藏了什么呢我们主要致力于挖掘出核心的东西
路径注册、匹配是如何实现的依托的核心是什么 关键词前缀树、暴露接口http 服务的请求路径是怎么样的 关键词one-loop 模型
二、Golang HTTP 标准库 原理
2.1 服务注册
首先我们围绕 http.HandleFunc 源码展开
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)
}// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux defaultServeMuxvar defaultServeMux ServeMuxtype ServeMux struct { // 对 Handler 的具体实现内部通过一个 map 维护了从 path 到 handler 的映射关系.mu sync.RWMutexm map[string]muxEntryes []muxEntry // slice of entries sorted from longest to shortest.hosts bool // whether any patterns contain hostnames
}type muxEntry struct { // 一个 handler 单元内部包含了请求路径 path 处理函数 handler 两部分.h Handlerpattern string
}可以看到是通过默认数据 defaultServeMux 实现的该结构重点包含的方法ServeHTTP 和 HandleFunc
首先讲解下为什么 ServeHTTP 方法很重要因为 ServeMux 是对 Handler 的具体实现
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {h, _ : mux.Handler(r)h.ServeHTTP(w, r)
}
而 Handler 的定义如下
type Handler interface {ServeHTTP(ResponseWriter, *Request)
}Handler 是一个 interface暴露了方法 ServeHTTP该方法根据 http 请求 Request 中的请求路径 path 映射到对应的 handler 处理函数对请求进行处理和响应.
这种实现接口方法有什么好处呢这里我们先留一个悬念之后我们可以在后面的请求流程中看到暂且不表。
其次我们来看 HandleFunc 方法内部会将处理函数 handler 转为实现了 ServeHTTP 方法的 HandlerFunc 类型将其作为 Handler interface 的实现类注册到 ServeMux 的路由 map 当中.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {mux.Handle(pattern, HandlerFunc(handler))
}type HandlerFunc func(ResponseWriter, *Request)// Handle registers the handler for the given pattern.
func (mux *ServeMux) Handle(pattern string, handler Handler) {// 将 path 和 handler 包装成一个 muxEntry以 path 为 key 注册到路由 map ServeMux.m 中
}2.2 服务启动
http.ListenAndServe 通过调用 net/http 包公开的方法实现对服务端的一键启动. 内部定义了一个新的 Server 对象嵌套执行 Server.ListenAndServe 方法
func ListenAndServe(addr string, handler Handler) error {server : Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}Server.ListenAndServe 方法中根据用户传入的端口申请到一个监听器 listener继而调用 Server.Serve 方法.
func (srv *Server) ListenAndServe() error {addr : srv.Addrln, err : net.Listen(tcp, addr)return srv.Serve(ln)
}Server.Serve 方法很核心体现了 http 服务端的运行架构for listener.accept 模式:
func (srv *Server) Serve(l net.Listener) error {ctx : context.WithValue(baseCtx, ServerContextKey, srv)for {rw, err : l.Accept()// ...connCtx : ctx// ...c : srv.newConn(rw)// ...go c.serve(connCtx)}}
}主要实现功能
将 server 封装成一组 kv 对添加到 context 当中开启 for 循环每轮循环调用 Listener.Accept 方法阻塞等待新连接到达每有一个连接到达创建一个 goroutine 异步执行 conn.serve 方法负责处理
其中 conn.serve 是响应客户端连接的核心方法
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {// ...c.r connReader{conn: c}c.bufr newBufioReader(c.r)c.bufw newBufioWriterSize(checkConnErrorWriter{c}, 410)for {w, err : c.readRequest(ctx)// 核心serverHandler{c.server}.ServeHTTP(w, w.req)}可以看下核心的实现
type serverHandler struct {srv *Server
}func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler : sh.srv.Handlerhandler.ServeHTTP(rw, req)
}在 serveHandler.ServeHTTP 方法中会对 Handler 作判断倘若其未声明则取全局单例 DefaultServeMux 进行路由匹配呼应了 http.HandleFunc 中的处理细节。
基于接口而非实现此后开始调用实现的 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()h, pattern mux.match(path)
}三、Gin 框架原理
Gin 是在 Golang HTTP 标准库 net/http 基础之上的再封装两者的交互边界图。
可以看出在 net/http 的既定框架下gin 所做的是提供了一个 gin.Engine 对象作为 Handler 注入其中从而实现路由注册/匹配、请求处理链路的优化。
我们通过一个 简化版 gin进行学习核心思想示例代码
func testMiddle(c *gin.Context) {fmt.Println(middle test)
}func main() {// 构造默认配置的 gin.Engineengine : gin.Default()// 注册中间件engine.Use(testMiddle)// 注册方法engine.Handle(GET, /test, func(c *gin.Context) {fmt.Println(route test)})// 启动 http serverif err : engine.Run(); err ! nil {fmt.Println(err)}
}主要做了几件事
构造默认配置的 gin.Engine注册中间件注册方法启动 http server
gin 是如何与 net/http 链接起来的呢
路由注册与查找gin 的核心结构体 Engine 即实现了该接口:
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {engine.handleHTTPRequest(c)
}
服务启动通过 Engine.Run() 启动 http server 的背后其实是通过 http.ListenAndServe() 启动
func (engine *Engine) Run(addr ...string) (err error) {address : resolveAddress(addr)debugPrint(Listening and serving HTTP on %s\n, address)err http.ListenAndServe(address, engine.Handler())return
}
至此整个文章已经实现了闭环更能够学习到连接的核心思想。 参考
[1]: https://zhuanlan.zhihu.com/p/609258171 Golang HTTP 标准库实现原理 [2]: https://astro.yufengbiji.com/posts/golang/ Golang net/http [3]: https://zhuanlan.zhihu.com/p/611116090 解析 Gin 框架底层原理 [4]: https://blog.csdn.net/weixin_45177370/article/details/135295839?spm1001.2014.3001.5501 Gin 源码深度解析及实现