一、Gin框架介绍
Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架,
由于使用了httprouter,速度提高了近40倍。
如果你是性能和高效的追求者, 你会爱上Gin。
Go世界里最流行的Web框架,Github上有32K+star。
基于httprouter开发的Web框架。
中文文档齐全,简单易用的轻量级框架。
1.1 Gin框架安装与使用
下载并安装Gin:
1
   | go get -u github.com/gin-gonic/gin
   | 
 
1.2 第一个Gin示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | package main
  import ( 	"github.com/gin-gonic/gin" )
  func main() { 	 	r := gin.Default() 	 	 	r.GET("/hello", func(c *gin.Context) { 		 		c.JSON(200, gin.H{ 			"message": "Hello world!", 		}) 	}) 	 	r.Run() }
   | 
 
将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串JSON字符串。
二、JSON渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | func main() { 	r := gin.Default()
  	 	r.GET("/someJSON", func(c *gin.Context) { 		 		c.JSON(http.StatusOK, gin.H{"message": "Hello world!"}) 	}) 	r.GET("/moreJSON", func(c *gin.Context) { 		 		var msg struct { 			Name    string `json:"user"` 			Message string 			Age     int 		} 		msg.Name = "小王子" 		msg.Message = "Hello world!" 		msg.Age = 18 		c.JSON(http.StatusOK, msg) 	}) 	r.Run(":8080") }
  | 
 
三、获取参数
3.1 获取querystring参数
querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。
获取请求的querystring参数的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | func main() { 	 	r := gin.Default() 	r.GET("/user/search", func(c *gin.Context) {          		username := c.DefaultQuery("username", "小王子") 		 		address := c.Query("address") 		 		c.JSON(http.StatusOK, gin.H{ 			"message":  "ok", 			"username": username, 			"address":  address, 		}) 	}) 	r.Run() }
  | 
 
我们输入对应的URL,就能获取到对应的参数了
1
   | http://localhost:9090/web?username=小王子&address=沙河
   | 
 
请求的数据通过form表单来提交,例如向/user/search发送一个POST请求,获取请求数据的方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | func main() { 	 	r := gin.Default() 	r.POST("/user/search", func(c *gin.Context) { 		 		 		username := c.PostForm("username") 		address := c.PostForm("address") 		 		c.JSON(http.StatusOK, gin.H{ 			"message":  "ok", 			"username": username, 			"address":  address, 		}) 	}) 	r.Run(":8080") }
  | 
 
3.3 获取path参数
请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。
获取请求URL路径中的参数的方式如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | func main() { 	 	r := gin.Default() 	r.GET("/user/search/:username/:address", func(c *gin.Context) { 		username := c.Param("username") 		address := c.Param("address") 		 		c.JSON(http.StatusOK, gin.H{ 			"message":  "ok", 			"username": username, 			"address":  address, 		}) 	})
  	r.Run(":8080") }
  | 
 
3.4 参数绑定
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。
下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
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 47 48 49 50 51 52 53 54 55
   |  type Login struct { 	User     string `form:"user" json:"user" binding:"required"` 	Password string `form:"password" json:"password" binding:"required"` }
  func main() { 	router := gin.Default()
  	 	router.POST("/loginJSON", func(c *gin.Context) { 		var login Login
  		if err := c.ShouldBind(&login); err == nil { 			fmt.Printf("login info:%#v\n", login) 			c.JSON(http.StatusOK, gin.H{ 				"user":     login.User, 				"password": login.Password, 			}) 		} else { 			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 		} 	})
  	 	router.POST("/loginForm", func(c *gin.Context) { 		var login Login 		 		if err := c.ShouldBind(&login); err == nil { 			c.JSON(http.StatusOK, gin.H{ 				"user":     login.User, 				"password": login.Password, 			}) 		} else { 			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 		} 	})
  	 	router.GET("/loginForm", func(c *gin.Context) { 		var login Login 		 		if err := c.ShouldBind(&login); err == nil { 			c.JSON(http.StatusOK, gin.H{ 				"user":     login.User, 				"password": login.Password, 			}) 		} else { 			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 		} 	})
  	 	router.Run(":8080") }
 
  | 
 
ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
- 如果是 
GET 请求,只使用 Form
绑定引擎(query)。 
- 如果是 
POST 请求,首先检查 content-type
是否为 JSON 或 XML,然后再使用
Form(form-data)。 
四、文件上传
4.1 单个文件上传
文件上传前端页面代码:
1 2 3 4 5 6 7 8 9 10 11 12
   | <!DOCTYPE html> <html lang="zh-CN"> <head>     <title>上传文件示例</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data">     <input type="file" name="f1">     <input type="submit" value="上传"> </form> </body> </html>
   | 
 
