gin框架中的路由是基于httprouter开发的。HelloWorld:
package main
import (
"github.com/gin-gonic/gin"
"fmt"
)
func main() {
r := gin.Default() //Default返回一个默认路由引擎
r.GET("/", func(c *gin.Context) {
username := c.Query("username")
fmt.Println(username)
c.JSON(200, gin.H{
"msg":"hello world",
})
})
r.Run() //默认位于0.0.0.0:8080,可传入参数":3030";也可以绑定服务器
}
c.Query("username") c.QueryDefault("username","lisi") //如果username为空,则赋值为lisi name := c.Param("name") //路由地址为:/user/:name/:pass,获取参数 reqPara := c.Request.URL.Query() //获取Get的所有参数
name := c.PostForm("name") price := c.DefaultPostForm("price", "100") reqPost = c.Request.PostForm //获取 Post 所有参数
参数绑定利用反射机制,自动提取querystring,form表单,json,xml等参数到结构体中,可以极大提升开发效率。
package main import ( "net/http" "github.com/gin-gonic/gin" "fmt" ) type User struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func login(c *gin.Context) { var user User fmt.Println(c.PostForm("username")) fmt.Println(c.PostForm("password")) err := c.ShouldBind(&user) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error() }) } c.JSON(http.StatusOK, gin.H{ "username": user.Username, "password": user.Password, }) } func main() { router := gin.Default() router.POST("/login", login) router.Run(":3000") }
静态化当前目录下static文件夹:
router := gin.Default() router.Static("/static", "./static") router.Run(":3000")
注意:同样推荐使用go build,不要使用开发工具的run功能。
c.JSON(200,gin.H{"msg":"OK"}) c.JSON(200,结构体)
router.LoadHTMLGlob("templates/**/*") router.GET("/test/index", func(c *gin.Context){ c.HTML(http.StatusOK, "test/index.tmpl", gin.H{ "msg": "test", }) })
模板文件:index.tmpl
{{define "test/index.tmpl"}} <html> <head> </head> <body> test... {{.}} ----- {{.msg}} </body> </html> {{end}}
注意事项:不要使用编辑器的run功能,会出现路径错误,推荐使用命令build,项目路径分配如下:
gin-01.png
router.POST("/upload", func (c *gin.Context) { file, err := c.FormFile("file") if (err != nil) { c.JSON(http.StatusInternalServerError, gin.H{ "msg": err.Error(), }) return } dst := fmt.Sprintf("/uploads/&s", file.Filename) c.SavaeUpLoadedFile(file, dst) c.JSON(http.StatusOK, gin.H{ "msg":"ok", }) })
router.POST("/upload", func(c *gin.Context) { // 多文件 form, _ := c.MultipartForm() files := form.File["upload[]"] for _, file := range files { log.Println(file.Filename) // 上传文件到指定的路径 // c.SaveUploadedFile(file, dst) } c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files))) })
访问路径是:/user/login
和 /user/signin
package main import ( "github.com/gin-gonic/gin" ) func login(c *gin.Context) { c.JSON(300, gin.H{ "msg": "login", }) } func logout(c *gin.Context) { c.JSON(300, gin.H{ "msg": "logout", }) } func main() { router := gin.Default() user := router.Group("/user") { user.GET("/login", login) user.GET("/logout", logout) } router.Run(":3000") }
笔者自己的路由设计,仅供参考:
项目结构如图:
gin-02.png
main.go:
package main import ( "Demo1/router" ) func main() { r := router.InitRouter() _ = r.Run() }
routes.go:
package router import ( "github.com/gin-gonic/gin" ) func InitRouter() *gin.Engine { r := gin.Default() // 路由模块化 userRouter(r) orderRouter(r) return r }
userRouter.go示例:
package router import ( "github.com/gin-gonic/gin" "net/http" ) func userRouter(r *gin.Engine) { r.GET("/user/login", userLogin) } func userLogin(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 10001, "msg": "登录成功", "data": nil, }) }
https://github.com/stretchr/testify/assert 是个很好的单元测试框架。
在上一节中配置了笔者自己项目的路由模块化思路,下面是配套的单元测试demo:
userRouter_test.go
package test import ( "Demo1/router" "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "testing" ) func TestUserRouter_userLogin(t *testing.T) { r := router.InitRouter() w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/user/login", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, `{"code":10001,"data":null,"msg":"登录成功"}`, w.Body.String()) }
gin框架允许在处理请求时,加入用户自己的钩子函数,该钩子函数即中间件。他的作用与Java中的拦截器,Node中的中间件相似。
中间件需要返回gin.HandlerFunc
函数,多个中间件通过Next函数来依次执行。
现在设计一个中间件,在每次路由函数执行前打印一句话,在上一节的项目基础上新建middleware
文件夹,新建一个中间件文件MyFmt.go
:
package middleware import ( "fmt" "github.com/gin-gonic/gin" ) // 定义一个中间件 func MyFMT() gin.HandlerFunc { return func(c *gin.Context) { host := c.Request.Host fmt.Printf("Before: %s\n",host) c.Next() fmt.Println("Next: ...") } }
在路由函数中使用中间件:
r.GET("/user/login", middleware.MyFMT(), userLogin)
打印结果:
Before: localhost:8080 Next: ... [GIN] 2019/07/28 - 16:28:16 | 200 | 266.33µs | ::1 | GET /user/login
全局中间件:直接使用 gin.Engine
结构体的Use()
方法,中间件将会在项目的全局起作用。
func InitRouter() *gin.Engine { r := gin.Default() // 全局中间件 r.Use(middleware.MyFMT()) // 路由模块化 userRouter(r) orderRouter(r) return r }
路由分组中使用中间件:
router := gin.New() user := router.Group("user", gin.Logger(),gin.Recovery()) { user.GET("info", func(context *gin.Context) { }) user.GET("article", func(context *gin.Context) { }) }
单个路由使用中间件(支持多个中间件的使用):
router := gin.New() router.GET("/test",gin.Recovery(),gin.Logger(),func(c *gin.Context){ c.JSON(200,"test") })
Gin也内置了一些中间件,可以直接使用:
func BasicAuth(accounts Accounts) HandlerFunc func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定 func ErrorLogger() HandlerFunc //错误日志处理 func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理 func Logger() HandlerFunc //日志记录 func LoggerWithConfig(conf LoggerConfig) HandlerFunc func LoggerWithFormatter(f LogFormatter) HandlerFunc func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc func Recovery() HandlerFunc func RecoveryWithWriter(out io.Writer) HandlerFunc func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件 func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件
中间件的最大作用就是拦截过滤请求,比如我们有些请求需要用户登录或者需要特定权限才能访问,这时候便可以中间件中做过滤拦截。
下面三个方法中断请求后,直接返回200,但响应的body中不会有数据:
func (c *Context) Abort() func (c *Context) AbortWithError(code int, err error) *Error func (c *Context) AbortWithStatus(code int) func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) // 中断后可以返回json数据
如果在中间件中调用gin.Context的Next()方法,则可以请求到达并完成业务处理后,再经过中间件后置拦截处理:
func MyMiddleware(c *gin.Context){ //请求前 c.Next() //请求后 }
Engine是框架的入口,是gin框架的核心,通过Engine对象来定义服务路由信息、组装插件、运行服务。不过Engine的本质只是对内置HTTP服务的包装。
gin.Default()
函数会生成一个默认的 Engine 对象,包含2个默认常用插件
func Default() *Engine { engine := New() engine.Use(Logger(), Recovery()) return engine }
在 Gin 框架中,路由规则被分成了最多 9 棵前缀树,每一个 HTTP Method对应一棵 前缀树 ,树的节点按照 URL 中的 / 符号进行层级划分,URL 支持 :name
形式的名称匹配,还支持 *subpath
形式的路径通配符:
// 匹配单节点 named pattern = /book/:id match /book/123 nomatch /book/123/10 nomatch /book/ // 匹配子节点 catchAll mode /book/*subpath match /book/ match /book/123 match /book/123/10
如图所示:
gin-03.jpeg
每个节点都会挂接若干请求处理函数构成一个请求处理链 HandlersChain。当一个请求到来时,在这棵树上找到请求 URL 对应的节点,拿到对应的请求处理链来执行就完成了请求的处理。
type Engine struct { ... trees methodTrees ... } type methodTrees []methodTree type methodTree struct { method string root *node // 树根 } type node struct { path string // 当前节点的路径 ... handlers HandlersChain // 请求处理链 ... } type HandlerFunc func(*Context) type HandlersChain []HandlerFunc
Engine 对象包含一个 addRoute 方法用于添加 URL 请求处理器,它会将对应的路径和处理器挂接到相应的请求树中:
func (e *Engine) addRoute(method, path string, handlers HandlersChain)
RouterGroup 是对路由树的包装,所有的路由规则最终都是由它来进行管理。Engine 结构体继承了 RouterGroup ,所以 Engine 直接具备了 RouterGroup 所有的路由管理功能,同时 RouteGroup 对象里面还会包含一个 Engine 的指针,这样 Engine 和 RouteGroup 就成了「你中有我我中有你」的关系。
type Engine struct { RouterGroup ... } type RouterGroup struct { ... engine *Engine ... }
RouterGroup 实现了 IRouter 接口,暴露了一系列路由方法,这些方法最终都是通过调用 Engine.addRoute 方法将请求处理器挂接到路由树中。
GET(string, ...HandlerFunc) IRoutes POST(string, ...HandlerFunc) IRoutes DELETE(string, ...HandlerFunc) IRoutes PATCH(string, ...HandlerFunc) IRoutes PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes // 匹配所有 HTTP Method Any(string, ...HandlerFunc) IRoutes
RouterGroup 内部有一个前缀路径属性,它会将所有的子路径都加上这个前缀再放进路由树中。有了这个前缀路径,就可以实现 URL 分组功能。
Engine 对象内嵌的 RouterGroup 对象的前缀路径是 /,它表示根路径。RouterGroup 支持分组嵌套,使用 Group 方法就可以让分组下面再挂分组,依次类推。
当 URL 请求对应的路径不能在路由树里找到时,就需要处理 404 NotFound 错误。当 URL 的请求路径可以在路由树里找到,但是 Method 不匹配,就需要处理 405 MethodNotAllowed 错误。Engine 对象为这两个错误提供了处理器注册的入口。
func (engine *Engine) NoMethod(handlers ...HandlerFunc) func (engine *Engine) NoRoute(handlers ...HandlerFunc)
异常处理器和普通处理器一样,也需要和插件函数组合在一起形成一个调用链。如果没有提供异常处理器,Gin 就会使用内置的简易错误处理器。
注意这两个错误处理器是定义在 Engine 全局对象上,而不是 RouterGroup。对于非 404 和 405 错误,需要用户自定义插件来处理。对于 panic 抛出来的异常需要也需要使用插件来处理。
Gin 不支持 HTTPS,官方建议是使用 Nginx 来转发 HTTPS 请求到 Gin。
gin.Context内保存了请求的上下文信息,是所有请求处理器的入口参数:
type HandlerFunc func(*Context) type Context struct { ... Request *http.Request // 请求对象 Writer ResponseWriter // 响应对象 Params Params // URL匹配参数 ... Keys map[string]interface{} // 自定义上下文信息 ... }
Context 对象提供了非常丰富的方法用于获取当前请求的上下文信息,如果你需要获取请求中的 URL 参数、Cookie、Header 都可以通过 Context 对象来获取。这一系列方法本质上是对 http.Request 对象的包装:
// 获取 URL 匹配参数 /book/:id func (c *Context) Param(key string) string // 获取 URL 查询参数 /book?id=123&page=10 func (c *Context) Query(key string) string // 获取 POST 表单参数 func (c *Context) PostForm(key string) string // 获取上传的文件对象 func (c *Context) FormFile(name string) (*multipart.FileHeader, error) // 获取请求Cookie func (c *Context) Cookie(name string) (string, error) ...
Context 对象提供了很多内置的响应形式,JSON、HTML、Protobuf 、MsgPack、Yaml 等。它会为每一种形式都单独定制一个渲染器。通常这些内置渲染器已经足够应付绝大多数场景,如果你觉得不够,还可以自定义渲染器。
func (c *Context) JSON(code int, obj interface{}) func (c *Context) Protobuf(code int, obj interface{}) func (c *Context) YAML(code int, obj interface{}) ... // 自定义渲染 func (c *Context) Render
0 Comments