认识 Gin        框架是一系列工具的集合,能让开发变的便捷。
       学习框架的目的就是为了提供项目的开发效率 ,使我们更加专注业务,而不是和业务无关的底层代码。
Go 流行的 Web 框架        如果学习过其他语言,可能知道Java用的比较多的是Spring框架,PHP用的比较多的是Laravel,python用的多的是Django,都在各自的语言中具有强大的统治力。
       Go 从诞生之初就带有浓重的开源属性,其原生库已经很强大,即使不依赖框架,也能进行高性能开发,又因为其语言并没有一定的设计标准,所以较为灵活,也就诞生了众多的框架,各具有特色,满足不同的喜好。
Gin        地址:https://github.com/gin-gonic/gin 
       号称最快的go语言web框架 ,目前是go官方的推荐框架(https://go.dev/doc/tutorial/) 
iris        地址:https://github.com/kataras/iris 
       性能比gin高一些,支持MVC,但这款框架评价不太好,使用上问题较多,近些年很少去选择使用
Beego        地址:https://github.com/beego/beego 
       国人开发,最早的go web框架之一,工具集比较完善,性能较差,据传言作者是php转行,所以框架带有浓厚的php特色,早期国内使用的多,目前少有人选择
fiber        地址:https://github.com/gofiber/fiber 
       2020年发布的框架,发展迅速,建立在fasthttp之上,性能目前最高,受Express启发,比较简洁,上手较快,和gin类似。
当然还有其他一些框架,但从star数上,以及流行程度上看,gin一骑绝尘,gin的好处在于其简洁,扩展性,稳定性以及性能都比较出色。
Go的框架其实是可以理解为库,并不是用了某一个框架就不能用别的框架,可以选择性的使用各个库中的优秀组件,进行组合 
 
Gin 介绍 
快速 :基于 Radix 树的路由,小内存占用 。没有反射。可预测的 API 性能。 
支持中间件 :传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。 
Crash 处理 :Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用 。例如,你可以向 Sentry 报告这个 panic! 
JSON 验证 :Gin 可以解析并验证请求 的 JSON,例如,检查所需值的存在。 
路由组 :更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。 
错误管理 :Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。 
内置渲染 :Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。 
可扩展性 :新建一个中间件非常简单。 
 
快速入门 要求 
安装 1 2 3 4 5 6 7 8 9 mkdir GinStudy cd GinStudy go work init mkdir helloworld cd helloworld go mod init test.com/helloworld go: creating new go.mod: module test.com/helloworld cd .. go work use ./helloworld 
 
Goland打开:
 
1 2 cd helloworld go get -u github.com/gin-gonic/gin 
 
示例程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package  mainimport  (	"github.com/gin-gonic/gin"  	"log"  ) func  main ()   {	r := gin.Default() 	 	r.GET("/hello" , func (ctx *gin.Context)   { 		 		ctx.JSON(200 , gin.H{ 			"name" : "hello world" , 		}) 	}) 	err := r.Run(":8080" ) 	if  err != nil  { 		log.Fatalln(err) 	} } 
 
测试:
 
路由        路由是URI到函数的映射。 
一个URI含: http://localhost:8080/user/find?id=11
协议 :比如 http,https 等 
ip端口或者域名 :比如 127.0.0.1:8080 或者www.test.com 
path :比如 /path 
query :比如 ?query 
 
同时访问的时候,还需要指明HTTP METHOD,比如:
GET :GET方法请求一个指定资源的表示形式. 使用GET的请求应该只被用于获取数据  
POST :POST方法用于将实体提交到指定的资源 ,通常会导致在服务器上的状态变化 
HEAD :HEAD方法请求一个与GET请求的响应相同的响应,但没有响应体 
PUT :PUT方法用请求有效载荷替换目标资源的所有当前表示 
DELETE :DELETE方法删除 指定的资源 
CONNECT :CONNECT方法建立一个到由目标资源标识的服务器的隧道  
OPTIONS :OPTIONS方法用于描述目标资源的通信选项  
TRACE :TRACE方法沿着到目标资源的路径执行一个消息环回测试  
PATCH :PATCH方法用于对资源应用部分修改  
 
使用的时候,应该尽量遵循其语义
 
RESTful API 规范        RESTful API 的规范建议我们使用特定的HTTP方法来对服务器上的资源进行操作。比如:
GET,表示读取 服务器上的资源 
POST,表示在服务器上创建 资源 
PUT,表示更新或者替换 服务器上的资源 
DELETE,表示删除 服务器上的资源 
PATCH,表示更新/修改资源的一部分  
 
请求方法 1 2 3 4 5 6 7 8 9 10 11 12 r.GET("/get" , func (ctx *gin.Context)   { 		ctx.JSON(200 , "get" ) }) r.POST("/post" , func (ctx *gin.Context)   { 		ctx.JSON(200 , "post" ) }) r.DELETE("/delete" , func (ctx *gin.Context)   { 		ctx.JSON(200 , "delete" ) }) r.PUT("/put" , func (ctx *gin.Context)   { 		ctx.JSON(200 , "put" ) }) 
 
如果想要支持所有:
1 2 3 r.Any("/any" , func (ctx *gin.Context)   { 		ctx.JSON(200 , "any" ) }) 
 
如果想要支持其中的几种:
1 2 3 4 5 6 7 8 9 10 11 12 r.GET("/hello" , func (ctx *gin.Context)   { 		 		ctx.JSON(200 , gin.H{ 			"name" : "hello world" , 		}) }) r.POST("/hello" , func (ctx *gin.Context)   { 		 		ctx.JSON(200 , gin.H{ 			"name" : "hello world" , 		}) }) 
 
URI        URI书写的时候,我们不需要关心scheme和authority这两部分,我们主要通过path和query两部分 的书写来进行资源的定位。
静态url,比如/hello,/user/find 
 
1 r.POST("/user/find" , func (ctx *gin.Context)   {}) 
 
1 2 3 4 r.POST("/user/find/:id" , func (ctx *gin.Context)   { 		param := ctx.Param("id" ) 		ctx.JSON(200 , param) }) 
 
1 2 3 4 r.POST("/user/*path" , func (ctx *gin.Context)   { 		param := ctx.Param("path" ) 		ctx.JSON(200 , param) }) 
 
处理函数 定义:
1 type  HandlerFunc func (*Context) 
 
通过上下文的参数,获取http的请求参数,响应http请求等。
分组路由        在进行开发的时候,我们往往要进行模块的划分,比如用户模块,以user开发,商品模块,以goods开头。或者进行多版本开发,不同版本之间路径是一致的,这种时候,就可以用到分组路由了。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ug := r.Group("/user" ){ 		ug.GET("find" , func (ctx *gin.Context)   { 			ctx.JSON(200 , "user find" ) 		}) 		ug.POST("save" , func (ctx *gin.Context)   { 			ctx.JSON(200 , "user save" ) 		}) } gg := r.Group("/goods" ){ 		gg.GET("find" , func (ctx *gin.Context)   { 			ctx.JSON(200 , "goods find" ) 		}) 		gg.POST("save" , func (ctx *gin.Context)   { 			ctx.JSON(200 , "goods save" ) 		}) } 
 
请求参数 Get 请求参数 使用Get请求传参时,类似于这样 http://localhost:8080/user/save?id=11&name=zhangsan,如何获取呢?
普通参数 request url: http://localhost:8080/user/save?id=11&name=zhangsan
1 2 3 4 5 6 7 8 r.GET("/user/save" , func (ctx *gin.Context)   { 		id := ctx.Query("id" ) 		name := ctx.Query("name" ) 		ctx.JSON(200 , gin.H{ 			"id" :   id, 			"name" : name, 		}) }) 
 
如果参数不存在,就给一个默认值:
1 2 3 4 5 6 7 8 9 10 r.GET("/user/save" , func (ctx *gin.Context)   { 		id := ctx.Query("id" ) 		name := ctx.Query("name" ) 		address := ctx.DefaultQuery("address" , "北京" ) 		ctx.JSON(200 , gin.H{ 			"id" :      id, 			"name" :    name, 			"address" : address, 		}) }) 
 
判断参数是否存在:
1 2 3 4 5 6 7 8 9 10 r.GET("/user/save" , func (ctx *gin.Context)   { 		id, ok := ctx.GetQuery("id" ) 		address, aok := ctx.GetQuery("address" ) 		ctx.JSON(200 , gin.H{ 			"id" :      id, 			"idok" :    ok, 			"address" : address, 			"aok" :     aok, 		}) }) 
 
id是数值类型,上述获取的都是string类型,根据类型获取:(结构体中要加tag噢)
1 2 3 4 5 6 7 8 9 10 11 12 type  User struct  {	Id   int64   `form:"id"`  	Name string  `form:"name"`  } r.GET("/user/save" , func (ctx *gin.Context)   { 		var  user User 		err := ctx.BindQuery(&user) 		if  err != nil  { 			log.Println(err) 		} 		ctx.JSON(200 , user) }) 
 
也可以:
1 2 3 4 5 6 7 8 r.GET("/user/save" , func (ctx *gin.Context)   { 		var  user User 		err := ctx.ShouldBindQuery(&user) 		if  err != nil  { 			log.Println(err) 		} 		ctx.JSON(200 , user) }) 
 
区别: 
1 2 3 4 5 type  User struct  {	Id      int64   `form:"id"`  	Name    string  `form:"name"`  	Address string  `form:"address" binding:"required"`  } 
 
数组参数 请求url:http://localhost:8080/user/save?address=Beijing&address=shanghai
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context)   { 		address := ctx.QueryArray("address" ) 		ctx.JSON(200 , address) }) 
 
