[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\n", 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, "\n") { format += "\n" } 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