后端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
   | func main() { 	router := gin.Default() 	 	 	 	router.POST("/upload", func(c *gin.Context) { 		 		file, err := c.FormFile("f1") 		if err != nil { 			c.JSON(http.StatusInternalServerError, gin.H{ 				"message": err.Error(), 			}) 			return 		}
  		log.Println(file.Filename) 		dst := fmt.Sprintf("C:/tmp/%s", file.Filename) 		 		c.SaveUploadedFile(file, dst) 		c.JSON(http.StatusOK, gin.H{ 			"message": fmt.Sprintf("'%s' uploaded!", file.Filename), 		}) 	}) 	router.Run() }
  | 
 
4.2 多个文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | func main() { 	router := gin.Default() 	 	 	 	router.POST("/upload", func(c *gin.Context) { 		 		form, _ := c.MultipartForm() 		files := form.File["file"]
  		for index, file := range files { 			log.Println(file.Filename) 			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index) 			 			c.SaveUploadedFile(file, dst) 		} 		c.JSON(http.StatusOK, gin.H{ 			"message": fmt.Sprintf("%d files uploaded!", len(files)), 		}) 	}) 	router.Run() }
  | 
 
五、重定向
5.1 HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。
1 2 3
   | r.GET("/test", func(c *gin.Context) { 	c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/") })
  | 
 
5.2 路由重定向
路由重定向,使用HandleContext:
1 2 3 4 5 6 7 8
   | r.GET("/test", func(c *gin.Context) {          c.Request.URL.Path = "/test2"     r.HandleContext(c) }) r.GET("/test2", func(c *gin.Context) {     c.JSON(http.StatusOK, gin.H{"hello": "world"}) })
  | 
 
六、Gin路由
6.1 普通路由
1 2 3
   | r.GET("/index", func(c *gin.Context) {...}) r.GET("/login", func(c *gin.Context) {...}) r.POST("/login", func(c *gin.Context) {...})
  | 
 
此外,还有一个可以匹配所有请求方法的Any方法如下:
1
   | r.Any("/test", func(c *gin.Context) {...})
  | 
 
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
1 2 3
   | r.NoRoute(func(c *gin.Context) { 		c.HTML(http.StatusNotFound, "views/404.html", nil) 	})
  | 
 
6.2 路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | func main() { 	r := gin.Default() 	userGroup := r.Group("/user") 	{ 		userGroup.GET("/index", func(c *gin.Context) {...}) 		userGroup.GET("/login", func(c *gin.Context) {...}) 		userGroup.POST("/login", func(c *gin.Context) {...})
  	} 	shopGroup := r.Group("/shop") 	{ 		shopGroup.GET("/index", func(c *gin.Context) {...}) 		shopGroup.GET("/cart", func(c *gin.Context) {...}) 		shopGroup.POST("/checkout", func(c *gin.Context) {...}) 	} 	r.Run() }
  | 
 
路由组也是支持嵌套的,例如:
1 2 3 4 5 6 7 8 9
   | shopGroup := r.Group("/shop") 	{ 		shopGroup.GET("/index", func(c *gin.Context) {...}) 		shopGroup.GET("/cart", func(c *gin.Context) {...}) 		shopGroup.POST("/checkout", func(c *gin.Context) {...}) 		 		xx := shopGroup.Group("xx") 		xx.GET("/oo", func(c *gin.Context) {...}) 	}
  | 
 
通常我们将路由分组用在划分业务逻辑或划分API版本时。
6.3 路由原理
Gin框架中的路由使用的是httprouter这个库。
其基本原理就是构造一个路由地址的前缀树。
七、Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
7.1 定义中间件
Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   |  func StatCost() gin.HandlerFunc { 	return func(c *gin.Context) { 		start := time.Now() 		c.Set("name", "小王子")  		 		c.Next() 		 		 		 		cost := time.Since(start) 		log.Println(cost) 	} }
 
  | 
 
7.2 注册中间件
在gin框架中,我们可以为每个路由添加任意数量的中间件。


中间的这个通过 Abort() 可以阻止执行

7.2.1 为全局路由注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | func main() { 	 	r := gin.New() 	 	r.Use(StatCost()) 	 	r.GET("/test", func(c *gin.Context) { 		name := c.MustGet("name").(string)  		log.Println(name) 		c.JSON(http.StatusOK, gin.H{ 			"message": "Hello world!", 		}) 	}) 	r.Run() }
  | 
 
7.2.2 为某个路由单独注册
1 2 3 4 5 6 7 8
   |  	r.GET("/test2", StatCost(), func(c *gin.Context) { 		name := c.MustGet("name").(string)  		log.Println(name) 		c.JSON(http.StatusOK, gin.H{ 			"message": "Hello world!", 		}) 	})
 
  | 
 
7.2.3 为路由组注册中间件
为路由组注册中间件有以下两种写法。
写法1:
1 2 3 4 5
   | shopGroup := r.Group("/shop", StatCost()) {     shopGroup.GET("/index", func(c *gin.Context) {...})     ... }
  | 
 
写法2:
1 2 3 4 5 6
   | shopGroup := r.Group("/shop") shopGroup.Use(StatCost()) {     shopGroup.GET("/index", func(c *gin.Context) {...})     ... }
  | 
 
7.3 中间件注意事项
gin.Default()默认使用了Logger和Recovery中间件,其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。 
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。 
如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。
八、运行多个服务
我们可以在多个端口启动服务,例如:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
   | package main
  import ( 	"log" 	"net/http" 	"time"
  	"github.com/gin-gonic/gin" 	"golang.org/x/sync/errgroup" )
  var ( 	g errgroup.Group )
  func router01() http.Handler { 	e := gin.New() 	e.Use(gin.Recovery()) 	e.GET("/", func(c *gin.Context) { 		c.JSON( 			http.StatusOK, 			gin.H{ 				"code":  http.StatusOK, 				"error": "Welcome server 01", 			}, 		) 	})
  	return e }
  func router02() http.Handler { 	e := gin.New() 	e.Use(gin.Recovery()) 	e.GET("/", func(c *gin.Context) { 		c.JSON( 			http.StatusOK, 			gin.H{ 				"code":  http.StatusOK, 				"error": "Welcome server 02", 			}, 		) 	})
  	return e }
  func main() { 	server01 := &http.Server{ 		Addr:         ":8080", 		Handler:      router01(), 		ReadTimeout:  5 * time.Second, 		WriteTimeout: 10 * time.Second, 	}
  	server02 := &http.Server{ 		Addr:         ":8081", 		Handler:      router02(), 		ReadTimeout:  5 * time.Second, 		WriteTimeout: 10 * time.Second, 	}     	g.Go(func() error { 		return server01.ListenAndServe() 	})
  	g.Go(func() error { 		return server02.ListenAndServe() 	})
  	if err := g.Wait(); err != nil { 		log.Fatal(err) 	} }
   |