1 2 3 4 5 r.GET("/user/save" , func (ctx *gin.Context)   { 		address, ok := ctx.GetQueryArray("address" ) 		fmt.Println(ok) 		ctx.JSON(200 , address) }) 
 
1 2 3 4 5 6 r.GET("/user/save" , func (ctx *gin.Context)   { 		var  user User 		err := ctx.ShouldBindQuery(&user) 		fmt.Println(err) 		ctx.JSON(200 , user) }) 
 
map 参数 请求url:http://localhost:8080/user/saveaddressMap[home]=Beijing&addressMap[company]=shanghai
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context)   { 		addressMap := ctx.QueryMap("addressMap" ) 		ctx.JSON(200 , addressMap) }) 
 
1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context)   { 		addressMap, _ := ctx.GetQueryMap("addressMap" ) 		ctx.JSON(200 , addressMap) }) 
 
map参数,bind并没有支持 
Post请求参数        post请求一般是表单参数和json参数。
表单参数 
1 2 3 4 5 6 7 8 9 10 11 12 r.POST("/user/save" , func (ctx *gin.Context)   { 		id := ctx.PostForm("id" ) 		name := ctx.PostForm("name" ) 		address := ctx.PostFormArray("address" ) 		addressMap := ctx.PostFormMap("addressMap" ) 		ctx.JSON(200 , gin.H{ 			"id" :         id, 			"name" :       name, 			"address" :    address, 			"addressMap" : addressMap, 		}) }) 
 
