> For the complete documentation index, see [llms.txt](https://yangsx95.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yangsx95.gitbook.io/notes/programming-language/golang/web-bian-cheng/gin.md).

# Gin

#### Gin

* 官方：[Github gin-gonic/gin](https://github.com/gin-gonic/gin)
* 下载：`go get -u github.com/gin-gonic/gin`
* 导入：`import "github.com/gin-gonic/gin"`

***

### 基本使用

#### 启动服务

```go
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
  // 定义路由
	r.GET("hello", func(c *gin.Context) {
		c.String(http.StatusOK, "hello")
	})
	// r.Run() // 默认监听8080
	r.Run(":8080") // 指定监听端口
}
```

#### 获取请求信息

```go
// 获取请求对象
request := c.Request

// 获取请求方法信息
// 此方法返回string类型
method := request.Method
fmt.Println(method)

// 获取请求uri信息
// 此方法返回 *url.URL 类型，可以获取URL中的所有信息
var url *url.URL = request.URL
fmt.Println(url)

// 获取请求头信息
// 此方法返回 http.Header 类型，可获取请求头中的所有信息
header := request.Header
fmt.Println(header)

// 获取请求体信息
// 此方法返回 io.ReadCloser 类型，可以从流中读取请求体数据
body := request.Body
fmt.Println(body)
```

#### 处理不同的请求方法

```go
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
```

#### 路径参数

必选的路径参数用`:`表示，`name`必须存在，否则不会匹配：

```go
router.GET("/user/:name", func(c *gin.Context) {
  name := c.Param("name")
  c.String(http.StatusOK, "Hello %s", name)
})
```

可选的路径参数用`*`表示，可以匹配`/user/lisi`和`/user/lisi/play`：

```go
router.GET("/user/:name/*action", func(c *gin.Context) {
  name := c.Param("name")
  action := c.Param("action")
  message := name + " is " + action
  c.String(http.StatusOK, message)
})
```

#### 查询参数

```go
router.GET("/welcome", func(c *gin.Context) {
  firstname := c.DefaultQuery("firstname", "Guest")
  lastname := c.Query("lastname") // 等同于 c.Request.URL.Query().Get("lastname")

  c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
```

#### Multipart/Urlencoded Form 表单处理

```go
router.POST("/form_post", func(c *gin.Context) {
  message := c.PostForm("message") // 获取表单字段，message
  nick := c.DefaultPostForm("nick", "anonymous") // 获取表单字段 nick，并给予缺省值

  c.JSON(200, gin.H{
    "status":  "posted",
    "message": message,
    "nick":    nick,
  })
})
```

#### 表单/查询参数数据转换map

```http
POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded

names[first]=thinkerou&names[second]=tianou
```

```go
router.POST("/post", func(c *gin.Context) {
  ids := c.QueryMap("ids")
  names := c.PostFormMap("names")

  fmt.Printf("ids: %v; names: %v", ids, names)
  // ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
})
```

#### 文件上传

**一次处理单个文件：**

```shell
curl -X POST http://localhost:8080/upload \
  -F "file=@/Users/appleboy/test.zip" \
  -H "Content-Type: multipart/form-data"
```

```go
// Set a lower memory limit for multipart forms (default is 32 MiB)
// 设置表单大小限制
router.MaxMultipartMemory = 8 << 20  // 8 MiB
router.POST("/upload", func(c *gin.Context) {
    // 从form中取出指定的文件字段
    file, _ := c.FormFile("file")
    log.Println(file.Filename)

    // 将文件保存到指定路径
    c.SaveUploadedFile(file, dst)

    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
  })
```

**一次处理多个文件：**

```shell
curl -X POST http://localhost:8080/upload \
  -F "upload[]=@/Users/appleboy/test1.zip" \
  -F "upload[]=@/Users/appleboy/test2.zip" \
  -H "Content-Type: multipart/form-data"
```

```go
router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// Multipart form
		form, _ := c.MultipartForm()
		files := form.File["upload[]"]

		for _, file := range files {
			log.Println(file.Filename)

			c.SaveUploadedFile(file, dst)
		}
		c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
	})
```

### Gin的中间件

Gin默认启动的路由（`gin.Default()`）自带集成两个中间件：

1. `Logger` 日志处理
2. `Recovery` 错误处理

#### 启动一个不带中间件的路由

```go
r := gin.New()
```

#### 启用某些中间件

```go
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
```

#### Logger全局中间件

**日志写入文件：**

```go
func main() {
  
    // Logging to a file.
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)

    // Use the following code if you need to write the logs to file and console at the same time.
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    router.Run(":8080")
}
```

**自定义日志格式：**

```go
func main() {
	router := gin.New()

	// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
	// By default gin.DefaultWriter = os.Stdout
	router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {

		// your custom format
		return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
				param.ClientIP,
				param.TimeStamp.Format(time.RFC1123),
				param.Method,
				param.Path,
				param.Request.Proto,
				param.StatusCode,
				param.Latency,
				param.Request.UserAgent(),
				param.ErrorMessage,
		)
	}))
	router.Use(gin.Recovery())

	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	router.Run(":8080")
}
```

**日志着色：**

```go
// 默认情况下，会根据选择的tty对日志进行着色
// 禁用日志着色
gin.DisableConsoleColor()

// 始终启用日志着色
gin.ForceConsoleColor()
```

#### Recovery全局中间件

### 路由分组

```go
v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}
```

#### 认证分组

```go
func main() {
	// Creates a router without any middleware by default
	r := gin.New()

	// Global middleware
	// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
	// By default gin.DefaultWriter = os.Stdout
	r.Use(gin.Logger())

	// Recovery middleware recovers from any panics and writes a 500 if there was one.
	r.Use(gin.Recovery())

	// Per route middleware, you can add as many as you desire.
	r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

	// Authorization group
	// authorized := r.Group("/", AuthRequired())
	// exactly the same as:
	authorized := r.Group("/")
	// per group middleware! in this case we use the custom created
	// AuthRequired() middleware just in the "authorized" group.
	authorized.Use(AuthRequired())
	{
		authorized.POST("/login", loginEndpoint)
		authorized.POST("/submit", submitEndpoint)
		authorized.POST("/read", readEndpoint)

		// nested group
		testing := authorized.Group("testing")
		testing.GET("/analytics", analyticsEndpoint)
	}

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}
```
