[Gin] 单文件极简 HTTP Server 流程分析 ( gin-gonic/gin )

/**
* example.go
*
* @link https://cnblogs.com/farwish
*/
package main import
"github.com/gin-gonic/gin" func main() {
   // 由于是外部调用包,所以必须含包名 gin. 作为前缀
   // Default 返回带有已连接 Logger 和 Recovery 中间件的 Engine 实例。
r := gin.Default()

   // Engine 结构体中内嵌了 RouterGroup 结构体,即继承了 RouterGroup(其有成员方法 GET、POST、DELETE、PUT、ANY 等) r.GET(
"/ping", func(c *gin.Context) {

     // 使用 context.go 提供的方法渲染 json
     // 关于 gin.H 可看这里:https://www.cnblogs.com/farwish/p/12628549.html c.JSON(
200, gin.H{ "message": "pong", })
})

  // 默认是 0.0.0.0:8080 端口,内部使用了 http.ListenAndServe(address, engine) r.Run("9090")
// listen and serve on 0.0.0.0:9090 }

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/gin.go
// gin.go

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
  // 打印 WARNING 信息,见 debug.go debugPrintWARNINGDefault()

  // 取得一个新的空 Engine 实例 engine :
= New()

  // 添加路由的全局中间件 engine.Use(Logger(), Recovery())
return engine } // Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Create an instance of Engine, by using New() or Default() type Engine struct { RouterGroup // Enables automatic redirection if the current route can't be matched but a // handler for the path with (without) the trailing slash exists. // For example if /foo/ is requested but a route only exists for /foo, the // client is redirected to /foo with http status code 301 for GET requests // and 307 for all other request methods. RedirectTrailingSlash bool // If enabled, the router tries to fix the current request path, if no // handle is registered for it. // First superfluous path elements like ../ or // are removed. // Afterwards the router does a case-insensitive lookup of the cleaned path. // If a handle can be found for this route, the router makes a redirection // to the corrected path with status code 301 for GET requests and 307 for // all other request methods. // For example /FOO and /..//Foo could be redirected to /foo. // RedirectTrailingSlash is independent of this option. RedirectFixedPath bool // If enabled, the router checks if another method is allowed for the // current route, if the current request can not be routed. // If this is the case, the request is answered with 'Method Not Allowed' // and HTTP status code 405. // If no other Method is allowed, the request is delegated to the NotFound // handler. HandleMethodNotAllowed bool ForwardedByClientIP bool // #726 #755 If enabled, it will thrust some headers starting with // 'X-AppEngine...' for better integration with that PaaS. AppEngine bool // If enabled, the url.RawPath will be used to find parameters. UseRawPath bool // If true, the path value will be unescaped. // If UseRawPath is false (by default), the UnescapePathValues effectively is true, // as url.Path gonna be used, which is already unescaped. UnescapePathValues bool // Value of 'maxMemory' param that is given to http.Request's ParseMultipartForm // method call. MaxMultipartMemory int64 // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. // See the PR #1817 and issue #1644 RemoveExtraSlash bool delims render.Delims secureJsonPrefix string HTMLRender render.HTMLRender FuncMap template.FuncMap allNoRoute HandlersChain allNoMethod HandlersChain noRoute HandlersChain noMethod HandlersChain pool sync.Pool trees methodTrees } // New returns a new blank Engine instance without any middleware attached. // By default the configuration is: // - RedirectTrailingSlash: true // - RedirectFixedPath: false // - HandleMethodNotAllowed: false // - ForwardedByClientIP: true // - UseRawPath: false // - UnescapePathValues: true func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ Handlers: nil, basePath: "/", root: true, }, FuncMap: template.FuncMap{}, RedirectTrailingSlash: true, RedirectFixedPath: false, HandleMethodNotAllowed: false, ForwardedByClientIP: true, AppEngine: defaultAppEngine, UseRawPath: false, RemoveExtraSlash: false, UnescapePathValues: true, MaxMultipartMemory: defaultMultipartMemory, trees: make(methodTrees, 0, 9), delims: render.Delims{Left: "{{", Right: "}}"}, secureJsonPrefix: "while(1);", } engine.RouterGroup.engine = engine engine.pool.New = func() interface{} { return engine.allocateContext() } return engine }