1 2 3 4 5 6 7 8 r.POST("/user/save" , func (ctx *gin.Context)   { 		var  user User 		err := ctx.ShouldBind(&user) 		addressMap, _ := ctx.GetPostFormMap("addressMap" ) 		user.AddressMap = addressMap 		fmt.Println(err) 		ctx.JSON(200 , user) }) 
 
不支持map绑定 
json 参数 1 2 3 4 5 6 7 8 9 10 11 {     "id" :1111 ,     "name" :"zhangsan" ,     "address" : [         "beijing" ,         "shanghai"      ],     "addressMap" :{         "home" :"beijing"      } } 
 
1 2 3 4 5 6 r.POST("/user/save" , func (ctx *gin.Context)   { 		var  user User 		err := ctx.ShouldBindJSON(&user) 		fmt.Println(err) 		ctx.JSON(200 , user) }) 
 
其他类型参数注入xml,yaml等和json道理一样
路径参数 请求url:http://localhost:8080/user/save/111
1 2 3 r.POST("/user/save/:id" , func (ctx *gin.Context)   { 		ctx.JSON(200 , ctx.Param("id" )) }) 
 
如果需要使用ctx.ShouldBindUri,则需要绑定tag
文件参数 1 2 3 4 5 6 7 8 9 10 11 12 13 r.POST("/user/save" , func (ctx *gin.Context)   { 		form, err := ctx.MultipartForm() 		if  err != nil  { 			log.Println(err) 		} 		files := form.File 		for  _, fileArray := range  files { 			for  _, v := range  fileArray { 				ctx.SaveUploadedFile(v, "./" +v.Filename) 			} 		} 		ctx.JSON(200 , form.Value) }) 
 
