go进阶(2) gin框架
gin框架使用
请求处理
一般常用的请求类型有GET、POST,俩者的数据处理方式并不相通,针对 go进阶(1) gin框架-CSDN博客 的数据解析与绑定一节进行新增与汇总;
GET请求
对于GET请求,服务端只需要对包含在URL中的query进行处理,使用
c.Query()
即可以获取query数据;
POST请求
对于POST请求,http常见的传输格式为四种:
- application/json
- application/x-www-form-urlencoded
- application/xml
- multipart/form-data
第一种可以采用
c.ShouldBindJSON()
解析请求体中的json数据;
第二种可以采用
c.PostForm()
获得单个key的值,也可以通过
c.ShouldBind()
来自动解析请求体中的数据;
第三种采用
c.ShouldBindXML()
解析请求体中的xml数据;
对于前三种,都可以使用
c.ShouldBind()
来自动解析对应类型的数据,相当于一个分析器,对于解析错误,可以对报错数据进行捕捉并返回自定义的相应数据;
而
c.Bind()
也可以自动解析对应类型的数据,但如果解析错误,则自动返回400状态码的报错;
第四种传输格式一般是文件类型的数据所使用的,一般采用
c.FormFile()
或是 c.Request.FormFile()
来对文件类型数据进行处理。
若在服务端需要多次绑定结构体,则需要使用
c.ShouldBindBodyWith()
可以读取c.Request.body并将结果存储到上下文,可以服用存储在上下文的body;
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// 读取 c.Request.Body 并将结果存入上下文。
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 这时, 复用存储在上下文中的 body。
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// 可以接受其他格式
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
c.String(http.StatusOK, `the body should be formB XML`)
} else {
...
}
}
HTML渲染
gin框架支持返回text/html格式的数据,导入包"html/template"
,
func main(){
r := gin.Default()
r.LoadHTMLGlob("templates/*")
//r.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Main website",
})
})
r.Run(":8080")
}
}
使用
r.LoadHTMLGlob("templates/*")
加载templates目录下所有的html文件,相当于r.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
逐个加载文件,在接口设计中添加
c.HTML()
使得响应返回text/htm格式的文件;
JSON响应
在Gin框架中,这四个方法是用于生成不同类型JSON响应的函数。让我们逐个介绍它们的作用和区别:
c.PureJSON()
用于发送原始的JSON响应,不会进行Unicode或HTML字符的转义,适合在已经确保数据安全性的情况下使用,例如在内部系统间的通信或对JSON格式有严格要求的场景。
举例:
c.PureJSON(http.StatusOK, []byte(`{"message": "<b>Hello, JSON!</b>"}`))
这将直接返回
{"message": "<b>Hello, JSON!</b>"}
,而不会将<b>转义为\u003cbr\u003e;
c.JSON()
用于将Go结构体或map[string]interface{}转换为JSON格式,并将其作为HTTP响应返回。
默认情况下,它使用UTF-8编码并对非ASCII字符进行转义处理。
例如:
c.JSON(http.StatusOK, gin.H{"message": "Hello, JSON!"})
这将返回一个JSON响应
{"message": "Hello, JSON!"}
c.AsciiJSON()
c.AsciiJSON()
方法与 c.JSON()
类似,但是会将所有非ASCII字符转换为转义序列,确保响应只包含ASCII字符,适用于某些客户端或中间件只能处理ASCII字符的情况;
举例:
c.AsciiJSON(http.StatusOK, gin.H{"message": "Hello, ASCII JSON!"})
这将返回
{"message": "Hello, ASCII JSON!"}
的JSON响应,其中非ASCII字符会被转义;
c.JSONP()
用于支持JSONP(JSON with Padding),允许跨域请求从服务器获取数据;
举例:
c.JSONP(http.StatusOK, gin.H{"message": "Hello, JSONP!"})
这将返回一个类似于
{"message": "Hello, JSONP!"}
c.SecureJSON()
为了防止劫持json的列表数据,默认增加while(1);
前缀,也可以通过r.SecureJsonPrefix(")]}',\n")
自定义前缀,传输格式为application/json
;
举例:
c.SecureJSON(http.StatusOK, []string{"hello"," world"})
这将返回
while(1);["hello"," world"]
选择使用哪种方法 取决于你的应用场景和需求:
- 如果需要原始的、不经过转义处理的JSON响应,可以使用
c.PureJSON()
; - 如果需要普通的JSON响应,并且能够处理Unicode字符,可以使用
c.JSON()
; - 如果需要确保所有响应只包含ASCII字符,可以使用
c.AsciiJSON()
; - 如果需要支持跨域请求,可以使用
c.JSONP()
; - 如果需要保护json列表数据,则使用
c.SecureJSON()
;
安全页眉
使用安全标头保护网络应用程序免受常见安全漏洞的攻击非常重要,本示例展示如何在 Gin 应用程序中添加安全标头,以及如何避免与主机标头注入相关的攻击(SSRF、开放重定向);
func main() {
r := gin.Default()
expectedHost := "localhost:8000"
// Setup Security Headers
r.Use(func(c *gin.Context) {
if c.Request.Host != expectedHost {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid host header"})
return
}
c.Header("X-Frame-Options", "DENY")
c.Header("Content-Security-Policy", "default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline';")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
c.Header("Referrer-Policy", "strict-origin")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("Permissions-Policy", "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()")
c.Next()
})
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":8000")
}
程序启动后,使用浏览器打开http://localhost:8000/ping
地址,F12打开检查工具,在网络功能中打开名为ping的请求,在表头可以查看服务端设置的安全保护响应标头,如下图:
render读取数据
在项目上线后,往往需要将产生的日志保存在固定的log文件中,方便进行记录与查看。我们需要项目日志输出的默认位置进行修改才能够实现以上功能;
代码如下:
func main() {
router := gin.Default()
router.GET("/someDataFromReader", func(c *gin.Context) {
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
reader := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="gopher.png"`,
}
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":8080")
}
此代码先通过http.Get()远程请求网络数据,获得reponse,再获得reponse响应里的body,contentLength,content-Type,还创建一个附加的json格式请求头,将其放入c.DataFromReader()中,将此render数据与附加数据发送到客户端;
日志记录
func main() {
// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
gin.DisableConsoleColor()
// 记录到文件。
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8000")
}
使用os包在当前目录下创建log文件,获得f文件指针,将gin.DefaultWriter的gin框架默认日志io流改为io包获得f文件的io流即可;
设置与获取Cookie
对于客户端用户登录的信息凭证可以存储在前端的cookie中,在用户使用api时,可以在客户端设置一个拦截器,对用户信息凭证进行检测,在服务端中将储存在cookie中的数据进行获取与分析,若用户凭证正确则允许使用api;
func main() {
router := gin.Default()
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie")
if err != nil {
cookie = "NotSet"
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
}
fmt.Printf("Cookie value: %s \n", cookie)
})
router.Run()
}
该代码使用c.Cookie()来获得对应key的value值,并使用c.SetCookie()设置了一个cookie值给客户端,具体函数功能请看后面函数描述一节;
gin函数
gin.DisableConsoleColor()
禁用控制台颜色
gin.H类型
gin.H封装了生成json数据的工具,定义的map[String]any类型;
gin.New() 函数
没有任何默认中间件的路由;
gin.Default() 函数
gin默认使用的路由组,一般赋值给变量r;
r.Use(middleware ...gin.HandlerFunc) gin.IRoutes
作用:将中间件添加到组中;
参数:封装好的中间件;
举例:r.Use(gin.Recovery())
r.GET(relativePath string, handlers ...gin.HandlerFunc)
作用:对于GET的restful请求方式进行捕获;
参数:第一个参数为地址路由名,第二个参数为内嵌函数用于处理逻辑;
举例:r.GET("/example", func(c *gin.Context) {})
r.POST(relativePath string, handlers ...gin.HandlerFunc)
作用:对于POST的restful请求方式进行捕获;
参数:第一个参数为相对地址路由名,第二个参数为内嵌函数用于处理逻辑;
举例:r.POST("/example", func(c *gin.Context) {})
r.LoadHTMLFiles(files ...string)
作用:gin框架获取html文件地址,可以在接口设计中使用该html文件地址;
参数:html文件地址;
举例:r.LoadHTMLFiles("index.html")
r.LoadHTMLGlob(pattern string)
作用:gin框架获取一个地址匹配项的所有html文件;
参数:正则匹配项;
举例:r.LoadHTMLGlob(".template/*")
r.Run(addr ...string)
作用:将路由器附加到http上,并开始监听服务器,不填参数默认为8080端口;
参数:port端口;
举例:r.Run(":8000")
gin.Context结构体
默认参数名为c;
c.Param(key string) string
作用:捕获URL地址的param,相当于c.Params.ByName(key),有两种捕获方式 ‘:param’ 与 ‘*param’,对于第一种可以直接获得对应param的值,第二种获得开头携带有 ‘/’ 的值;
参数:param名;
举例:name:=c.Param("name")
c.DefaultQuery(key string, defaultValue string) string
作用:捕获URL地址的query,若捕获不到数据可以使用参数defaultValue的值;
参数:第一个参数为query的参数名,第二个参数为默认query参数值;
举例:name:=c.DefaultQuery("name","lxh")
c.PostForm(key string) (value string)
作用:捕获前端表单中对应key的值;
参数:表单数据里属性name的键值;
举例:userName:=c.PostForm(“userName”)
c.DefaultPostForm(key string, defaultValue string) string
作用:捕获前端表单中对应key的值,若捕获不到数据可以使用参数defaultValue的值;
参数:第一个参数为属性name的参数名,第二个参数为默认name的参数值;
举例:types:=c.DefaultPostForm(“type”,“post”)
c.FormFile(name string) (*multipart.FileHeader, error)
作用:捕获前端提交的文件;
参数:前端input标签属性type为file的数据;
举例:file, err := c.FormFile(“file”)
c.Request.FormFile(key string) (multipart.File, *multipart.FileHeader, error)
作用:获得原生Request的formFile,可以获得更多请求的数据;
参数:前端input标签属性type为file的name值;
举例:_, headers, err := c.Request.FormFile("file")
c.SaveUploadedFile(file *multipart.FileHeader, dst string) error
作用:保存文件数据到本地中;
参数:第一个参数为二进制文件,第二个参数为保存的路径文件名;
举例:c.SaveUploadedFile(file, file.Filename)
c.String(code int, format string, values ...any)
作用:在页面中显示字符串;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的字符串;
举例:c.String(http.StatusOK,"ok")
c.HTML(code int, name string, obj any)
作用:响应返回给前端一个在服务端地址为name的html文件;
参数:第一个参数为code状态码,第二个参数为html文件地址,第三个参数为json数据;
举例:c.HTML(http.StatusOK, "index.html", gin.H{"title": "Posts",})
c.JSON(code int, obj any)
作用:在响应体中放入json数据;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的json数据,一般使用gin.H{“key” : value,…}的gin自定义类型来存储json数据;
举例:c.JSON(200, gin.H{"message": file.Filename})
c.AsciiJSON(code int, obj any)
作用:在响应体中放入只包含可以转换成ASCLL码的json数据,相比较c.JSON(),可以满足部分只准许ascll码的中间件或者客户端;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的json数据,一般使用gin.H{“key” : value,…}的gin自定义类型来存储json数据;
举例:c.JSON(200, gin.H{"message": file.Filename})
c.JSONP(code int, obj any)
作用:跨域在响应体中放入json数据,传输格式为application/javascript
;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的json数据,一般使用gin.H{“key” : value,…}的gin自定义类型来存储json数据;
举例:c.JSONP(200, gin.H{"message": file.Filename})
c.PureJSON(code int, obj any)
作用:在响应体中放入json数据,对特殊字符不使用unicode实体来替换,而是使用字面;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的json数据,一般使用gin.H{“key” : value,…}的gin自定义类型来存储json数据;
举例:c.JSONP(200, gin.H{"message": <br>加粗</br>})
c.SecureJSON(code int, obj any)
作用:在响应体中放入json数据,并为了防止劫持json的列表数据,默认增加 while(1);
前缀,也可以通过r.SecureJsonPrefix(")]}',\n")
自定义前缀,传输格式为application/json
;
参数:第一个参数为code状态码,一般可以使用net/http
包的状态码常量,第二个参数为要输出的json数据,一般使用gin.H{“key” : value,…}的gin自定义类型来存储json数据;
举例:c.SecureJSON(http.StatusOK,[]string{"hello"," world"})
c.MultipartForm() (*multipart.Form, error)
作用:获得解析后的多表单数据集合;
c.ShouldBindJSON(obj any) error
作用:将request的body中的数据,自动按照json格式解析到结构体;
参数:属于json类型的指针变量;
举例:err := c.ShouldBindJSON(&json)
c.ShouldBindUri(obj any) error
作用:将url中的param,解析到结构体中;
参数:属于json类型的指针变量;
举例:err := c.ShouldBindUri(&login)
c.ShouldBind(obj any) error
作用:自动解析数据格式,对于解析失败,可以在服务端捕捉报错信息,并对返回的数据进行处理;
c.Bind(obj any) error
作用:根据Method与Content-Type来自动选择解析的类型,相对于ShouldBind()
,Bind()
会在解析失败后自动返回400状态码的报错;
参数:指针变量;
举例:err := c.Bind(&form);
c.ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error)
作用:按照参数bb的类型来解析body数据,并可以多次复用;
参数:第一个参数为指针变量,第二个参数是对应解析的类型;
举例:errB := c.ShouldBindBodyWith(&obj,binding.JSON);
c.DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string)
作用:用于从 io.Reader
接口中读取数据并发送给客户端的高级响应方法,适用于处理动态生成或大数据量的响应场景;
参数:第一个参数为code状态码,第二个参数为二进制文本长度,第三个参数为传输格式,第四个参数为response响应的body,第五个参数为附加json数据;
举例:c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
c.Cookie(name string) (string, error)
作用:获得客户端cookie中key为name的value值;
参数:cookie的key值;
举例:c.Cookie("gin_cookie")
c.SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
作用:在客户端中创建一个cookie数据,key为name,并设置了过期时间maxAge,cookie可访问的路径path(若为“/”则默认为全网站都可以访问),cookie的域名(若为"localhost"就是本地可访问),secure为true则只允许HTTPS协议访问,为false则可以允许HTTP协议访问,httpOnly为true则不会被客户端的JavaScript代码访问;
参数:第一个参数为cookie的key值,第二个参数为value值,第三个参数为超时时间(s),第四个参数为可访问路径,第五个参数为可访问ip,第六个参数为是否允许安全协议,第七个参数为是否允许客户端JavaScript代码访问;
举例:c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)