这篇笔记我们深入学习如何使用Gin框架读取HTTP请求信息以及如何返回响应信息。
对于URL参数,我们可以使用Query()
方法获取。
engine.GET("/getStudent", func(context *gin.Context) {
id := context.Query("id")
fmt.Printf("%v\n", id)
})
如果参数不存在,会返回空字符串。
对于路径参数,我们可以使用Param()
方法获取。
engine.GET("/getStudent/:id", func(context *gin.Context) {
id := context.Param("id")
fmt.Printf("%v\n", id)
})
不过和URL参数不同,如果路径参数不存在,这条路由信息是无法匹配成功的。
读取请求头信息,我们可以使用GetHeader()
方法。
engine.GET("/getStudent", func(context *gin.Context) {
id := context.GetHeader("id")
fmt.Printf("%v\n", id)
})
如果请求头不存在,会返回空字符串。
表单请求位于POST或PUT的请求体中,我们可以使用PostForm()
方法读取表单字段。
engine.POST("/addStudent", func(context *gin.Context) {
name := context.PostForm("name")
age := context.PostForm("age")
fmt.Printf("%v %v\n", name, age)
})
注意表单格式实际上有form-data
和x-www-form-urlencoded
两种格式,Gin对这两种格式的表单进行了统一的支持。
此外,Gin也支持将表单参数绑定到结构体,下面是一个例子。
type Student struct {
Name string `form:"name"`
Age string `form:"age"`
}
engine.POST("/addStudent", func(context *gin.Context) {
stu := Student{}
err := context.ShouldBind(&stu)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v %v\n", stu.Name, stu.Age)
})
代码中我们使用了ShouldBind()
方法将请求表单绑定到了stu
结构体实例上,结构体的定义中使用了Tag语法指定字段和表单Key的对应关系。绑定时,请求表单多字段或是少字段实际上不会报错,但如果发生类型不匹配等情况,就会抛出异常。
Gin对于JSON请求体也封装了自动绑定功能,下面是一个例子:
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
engine.POST("/addStudent", func(context *gin.Context) {
stu := Student{}
err := context.ShouldBindJSON(&stu)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%v %v\n", stu.Name, stu.Age)
})
类似于表单绑定,我们首先在结构体的定义中使用了Tag语法标注了结构体字段和JSON字段的映射关系,对于JSON数据我们使用了ShouldBindJSON()
方法进行绑定,这里如果JSON解析出错则会抛出异常。此外对于XML、YAML、TOML也是类似的,Gin对这几种常见数据交换格式都进行了支持。
当然,我们其实也可以字符串的形式取出请求数据,然后手动对JSON进行反序列化和结构体映射,但这样做代码就比较冗长了,一般不推荐这样写。
HTTP协议的文件上传一般采用form-data
格式的表单请求,Gin对于文件上传进行了完善的封装,我们不必自己操作流,直接调用相应的API保存文件即可,下面是一个例子。
engine.POST("/addStudent", func(context *gin.Context) {
file, err := context.FormFile("file")
if err != nil {
fmt.Println(err)
return
}
err = context.SaveUploadedFile(file, file.Filename)
if err != nil {
fmt.Println(err)
return
}
})
代码中,我们首先使用FormFile()
获取表单中的文件,它返回一个*multipart.FileHeader
对象,其中包含上传文件的信息如文件名、文件大小等;随后我们调用Gin提供的SaveUploadedFile()
方法将上传的文件保存到本地,该方法接收2个参数,第1个参数为*multipart.FileHeader
指针,第2个参数为文件名。
Gin框架返回JSON十分简单,如果我们有一个结构体对象,直接使用context.JSON()
方法返回即可,Gin框架会为对象进行序列化。
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
engine.GET("/student", func(context *gin.Context) {
s := Student{"Tom", 18}
context.JSON(200, s)
})
如果我们还需要对JSON进行一些加工,可以直接使用Gin.H
这个快捷类型对JSON数据进行封装。
engine.GET("/student", func(context *gin.Context) {
s := Student{"Tom", 18}
context.JSON(200, gin.H{
"code": 200,
"message": "success",
"data": s,
})
})
这里可能很多人对gin.H
比较费解,它其实就是一个自定义类型,其真实的类型是map[string]interface{}
,即键为string
类型,值为任何类型的Map结构(Go语言中空接口interface{}
可以代表任何类型)。这个类型我们可以将其理解为类似Java的Map<String, Object>
。
Gin框架在返回值中设置Header十分简单,调用Header()
方法即可,下面是一个例子。
engine.GET("/student", func(context *gin.Context) {
context.Header("Token", "abc123")
})
Gin框架支持响应静态文件,静态文件的配置在前面路由的章节已经介绍过了,这里就不再赘述了。那么如何动态的返回一些二进制数据作为文件下载请求的结果呢?这就需要我们手动向输出流写数据了,下面是一个例子。
engine.GET("/image", func(context *gin.Context) {
context.Header("Content-Disposition", "attachment; filename=data.jpg")
file, err := os.Open("data.jpg")
if err != nil {
fmt.Println(err)
return
}
bytes, err := io.ReadAll(file)
if err != nil {
fmt.Println(err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Println(err)
return
}
}(file)
_, err = context.Writer.Write(bytes)
if err != nil {
fmt.Println(err)
return
}
context.Writer.Flush()
})
代码逻辑其实很简单,我们读取了文件data.jpg
的二进制数据,并将其写入到输出流中。这里由于文件较小,我们是将数据全部读入内存,然后再写入输出流的,如果文件较大,则需要循环写入和读取。