func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
    assert1(path[0] == '/', "path must begin with '/'")
    assert1(method != "", "HTTP method can not be empty")
    assert1(len(handlers) > 0, "there must be at least one handler")

    debugPrintRoute(method, path, handlers)
    root := engine.trees.get(method)
    if root == nil {
        root = new(node)
        root.fullPath = "/"
        engine.trees = append(engine.trees, methodTree{method: method, root: root})
    }
    root.addRoute(path, handlers)
}

// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
    defer func() { debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s
", address)
    err = http.ListenAndServe(address, engine)
    return
}

// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
    engine.RouterGroup.Use(middleware...)
    engine.rebuild404Handlers()
    engine.rebuild405Handlers()
    return engine
}

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/routergroup.go

// routergroup.go

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    return &RouterGroup{
        Handlers: group.combineHandlers(handlers),
        basePath: group.calculateAbsolutePath(relativePath),
        engine:   group.engine,
    }
}

// BasePath returns the base path of router group.
// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
func (group *RouterGroup) BasePath() string {
    return group.basePath
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.Handlers) + len(handlers)
    if finalSize >= int(abortIndex) {
        panic("too many handlers")
    }
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.Handlers)
    copy(mergedHandlers[len(group.Handlers):], handlers)
    return mergedHandlers
}

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}

func (group *RouterGroup) returnObj() IRoutes {
    if group.root {
        return group.engine
    }
    return group
}

// Handle registers a new request handle and middleware with the given path and method.
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
// See the example code in GitHub.
//
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
// functions can be used.
//
// This function is intended for bulk loading and to allow the usage of less
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
    if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
        panic("http method " + httpMethod + " is not valid")
    }
    return group.handle(httpMethod, relativePath, handlers)
}

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/context.go

// context.go

// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
  // render.JSON 见 github.com/gin-gonic/gin/render c.Render(code, render.JSON{Data: obj}) }
// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) if !bodyAllowedForStatus(code) { r.WriteContentType(c.Writer) c.Writer.WriteHeaderNow() return } if err := r.Render(c.Writer); err != nil { panic(err) } }

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/render/render.go

// render/render.go

package render

import "net/http"

// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
type Render interface {
    // Render writes data with custom ContentType.
    Render(http.ResponseWriter) error
    // WriteContentType writes custom ContentType.
    WriteContentType(w http.ResponseWriter)
}

var (
    _ Render     = JSON{}
    _ Render     = IndentedJSON{}
    _ Render     = SecureJSON{}
    _ Render     = JsonpJSON{}
    _ Render     = XML{}
    _ Render     = String{}
    _ Render     = Redirect{}
    _ Render     = Data{}
    _ Render     = HTML{}
    _ HTMLRender = HTMLDebug{}
    _ HTMLRender = HTMLProduction{}
    _ Render     = YAML{}
    _ Render     = Reader{}
    _ Render     = AsciiJSON{}
    _ Render     = ProtoBuf{}
)

func writeContentType(w http.ResponseWriter, value []string) {
    header := w.Header()
    if val := header["Content-Type"]; len(val) == 0 {
        header["Content-Type"] = value
    }
}

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/render/json.go

// rendor/json.go

//
JSON contains the given interface object. type JSON struct { Data interface{} } var jsonContentType = []string{"application/json; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonAsciiContentType = []string{"application/json"} // Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) } return } // WriteContentType (JSON) writes JSON ContentType. func (r JSON) WriteContentType(w http.ResponseWriter) { writeContentType(w, jsonContentType) } // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { return err } _, err = w.Write(jsonBytes) return err }

https://sourcegraph.com/github.com/gin-gonic/gin/-/blob/debug.go

// debug.go

func debugPrintWARNINGDefault() {
    if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
        debugPrint(`[WARNING] Now Gin requires Go 1.11 or later and Go 1.12 will be required soon.

`)
    }
    debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

`)
}

func debugPrintWARNINGNew() {
    debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:    export GIN_MODE=release
 - using code:    gin.SetMode(gin.ReleaseMode)

`)
}

func debugPrint(format string, values ...interface{}) {
    if IsDebugging() {
        if !strings.HasSuffix(format, "
") {
            format += "
"
        }
        fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
    }
}

// IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.ReleaseMode) to disable debug mode.
func IsDebugging() bool {
    return ginMode == debugCode
}

Link: https://www.cnblogs.com/farwish/p/12701654.html
原文地址:https://www.cnblogs.com/farwish/p/12701654.html