gin是一个开源的,用golang开发的web框架,https://github.com/gin-gonic/gin 地址如下。它有如下特性:
1,快,基于前缀树(radix tree)的路由策略,占用更小的内存,无须反射。
2,支持中间件,对于一个http请求,可以通过一串链式的中间件处理,然后再作出最后的应答。
3,crash-free,不会crash停服,Gin框架可以捕获http请求中的panic,并恢复。
4,routes group。更好的组织你的路由
5,内置的rendering,gin提供了简单的api使用json,xml,html,pb等
6,扩展性,可以简单的创建自己的中间件
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
示例1应该是最简单的一个例子了,通过http GET 方法访问 /ping 返回一个json格式的应答。简单的几行代码就可以搭建一个简单的可运行的cgi服务。
package main
import "github.com/gin-gonic/gin"
func main() {
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire.
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// Authorization group
// authorized := r.Group("/", AuthRequired())
// exactly the same as:
authorized := r.Group("/")
// per group middleware! in this case we use the custom created
// AuthRequired() middleware just in the "authorized" group.
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
// nested group
testing := authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
示例2,将服务挂在8080端口下,这个示例涉及到中间件,route group。相对来说会复杂点。以中间件来说,当匹配到“/benchmark” 这个cgi的时候,他的处理会经过一个链式的处理,这个http请求到来的时候会先后经过,gin.Logger(),gin.Recover(),MyBenchLoggerer()以及最后的benchEndpoint的处理。对于下面的route group,则是一个group下的cig,共有一个middleware。示例的意思就是说在调用这些cgi的handler之前,都会先调用到鉴权服务authRequired.
// 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
}
// 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()
}
type Engine struct {
RouterGroup
.......
}
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
Use调用就是构造了一个HandlersChain,并保存在RouterGroup 结构中。
以GET为例
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("POST", relativePath, handlers)
}
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 (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)
}
// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers HandlersChain) {
..............
}
当调用GET/POST的时候,都会调用到handle(...),该调用又两个地方比较重要:
1,combineHandlers,该调用将之前Use中添加的middleware与此处GET/POST中的handler 合并起来了。并且其顺序是middleware在前。同时我们也可以看到,只有在GET调用之前的middleware,在对应的http请求到来时才会被调用到。举个例子:
r.Use(A)
r.GET("/ping",B)
r.Use(C)
如果调用顺序是这样的,那么GET请求/ping 这个cgi的时候,请求会经过A->B的处理,不会经过C.
2,addRoute,该调用是将对应的请求挂到engine.trees上,每类请求方式(GET/POST..)一颗前缀树。使用前缀树(radix tree)可以节省存储空间,以及提高查找效率。需要说明的是,添加前缀树节点的时候是线程不安全的。
// 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
}
func ListenAndServe(addr string, handler Handler) error {
..............
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
http.ListenAndServe的内部是go自身的网络框架调用,这里就不深入下去。我们可以看到Run中ListenAndServe传入的是engine,而需要的的参数是Handler类型,显然我们知道,这个网络框架不论内部怎么调用,最后都会回调到func(engine *Engine)ServeHTTP()这个函数中。
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
func (engine *Engine) handleHTTPRequest(c *Context) {
...................
...................
// 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
value := root.getValue(rPath, c.Params, unescape)
if value.handlers != nil {
c.handlers = value.handlers
c.Params = value.params
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
...............
}
}
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
gin.Context主要是用来做参数传递的。handleHTTPRequest这里就是整个调用的关键了。首先会根据http方法(GET/POST..)找到对应的前缀树。然后再通过http请求的路径(比如/ping),找到对应的节点(nodeValue),这个节点里面就保存了前面添加的middleware以及最后的处理函数。
我们看这个c.Next(),它会从当前位置开始,遍历整个handlers,然后调用相应的函数(c.handlers[c.index](c)).
回包一般都是调用链的最后一个函数来进行回包,比如示例1中的代码:
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
回包就是GET中的匿名函数回包的,返回一个json应答。
// 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{}) {
c.Render(code, render.JSON{Data: obj})
}
// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj})
}
// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj})
}
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj interface{}) {
c.Render(code, render.ProtoBuf{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)
}
}
// 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
}
我们可以看到,c.JSON(),最后会通过WriteJSON调用回包。此外,我们也可以看到gin内置的应答格式还是非常多的。原生支持json,xml,yaml和pb以及其它的一些格式。
目前部门内的web框架都是基于java的jungle框架,比较老了。go作为后起之秀,得益于其语言与性能的优势,目前部门内新服务都是用go开发,需要重构的老服务也在用go迁移重构。对于go的web框架,目前再调研中,故将gin框架大体熟悉了解了下。作文记录下。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。