原文地址 基本路由 · Go语言中文文档
一、简介
Gin是一个golang的轻量级web框架,性能不错,API友好。
Gin支持Restful风格的API,可以直接从URL路径上接收api参数或者URL参数,也可是使用json或者表单 数据绑定的方式接收参数。
Gin响应前端的方式,可以使用的方式包括 json、结构体、protobuff的、XML、YAML。
Gin可以使用路由分组的方式管理API,支持api路径重定向。
Gin的路由库用的是 httprouter,路由节点的数据结构是压缩字典树,提高路由效率。
Gin支持全局中间件和局部中间件,支持中间件分段逻辑的Next方法。
Gin支持cookie和Session的方法去识别和验证客户端。
Gin支持 Air 实时监听代码文件自动重新编译执行。
二、gin路由
1. 基本路由
gin 框架中采用的路由库是基于 httprouter 做的,是第三方HTTP路由包,特点高性能、可扩展
(1) 使用方式:
New()函数生成了一个 Router对象 。
GET()方法 或 POST()方法 注册一个适配路径的响应函数
将 Router对象 作为参数传给 ListenAndServe()函数 启动HTTP服务。
router := httprouter.New()
router.GET("/", handleFunc)
http.ListenAndServe(":8080", router)
(2) httprouter 为常用的HTTP方法提供快捷使用方式:
获取: func (r *Router) GET(path string, handle Handle)
添加: func (r *Router) POST(path string, handle Handle)
修改: func (r *Router) PUT(path string, handle Handle)
删除: func (r *Router) DELETE(path string, handle Handle)
(3) httprouter 包中对URL使用两种匹配模式:
形如/user/name的精确匹配
形如/user/*name的匹配所有的模式
(4) 可以处理二级域名。先 根据域名获取对应的Handler路由,然后调用分发机制去处理。
func main() {
......
//分别处理不同的二级域名
hs := make(HostMap)
hs["sub1.localhost:8080"] = userRouter
hs["sub2.localhost:8080"] = dataRouter
//一级域名:localhost,二级域名:sub1.localhost
http.ListenAndServe(":8080", hs)
}
type HostMap map[string]http.Handler
func (hs HostMap) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//先根据域名获取对应的Handler路由,然后调用处理
if handler := hs[r.Host]; handler != nil {
handler.ServeHTTP(w, r)
}
}
2. Restful风格的API
gin支持Restful风格的API,即: URL定位资源,用HTTP描述操作。例如:
(1) 获取文章 /blog/getXxx Get blog/Xxx
(2) 添加 /blog/addXxx POST blog/Xxx
(3) 修改 /blog/updateXxx PUT blog/Xxx
(4) 删除 /blog/delXxxx DELETE blog/Xxx
3. API参数
Api参数就是在Api地址上输入的参数。
可以通过Context的Param方法来获取API参数
func main() {
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
action = strings.Trim(action, "/") //截取/后面的内容
c.String(http.StatusOK, name+" is "+action)
})
r.Run(":8000") //监听8080端口
}
在浏览器输入: http://localhost:8080/user/枯藤/doview,获得name参数"枯藤",action参数 "doview"
4. URL参数
URL参数就是在用Get方式,在?后面输入的参数。
可以通过DefaultQuery()或Query()方法获取URL参数
r.GET("/user", func(c *gin.Context) {
name := c.DefaultQuery("name", "枯藤") //name是参数名称,枯藤是默认值
c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})
在浏览器输入: http://localhost:8080/user?name=乌鸦,获得name参数"乌鸦"
5. 表单参数
(1) 表单传输为 post请求。http常见的传输格式:
application/json
application/x-www-form-urlencoded (默认)
multipart/form-data
(2) 表单参数可以通过 PostForm() 方法获取
r.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("userpassword")
c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s", username, password))
})
6. 上传单个文件
(1) multipart/form-data格式用于文件上传
(2) 通过 FormFile() 方法获得文件对象
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
c.String(http.StatusOK, file.Filename)
})
7. 上传多个文件
(1) 上传多个文件也是用 multipart/form-data格式
(2) 通过 MultipartForm() 方法获得文件对象列表
r.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm()
files := form.File["files"] // 获取所有图片
for _, file := range files { // 遍历所有图片
c.SaveUploadedFile(file, file.Filename)
}
})
8. routes group
routes group(路由分组) 可以分组管理相同的URL。
如下代码,v1和v2分别是两个路由组。
func main() {
r := gin.Default() //创建默认路由
v1 := r.Group("/v1") //路由组1
{ // {} 是书写规范
v1.GET("/login", login)
v1.GET("submit", submit)
}
v2 := r.Group("/v2") //路由组2
{
v2.POST("/login", login)
v2.POST("/submit", submit)
}
r.Run(":8000")
}
9. 路由原理
httprouter使用的数据结构是压缩字典树。
普通的字典树:每个字母都建立一个路由节点
压缩字典树:只对每个有效前缀建立路由节点。如下图所示:
三、gin数据解析和绑定
支持三种方式的数据解析,json、表单 和 url参数
1. Json 数据解析和绑定
使用 context 的 ShouldBindJSON 方法
type Login struct {
User string `json:"user" binding:"required"`
Pssword string `json:"password" binding:"required"`
}
func main() {
r := gin.Default()
r.POST("loginJSON", func(c *gin.Context) {
// 声明接收的变量
var json Login
// 将request的body中的数据,自动按照json格式解析到结构体
if err := c.ShouldBindJSON(&json); err != nil {
......
}
......
})
......
}
2. 表单数据解析和绑定
使用 context 的 Bind 方法
type Login struct {
User string `form:"username" binding:"required"`
Pssword string `form:"password" binding:"required"`
}
func main() {
r := gin.Default()
r.POST("/loginForm", func(c *gin.Context) {
var form Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.Bind(&form); err != nil {
......
}
......
})
......
}
3. URI数据解析和绑定
使用 context 的 ShouldBindUri 方法
type Login struct {
User string `uri:"user" xml:"user" binding:"required"`
Pssword string `uri:"password" xml:"password" binding:"required"`
}
func main() {
r := gin.Default()
r.GET("/:user/:password", func(c *gin.Context) {
if err := c.ShouldBindUri(&login); err != nil {
.......
}
.......
})
.......
}
四、gin 渲染
1. 各种数据格式的响应
(1) json 响应
c.JSON(200, gin.H{"message": "someJSON", "status": 200})
(2) 结构体 响应
var msg struct {
Name string
Message string
}
msg.Name = "root"
msg.Message = "message"
c.JSON(200, msg)
(3) XML,YAML 响应
c.XML(200, gin.H{"message": "abc"})
c.YAML(200, gin.H{"name": "zhangsan"})
(4) protobuf 响应
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
c.ProtoBuf(200, data)
2. HTML模板渲染
gin支持加载 HTML模板, 然后根据模板参数进行配置并返回相应的数据。
本质上就是字符串替换。
func main() {
r := gin.Default()
r.LoadHTMLGlob("tem/*") //加载模板文件
r.GET("/index", func(c *gin.Context) {
//index.html就是具体的模板文件
c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "ce": "123456"})
})
r.Run()
}
3. 重定向
可以使用 context 的 Redirect 方法进行重定向
func main() {
r := gin.Default()
r.GET("/redirect_me", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
})
r.Run()
}
当客户端请求 /redirect_me 时,服务器将重定向到 https://www.5lmh.com
4. 同步异步
goroutine机制可以实现异步处理,但不应该使用原始上下文,必须使用它的只读副本。
func main() {
r := gin.Default()
r.GET("/long_async", func(c *gin.Context) {
copyContext := c.Copy() // 需要拷贝一个副本
go func() { // 异步处理
time.Sleep(3 * time.Second)
log.Println("异步执行:" + copyContext.Request.URL.Path)
}()
})
r.Run(":8000")
}
五、gin中间件
1. 全局中间件
所有请求都经过此中间件。
实现代码如下:
func MiddleWare() gin.HandlerFunc {
......
}
func main() {
r := gin.Default()
r.Use(MiddleWare())
r.Run()
}
2. Next()方法
当程序执行到context.Next时,会转而先去执行具体的业务逻辑。
执行完业务逻辑处理函数之后,程序会再次回到context.Next处,继续执行中间件后续的代码。
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行了")
c.Next() //转去执行业务逻辑
//业务逻辑执行完,返回此处,继续执行中间件代码
.......
fmt.Println("中间件执行完毕", status)
}
}
func main() {
r := gin.Default()
r.Use(MiddleWare()) // 注册中间件
r.GET("/ce", func(c *gin.Context) {
......
})
r.Run()
}
解释原文 Golang gin 中间件、context.Next函数_gin context.next-CSDN博客
3. 局部中间件
中间件只在具体某个路由生效,不使用 r.use() 定义的中间件。
func main() {
r := gin.Default()
//只在 /ce 的路由里使用局部中间件 PartMiddleWare()
r.GET("/ce", PartMiddleWare(), func(c *gin.Context) {
......
})
r.Run()
}
解释原文 局部生效的中间件_如何使用局部中间件-CSDN博客
六、会话控制
1. Cookie介绍
(1) HTTP是无状态协议,服务器不能记录浏览器的访问状态,不能区分两次请求是否由同一个客户端发出。
(2) Cookie 就是一段可以区分不同客户端的信息,由服务器创建,发送给浏览器,浏览器保存。
(3) 浏览器向服务器发送请求时,都会同时将Cookie发送给服务器,服务器根据处理请求。
2. Cookie的使用
登录时设置cookie,其他请求检验cookie,代码如下:
func AuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
//2.获取cookie并校验
if cookie, err := c.Cookie("abc"); err == nil {
if cookie == "123" {
c.Next() //3.cookie检验通过,执行请求逻辑
return
}
}
......
return
}
}
func main() {
r := gin.Default()
r.GET("/login", func(c *gin.Context) {
c.SetCookie("abc", "123", 60, "/", "localhost", false, true) //1.登录成功,设置cookie
c.String(200, "Login success!")
})
......
}
3. Cookie的缺点
(1) 不安全,明文,容易泄露信息
(2) 可以被浏览器禁用。
(3) 有数量限制。某些浏览器对某个域名的cookie有数量限制,大概50个。
4. Sessions
(1) session和cookie 实现的目标是一致的,但实现的方法不同;
(2) session 是另一种记录客户状态的机制, 不同的是 Cookie 保存在客户端浏览器中,而 session保存在服务器
(3) 浏览器第一次访问服务器时,服务器端会创建一个 session 对象,生成一个类似于 key,value 的键值对, 然后将 value 保存到服务器,将 key(cookie)返回到浏览器(客户)端。
浏览器下次访问时会携带 key(cookie),找到对应的 session(value)。
(4) sessions包地址: github.com/gorilla/sessions
七、参数验证
1. 结构体验证
用gin框架的数据验证,可以不用解析数据,减少if else,会简洁许多。
比如,在定义结构体时,添加如下 binding:"required,gt=10",表示不能为空并且大于10
type Person struct {
//不能为空并且大于10
Age int `form:"age" binding:"required,gt=10"`
}
2. 自定义验证
可以自定义验证函数,并注册到 validator中。
包地址:gopkg.in/go-playground/validator.v9/translations
type Person struct {
//1.在binding上使用自定义的 参数名称 stringCheckName
Name string `form:"name" binding:"stringCheckName"`
}
//2.自定义的校验方法
func checkStringValid(v *validator.Validate, ......) bool {
if value, ok := field.Interface().(string); ok {
return value != "" && !("5lmh" == value) //字段不能为空,并且不等于5lmh
}
return true
}
func main() {
r := gin.Default()
// 3、将我们自定义的校验方法 和 参数名称 注册到 validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("stringCheckName", checkStringValid)
}
}
3. 多语言翻译验证
如需要自定义返回信息的语言,比如部分用户返回中文,部分返回英文。
可以使用 validator.v9/translations
import (
ut "github.com/go-playground/universal-translator"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
)
func startPage(c *gin.Context) {
locale := c.DefaultQuery("locale", "zh") //1.获得前端使用语言
//2.获得翻译器
en := en.New()
zh := zh.New()
Uni = ut.New(en, zh)
trans, _ := Uni.GetTranslator(locale)
//3.注册翻译器
Validate = validator.New()
en_translations.RegisterDefaultTranslations(Validate, trans)
//4.解析结构体,获得翻译过的内容
user := User{}
c.ShouldBind(&user)
err := Validate.Struct(user)
}
八、其他
1. 日志
使用 io.MultiWriter 和 gin.DefaultWriter
import (
"io"
"os"
)
func main() {
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f) //将日志写入文件
//gin.DefaultWriter = io.MultiWriter(f, os.Stdout) //将日志同时写入文件和控制台
}
2. Air 实时加载
在开发调试的时候,变更代码之后需要按下Ctrl+C停止程序并重新编译再执行,不是很方便。
Air能够实时监听项目的代码文件,在代码发生变更之后自动重新编译并执行,提高开发效率。
3. gin验证码
为了防止接口被恶意频繁调用,可以采用加验证码的方式。
库地址:github.com/dchest/captcha