响应 字符串方式 1 2 3 r.GET("/user/save" , func (ctx *gin.Context)   { 		ctx.String(http.StatusOK, "this is a %s" , "ms string response" ) }) 
 
JSON 方式 1 2 3 4 5 r.GET("/user/save" , func (ctx *gin.Context)   { 		ctx.JSON(http.StatusOK, gin.H{ 			"success" : true , 		}) }) 
 
XML 方式 1 2 3 4 5 6 7 8 9 10 11 type  XmlUser struct  {	Id   int64   `xml:"id"`  	Name string  `xml:"name"`  } r.GET("/user/save" , func (ctx *gin.Context)   { 		u := XmlUser{ 			Id:   11 , 			Name: "zhangsan" , 		} 		ctx.XML(http.StatusOK, u) }) 
 
文件格式 1 2 3 4 r.GET("/user/save" , func (ctx *gin.Context)   { 		 		ctx.FileAttachment("./1.png" , "2.png" )   }) 
 
设置 http 响应头 1 2 3 r.GET("/user/save" , func (ctx *gin.Context)   { 		ctx.Header("test" , "headertest" ) }) 
 
重定向 1 2 3 r.GET("/user/save" , func (ctx *gin.Context)   { 		ctx.Redirect(http.StatusMovedPermanently, "http://www.baidu.com" ) }) 
 
YAML 方式 1 2 3 r.GET("/user/save" , func (ctx *gin.Context)   { 		ctx.YAML(200 , gin.H{"name" : "ms" , "age" : 19 }) }) 
 
模版渲染        模板是 golang 语言的一个标准库 ,使用场景很多,gin框架同样支持模板。
基本使用 定义一个存放模板文件的templates文件夹:
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html > <head >     <meta  charset ="utf-8" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1" >      <title > gin_templates</title >  </head > <body > {{.title}} </body > </html > 
 
后端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  mainimport  (	"github.com/gin-gonic/gin"  	"net/http"  ) func  main ()   {	r := gin.Default() 	 	r.LoadHTMLFiles("templates/index.tmpl" ) 	r.GET("/index" , func (c *gin.Context)   { 		 		 		c.HTML(http.StatusOK, "index.tmpl" , gin.H{ 			"title" : "hello 模板" , 		}) 	}) 	r.Run(":9090" )  } 
 
多个模板渲染 如果有多个模板,可以统一进行渲染:
1 2 r.LoadHTMLGlob("templates/**" ) 
 
如果目录为templates/post/index.tmpl和templates/user/index.tmpl这种,可以:
1 2 r.LoadHTMLGlob("templates/**/*" ) 
 
