共计 3901 个字符,预计需要花费 10 分钟才能阅读完成。
本篇次要介绍 gin 服务启动过程的源码
Run() 启动入口
咱们的程序都是通过调用 Run 函数来启动 gin 的实例,上面来看一下 Run 的源码:
func (engine *Engine) Run(addr ...string) (err error) {defer func() {debugPrintError(err) }()
// 解析服务地址
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
// 此处会 block
err = http.ListenAndServe(address, engine)
return
}
该办法其实是对 http.ListenAndServe 的简略封装,以下逻辑就进入 net/http 包中了,持续往下看:
func ListenAndServe(addr string, handler Handler) error {
// 配置 tcp 的监听地址和监听到来后的处理函数
server := &Server{Addr: addr, Handler: handler}
// 持续调用。。。return server.ListenAndServe()}
// Handler 定义
type Handler interface {
// 申请到来后的解决逻辑,gin 框架会本人实现
ServeHTTP(ResponseWriter, *Request)
}
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)
}
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()
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
// 创立一个新连贯,一个 conn 对象,代表服务端的 http 连贯,里边蕴含该次申请的数据
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
// 开启一个 goroutine 解决申请,将上下文传递进去,高并发的保障
go c.serve(connCtx)
}
}
// serve 函数只摘取最重要的一行
func (c *conn) serve(ctx context.Context) {
...
// 这里就会调用 gin 框架实现的 ServeHTTP 逻辑
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
ServeHTTP 逻辑
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从对象池子里获取一个 Context 对象,池子里有能够间接拿来用,没有则创立一个新的对象返回
c := engine.pool.Get().(*Context)
// 把申请的相干数据都写入 Context
c.writermem.reset(w)
c.Request = req
c.reset()
// 申请解决逻辑
engine.handleHTTPRequest(c)
// 应用结束,放回池子,复用
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
rPath = cleanPath(rPath)
// Find root of the tree for the given HTTP method
t := engine.trees
// 遍历路由数组,依据申请办法找出对应的那棵树
for i, tl := 0, len(t); i < tl; i++ {if t[i].method != httpMethod {continue}
root := t[i].root
// Find route in tree
// 从树中找到路由对应的函数解决链和参数
handlers, params, tsr := root.getValue(rPath, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
// 按程序执行 handler 办法,能够配合在中间件中应用
c.Next()
// handlers 中的所有函数都处理完毕后,该返回了
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && rPath != "/" {
if tsr && engine.RedirectTrailingSlash {redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {return}
}
break
}
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {continue}
if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
// 看一下 Next 办法,很简略
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {c.handlers[c.index](c)
c.index++
}
}
总结
- 启动流程很清晰,通过 net/http 包监听申请,外围逻辑就是一个死循环,有限期待,当有申请达到指定端口时,启动一个 goroutine 异步解决该申请。
- 申请的解决逻辑应用的是 gin 框架本人实现的 ServeHTTP 函数,gin 对 Conext 的封装和复用,也是一大亮点。
- context 的 Next 办法在中间件中的应用,能够实现后置中间件。
正文完