Gin框架
1. context的用处
Golang 甚至把用 Context 称为一种并发模式,标准库中定义了 Context 接口,并且在多种第三方库中都有支持。
其实原因和 goroutine 有关,goroutine 易于创建,更容易泄露,而且也没有什么通用的接口结束一个 goroutine(甚至连 ID 都拿不到)。
所以 Context 模式声明一些接口,显式传递给子函数,子函数的 goroutine 主动检查 Context 的状态并作出正确的响应。
Context 是一种接口,相同请求范围内的 goroutine 需要主动检查 Context 的状态来进行合适的处理:
Done() <-chan struct{
}:返回一个管道,当 Context 取消或者超时的时候会被关闭
Err() error:返回 Done 管道关闭的原因
Deadline() (deadline time.Time, ok bool):返回将要超时的时间
Value(key interface{
}) interface{
}:返回 Context 的值
Golang 建议 Context 作为第一个参数主动进行传递,表示子函数位于指定的上下文中,比如轻量级 Web 框架 gin 的每个请求在独立的 goroutine 中执行,每个 view 函数接受一个 Context 结构,此结构实现了 Context 接口,如进行 streaming 处理:
func Messages(ctx *gin.Context) {
messagesCh := make(chan interface{
}, 10)
queryMessages(ctx, messagesCh)
ctx.Stream(func(w io.Writer) bool {
select {
case message, ok := <-messagesCh:
if ok {
w.Write(MessageToBytes(message))
}
return ok
case <-ctx.Done():
return false
}
})
}
假设 queryMessages 函数会启动 goroutine 来查询数据,通过 messagesCh 返回数据,ctx.Stream 的回调会不断轮询 messagesCh 来向客户端返回数据,同时也关注 Context 是否结束并提前结束请求。
同时 Context 支持嵌套:
父 context 关闭同时也会关闭子 context。
切记不要传递 nil 作为 context,如果不确定使。用哪个具体的类型,请传递 context.TODO。
Next方法——它的作用就是先执行以下一个中间件,执行完了再回来继续执行接下来的逻辑。
Abort方法——中断操作,用于阻断请求。
实例:
func CheckToken(c *gin.Context) {
session, err := GetSession(c)
if err != nil {
if err == http.ErrNoCookie {
log.Errorf("In CheckToken GetSession fail: %v", err)
c.Error(ErrUnauthorized)
} else if err == badger.ErrKeyNotFound {
log.Errorf("In CheckToken GetSession fail: %v", err)
c.Error(ErrOutdatedSession)
} else {
log.Errorf("In CheckToken GetSession fail: %v", err)
c.Error(ErrInternalServerError)
}
c.Abort()
return
}
log.Infof("session: %+v", session)
c.Set("sid", session.Sid)
c.Set("user_id", session.UserID)
c.Set("user_name", session.UserName)
RefreshSession(session)
c.Next()
}
https://www.zhihu.com/question/269905592/answer/359259436
2. http.HandlerFunc()
//http.Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
一般当创建一个如下的方法时,可通过http.HandlerFunc(name)转换为Handler类型,因为HanderFunc里有ServeHttp方法,实现了Handler接口。
type HandlerFunc func(ResponseWriter, *Request)
func (HandlerFunc) ServeHTTP
示例:
// ErrorHandler 对错误结果统一处理
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
errs := c.Errors.ByType(gin.ErrorTypeAny)
if len(errs) > 0 {
err := errs.Last().Err
switch err.(type) {
case *ApiError:
parsedErr := err.(*ApiError)
c.JSON(parsedErr.HTTPCode, wrapFailResponse(parsedErr.StatusCode, parsedErr.Status))
return
default:
}
}
}
}
3. gin路由配置
1. 基本路由
gin框架封装了http库,提供了 GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS 这些http请求方式。
使用 router.method() 来绑定路由:
router := gin.Default()
router.GET("/get", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "get方法"}) })
router.POST("/post", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "post方法"}) })
router.PUT("/put", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "put方法"}) })
router.DELETE("/delete", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "delete"}) })
router.PATCH("/patch", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "patch"}) })
router.HEAD("/head", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "head"}) })
router.OPTIONS("/options", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "options"}) })
router.Run(":9999")//指定端口 localhost:9999
2.分组路由
func serverAuthAPI(router *gin.Engine) {
r := router.Group("/api/auth")
r.POST("/login", LoginHandler)
r.POST("/logout", CheckToken, ShouldChangePassword, LogoutHandler)
r.GET("/captcha", GenerateCaptchaHandler)
}
//没有路由的页面
//为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码
htmlDir := config.GetConfig().ClientControlServer.HTMLDir
router.Use(static.Serve("/", static.LocalFile(htmlDir, false)))
router.NoRoute(func(c *gin.Context) {
c.File(filepath.Join(htmlDir, static.INDEX))
})
3. 设置中间件
Gin Mode的选择:开发调试过程中,使用debug模式就可以了。在上线的时候,一定要选择release模式。test可以用在测试场景中。
gin.SetMode(gin.DebugMode)
router := gin.New()
router.Use(LoggerByTime())//中间件:日志
router.Use(gin.Recovery())
router.Use(Cors())//中间件:跨域
router.Use(ErrorHandler()) //统一错误处理
// ErrorHandler 对错误结果统一处理
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next() //先按序执行相应服务,结果返回时才进入错误信息处理
errs := c.Errors.ByType(gin.ErrorTypeAny)
if len(errs) > 0 {
err := errs.Last().Err
switch err.(type) {
case *ApiError:
parsedErr := err.(*ApiError)
c.JSON(parsedErr.HTTPCode, wrapFailResponse(parsedErr.StatusCode, parsedErr.Status))
return
default:
}
}
}
}
https://blog.csdn.net/yuyinghua0302/article/details/105296475
4. gin获取URL参数
1. 获取URL路径全部参数
以/为分割符,每个参数以“:”为参数表示动态变量,会自动绑定到路由对应的参数上
路由规则:[:]表示可以不用匹配
比如:http://localhost:8080/user/李四/20/北京/男 将匹配 “http://localhost:8080/user/:name/:age/:address/:sex”
上面的这个链接中,可以通过向上面讲的使用/user/:name/:age/:address/:sex来分别匹配李四、20、北京、男
获取参数:
//http://localhost:8080/user/李四/20/北京/男
router.GET("/user/:name/:age/:address/:sex", func(c *gin.Context) {
//打印URL中所有参数
//"[{name 李四} {age 20} {address 北京} {sex 男}]\n"
c.JSON(http.StatusOK, fmt.Sprintln(c.Params))
})
2. 获取URL路径单个参数
使用gin.Context对象的Param(key)方法获取某一个key的值,方法声明如下:
//http://localhost:8080/login/15949629528/123456
router.GET("/login/:name/:password", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
//{ name: "15949629528", password: "123456" }
"name": c.Param("name"),
"password": c.Param("password"),
})
})
访问:http://localhost:8080/login/15949629528/123456
输出结果:{ name: “15949629528”, password: “123456” }
3. 获取URL中指定的参数【GET、POST请求】
比如:http://localhost:8080/login?name=张三&password=123456
可以使用接下在的方法获取请求参数name、password的值:
//返回URL中key的值
func (c *Context) Query(key string) string
//GET请求
router.GET("/login", func(c *gin.Context) {
//{ name: "张三", password: "123456" }
c.JSON(http.StatusOK, gin.H{
"name": c.Query("name"),
"password": c.Query("password"),
})
})
//POST请求
router.POST("/login", func(c *gin.Context) {
//{"name":"张三","password":"123456"}
c.JSON(http.StatusOK, gin.H{
"name": c.Query("name"),
"password": c.Query("password"),
})
})
访问:http://localhost:8080/login?name=张三&password=123456
输出结果:{ name: “张三”, password: “123456” }
4. 获取指定默认值的参数的【GET、POST请求】
gin.Context.DefaultQuery()方法,允许你指定接收的参数名,以及没有接收到该参数值时,设置的默认值,声明如下:
func (c *Context) DefaultQuery(key, defaultValue string) string
只有当请求没有携带key,那么此时的默认值就会生效。其他情况,默认值不生效。即使URL中的该key的值为空,那么也不会启用默认值,获取的值就是空。
获取URL中的参数值:
//GET请求
router.GET("/user", func(c *gin.Context) {
//{ name: "张三", password: "123456" }
c.JSON(http.StatusOK, gin.H{
"name": c.DefaultQuery("name", "默认张三"),
"password": c.DefaultQuery("password", "默认密码"),
})
})
//POST请求
router.POST("/user", func(c *gin.Context) {
//{"name":"张三","password":"默认密码"}
c.JSON(http.StatusOK, gin.H{
"name": c.DefaultQuery("name", "默认张三"),
"password": c.DefaultQuery("password", "默认密码"),
})
})
访问:http://localhost:8080/user?password=
输出结果:{ name: “默认张三”, password: “默认密码” }