自定义模板函数 1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html > <head >     <meta  charset ="utf-8" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1" >      <title > gin_templates</title >  </head > <body > {{.title | safe}} </body > </html > 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 r.SetFuncMap(template.FuncMap{ 		"safe" : func (str string )  template .HTML   { 			return  template.HTML(str) 		}, }) r.LoadHTMLGlob("templates/**" ) r.GET("/index" , func (c *gin.Context)   { 		 		 		c.HTML(http.StatusOK, "index.tmpl" , gin.H{ 			"title" : "<a href='http://baidu.com'>跳转到其他地方</a>" , 		}) }) 
 
静态文件处理 如果在模板中引入静态文件,比如样式文件 index.css
1 2 3 body {    background-color : aqua; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html > <head >     <meta  charset ="utf-8" >      <meta  name ="viewport"  content ="width=device-width, initial-scale=1" >      <title > gin_templates</title >      <link  rel ="stylesheet"  href ="/css/index.css" >  </head > <body > {{.title}} </body > </html > 
 
1 2 r.Static("/css" , "./static/css" ) 
 
会话        会话控制涉及到cookie和session的使用。 
cookie 
HTTP是无状态协议 ,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出 
Cookie就是解决HTTP协议无状态的方案之一  
Cookie实际上就是服务器保存在浏览器上的一段信息 。浏览器有了Cookie之后,每次向服务器发送请求时都会同时将该信息发送服务器,服务器收到请求后,就可以根据该信息处理请求 
Cookie由服务器创建,并发送给浏览器,最终由浏览器保存  
 
设置 cookie 1 func  (c *Context)  SetCookie (name, value string , maxAge int , path, domain string , secure, httpOnly bool ) 
 
参数说明:
参数名 
类型 
说明 
 
 
name 
string 
cookie名字 
 
value 
string 
cookie值 
 
maxAge 
int 
有效时间,单位是秒,MaxAge=0 忽略MaxAge属性,MaxAge<0 相当于删除cookie, 通常可以设置-1代表删除,MaxAge>0 多少秒后cookie失效 
 
path 
string 
cookie路径 
 
domain 
string 
cookie作用域 
 
secure 
bool 
Secure=true,那么这个cookie只能用https协议发送给服务器 
 
httpOnly 
bool 
设置HttpOnly=true的cookie不能被js获取到 
 
1 2 3 4 r.GET("/cookie" , func (c *gin.Context)   { 		 		c.SetCookie("site_cookie" , "cookievalue" , 3600 , "/" , "localhost" , false , true ) }) 
 
读取 cookie 1 2 3 4 5 6 7 8 9 10 r.GET("/read" , func (c *gin.Context)   { 		 		data, err := c.Cookie("site_cookie" ) 		if  err != nil  {       c.String(200 ,"not found!" ) 			return  		}   	 		c.String(200 ,data) }) 
 
删除 cookie 通过将cookie的MaxAge设置为-1, 达到删除cookie的目的。 
1 2 3 4 5 r.GET("/del" , func (c *gin.Context)   { 		 		c.SetCookie("site_cookie" , "cookievalue" , -1 , "/" , "localhost" , false , true ) 		c.String(200 ,"删除cookie" ) }) 
 
Session 在Gin框架中,我们可以依赖gin-contrib/sessions 中间件处理session。
安装session包
1 go get github.com/gin-contrib/sessions 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package  mainimport  (	"fmt"  	"github.com/gin-contrib/sessions"  	"github.com/gin-contrib/sessions/cookie"  	"github.com/gin-gonic/gin"  ) func  main ()   {	r := gin.Default() 	 	store := cookie.NewStore([]byte ("secret" )) 	 	 	r.Use(sessions.Sessions("mysession" , store)) 	r.GET("/hello" , func (c *gin.Context)   { 		 		session := sessions.Default(c) 		 		 		if  session.Get("hello" ) != "world"  { 			fmt.Println("没读到" ) 			 			session.Set("hello" , "world" ) 			session.Save() 		} 		c.JSON(200 , gin.H{"hello" : session.Get("hello" )}) 	}) 	r.Run(":8080" ) } 
 
多 session 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package  mainimport  (	"github.com/gin-contrib/sessions"  	"github.com/gin-contrib/sessions/cookie"  	"github.com/gin-gonic/gin"  ) func  main ()   {	r := gin.Default() 	store := cookie.NewStore([]byte ("secret" )) 	sessionNames := []string {"a" , "b" } 	r.Use(sessions.SessionsMany(sessionNames, store)) 	r.GET("/hello" , func (c *gin.Context)   { 		sessionA := sessions.DefaultMany(c, "a" ) 		sessionB := sessions.DefaultMany(c, "b" ) 		if  sessionA.Get("hello" ) != "world!"  { 			sessionA.Set("hello" , "world!" ) 			sessionA.Save() 		} 		if  sessionB.Get("hello" ) != "world?"  { 			sessionB.Set("hello" , "world?" ) 			sessionB.Save() 		} 		c.JSON(200 , gin.H{ 			"a" : sessionA.Get("hello" ), 			"b" : sessionB.Get("hello" ), 		}) 	}) 	r.Run(":8080" ) } 
 
基于 redis 存储引擎的 session        如果我们想将 session 数据保存到 redis 中,只要将 session 的存储引擎改成 redis 即可。
       使用 redis 作为存储引擎的例子:
       首先安装 redis 存储引擎的包:
1 go get github.com/gin-contrib/sessions/redis 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package  mainimport  (	"github.com/gin-contrib/sessions"  	"github.com/gin-contrib/sessions/redis"  	"github.com/gin-gonic/gin"  ) func  main ()   {	r := gin.Default() 	 	 	 	 	 	 	 	store, _ := redis.NewStore(10 , "tcp" , "localhost:6379" , "" , []byte ("secret" )) 	r.Use(sessions.Sessions("mysession" , store)) 	r.GET("/incr" , func (c *gin.Context)   { 		session := sessions.Default(c) 		var  count int  		v := session.Get("count" ) 		if  v == nil  { 			count = 0  		} else  { 			count = v.(int ) 			count++ 		} 		session.Set("count" , count) 		session.Save() 		c.JSON(200 , gin.H{"count" : count}) 	}) 	r.Run(":8080" ) } 
 
中间件        在Gin框架中,中间件 (Middleware)指的是可以拦截http请求-响应 生命周期的特殊函数,在请求-响应生命周期中可以注册多个中间件,每个中间件执行不同的功能,一个中间执行完再轮到下一个中间件执行。
中间件的常见应用场景如下: 
请求限速 
api接口签名处理  
权限校验  
统一错误处理 
 
       Gin支持设置全局中间件 和针对路由分组设置中间件 ,设置全局中间件意思就是会拦截所有请求,针对分组路由设置中间件,意思就是仅对这个分组下的路由起作用。
中间件使用 1 2 3 4 5 6 7 8 9 10 r := gin.New() 	 	 	r.Use(gin.Logger()) 	 	r.Use(gin.Recovery()) 	r.GET("/test" , func (ctx *gin.Context)   { 		panic (errors.New("test error" )) 	}) 	r.Run(":8080" ) 
 
自定义中间件        使用 Use  可以使用 Gin 自带的中间件或者其他第三方中间件,也可以自己开发中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package  mainimport  (  "github.com/gin-gonic/gin"  	"log"  	"time"  ) func  Logger ()  gin .HandlerFunc   {	return  func (c *gin.Context)   { 		t := time.Now() 		 		c.Set("example" , "12345" ) 		       		 		c.Next() 		 		latency := time.Since(t) 		log.Print(latency) 		 		status := c.Writer.Status() 		log.Println(status) 	} } func  main ()   {	r := gin.New() 	 	r.Use(Logger()) 	r.GET("/test" , func (c *gin.Context)   { 		 		example := c.MustGet("example" ).(string ) 		 		log.Println(example) 	}) 	 	r.Run(":8080" ) }