入参绑定
Bind
根据 Content-Type 可以自动绑定参数,优先绑定 body 中的 Json 或 Form,然后绑定 QueryString。
Bind 如果绑定失败,会返回400状态码,且设置 response 的header的 Content-Type 为 text/plain
ShouldBind 如果绑定失败,后续代码返回指定的 StatesCode 仍然可以生效,Content-Type 仍然是默认的 application/json
ShouldBindJSON
仅匹配 body 传 Json 类型的参数,不绑定 Form 和 QueryString。BindJSON 方法的特性类似如上。
路由
gin框架采用了进行路由匹配,httprouter 是通过基数树(radix tree)来进行高效的路径查找。
前缀树(trie),是一个多叉树,广泛应用于字符串搜索,每个树节点存储一个字符,从根节点到任意一个叶子结点串起来就是一个字符串;基数树(radix tree)是优化之后的前缀树,对空间进一步压缩,如果该节点是唯一的子树,就和父节点合并。
假设有以下路由信息:
r := gin.Default()
r.GET("/", func1)
r.GET("/search/", func2)
r.GET("/support/", func3)
r.GET("/user/", func4)
r.GET("/user/:id/", func5)
r.GET("/about-us/", func6)
r.GET("/about-us/team/", func7)
r.GET("/contact/", func8)
那么我们会得到一个GET方法对应的路由树,具体结构如下:(路由器为每种请求方法管理一棵单独的树,还有POST、PUT、DETELE等)
Priority Path Handle
9 \ *<1>
3 ├s nil
2 |├earch\ *<2>
1 |└upport\ *<3>
2 ├user\ *<4>
1 | └:id nil
1 | └\ *<5>
2 ├about-us\ *<6>
1 | └team\ *<7>
1 └contact\ *<8>
每个树级别上的子节点都按Priority(优先级)
排序,其中优先级(最左列)就是在子节点(子节点、子子节点等等)中注册的句柄的数量,短路径查找速度快,这样设计可以让尽可能多的路由快速被定位。
使用基数树对比于直接用map做路由有什么优势?
路由有/user/:id
这种有参数的类型,用map就不好匹配了,因此我们需要的是根据路由模式进行匹配,而不仅仅是比较哈希值。
路由树的节点类型:
static: 静态节点(默认),比如上面的s,earch等节点
中间件
可以使用应用于全局的中间件,也可以使用应用于某个路由组的中间件,可以使用中间件链按序执行。
c.Next()
c.Next() 仅可以在中间件中使用。c.Next() 会把函数链中挂起的函数(包括中间件和controller)都执行完毕再执行其后续代码,以此可以实现中间件链的嵌套调用,而不是默认的顺序调用。
c.Abort()
c.Abort() 阻止未执行的挂起函数继续被调用,未执行的中间件和controller都不再执行,已执行的函数还会继续执行其后续代码。
应用场景:在鉴权中间件中判断用户无权限之后调用c.Abort()。
c.Set() & c.Get()
中间件和controller都能使用,在不同的中间件和controller之间可以利用这2个函数来传值。
应用场景:在中间件中根据用户传递的cookie或token来判断用户身份,然后c.Set("userId", id)好,controller就可以调用c.Get("userId")直接取出来。
优雅关机
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
// 开启一个goroutine启动服务
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号来优雅地关闭服务器,为关闭服务器操作设置一个5秒的超时
quit := make(chan os.Signal, 1) // 创建一个接收信号的通道
// kill 默认会发送 syscall.SIGTERM 信号
// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞
<-quit // 阻塞在此,当接收到上述两种信号时才会往下执行
log.Println("Shutdown Server ...")
// 创建一个5秒超时的context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown: ", err)
}
log.Println("Server exiting")
}
优雅重启
多台服务器,滚动发布,当1台服务器shutdown时,还有其他服务器可以为用户提供服务,逐个重启即可。