1. 解析参数
1.1. 绑定表单数据到结构体
下述代码实现了一个简单的Web应用程序,通过Gin框架处理HTTP请求,将从请求中获取的数据绑定到结构体变量,并生成JSON格式的HTTP响应体。
1.1.1. 完整代码
package main
import "github.com/gin-gonic/gin"
type StructA struct {
FieldA string `form:"field_a"` // FieldA 是 StructA 的字段,使用 form 标签 "field_a"
}
type StructB struct {
NestedStruct StructA // NestedStruct 是 StructB 的字段,类型为 StructA
FieldB string `form:"field_b"` // FieldB 是 StructB 的字段,使用 form 标签 "field_b"
}
type StructC struct {
NestedStructPointer *StructA // NestedStructPointer 是 StructC 的指针字段,指向 StructA
FieldC string `form:"field_c"` // FieldC 是 StructC 的字段,使用 form 标签 "field_c"
}
type StructD struct {
NestedAnonyStruct struct { // NestedAnonyStruct 是 StructD 的匿名结构体字段
FieldX string `form:"field_x"` // FieldX 是匿名结构体的字段,使用 form 标签 "field_x"
}
FieldD string `form:"field_d"` // FieldD 是 StructD 的字段,使用 form 标签 "field_d"
}
func GetDataB(c *gin.Context) {
var b StructB
c.Bind(&b) // 将请求数据绑定到 StructB
c.JSON(200, gin.H{
"a": b.NestedStruct, // 返回 NestedStruct 的值作为响应,键名为 "a"
"b": b.FieldB, // 返回 FieldB 的值作为响应,键名为 "b"
})
}
func GetDataC(c *gin.Context) {
var b StructC
c.Bind(&b) // 将请求数据绑定到 StructC
c.JSON(200, gin.H{
"a": b.NestedStructPointer, // 返回 NestedStructPointer 的值作为响应,键名为 "a"
"c": b.FieldC, // 返回 FieldC 的值作为响应,键名为 "c"
})
}
func GetDataD(c *gin.Context) {
var b StructD
c.Bind(&b) // 将请求数据绑定到 StructD
c.JSON(200, gin.H{
"x": b.NestedAnonyStruct, // 返回 NestedAnonyStruct 的值作为响应,键名为 "x"
"d": b.FieldD, // 返回 FieldD 的值作为响应,键名为 "d"
})
}
func main() {
r := gin.Default()
r.GET("/getb", GetDataB) // 注册 GetDataB 作为 GET /getb 路由的处理函数
r.GET("/getc", GetDataC) // 注册 GetDataC 作为 GET /getc 路由的处理函数
r.GET("/getd", GetDataD) // 注册 GetDataD 作为 GET /getd 路由的处理函数
r.Run() // 启动 Gin web 服务器
}
1.1.2. 知识点
-
c.Bind(&b):使用了Gin框架的Bind方法,将从HTTP请求中获取的数据绑定到b变量。&b表示传递b的指针,以便将数据绑定到b变量的字段。
-
c.JSON(200, gin.H{"a": b.NestedStruct, "b": b.FieldB}):使用Gin框架的JSON方法,生成HTTP响应的JSON格式。200表示HTTP响应的状态码为200,gin.H是Gin框架提供的一种快捷方式,表示一个map[string]interface{}类型的数据结构,用于构造JSON响应体。在这里,构造了一个包含两个键值对的JSON响应体,其中键为a和b,值分别为b.NestedStruct和b.FieldB,这些值来自之前绑定的b变量的字段。
-
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,通常用于在不同应用程序之间传输和存储数据。JSON采用键值对的形式表示数据,使用文本格式进行序列化,具有简洁、易读、易解析的特点。
1.1.3. 示例概述
-
定义了四个结构体类型:StructA、StructB、StructC、StructD,分别包含了不同的字段和标签。
-
在GetDataB处理函数中,从HTTP请求中绑定数据到StructB类型的变量b,然后生成JSON响应体,包含了b.NestedStruct和b.FieldB字段的值。
-
在GetDataC处理函数中,从HTTP请求中绑定数据到StructC类型的变量b,然后生成JSON响应体,包含了b.NestedStructPointer和b.FieldC字段的值。
-
在GetDataD处理函数中,从HTTP请求中绑定数据到StructD类型的变量b,然后生成JSON响应体,包含了b.NestedAnonyStruct和b.FieldD字段的值。
-
在main函数中,创建了一个Gin的默认路由器,并注册了三个路由(/getb、/getc、/getd),并将对应的处理函数(GetDataB、GetDataC、GetDataD)作为处理函数。最后调用r.Run()启动Gin的web服务器,监听并处理HTTP请求。
1.1.4. 使用 curl 命令结果:
-
命令行输入: curl "http://localhost:8080/getb?field_a=hello&field_b=world"
终端显示: {"a":{"FieldA":"hello"},"b":"world"} -
命令行输入: curl "http://localhost:8080/getc?field_a=hello&field_c=world"
终端显示: {"a":{"FieldA":"hello"},"c":"world"} -
命令行输入: curl "http://localhost:8080/getd?field_x=hello&field_d=world"
终端显示: {"d":"world","x":{"FieldX":"hello"}}
1.2. 绑定 HTML 复选框
1.2.1. 前置条件
在main.go文件的同目录下,存在
views/form.html
目录,且form.html文件里存放了HTML模板文件,如下:
<!DOCTYPE html>
<html>
<head>
<title>My Form Template</title>
</head>
<body>
<h1>Check some colors</h1>
<form action="/" method="POST">
<label for="red">Red</label>
<input type="checkbox" name="colors[]" value="red" id="red">
<label for="green">Green</label>
<input type="checkbox" name="colors[]" value="green" id="green">
<label for="blue">Blue</label>
<input type="checkbox" name="colors[]" value="blue" id="blue">
<input type="submit">
</form>
</body>
</html>
1.2.2. 完整代码
package main
import (
"github.com/gin-gonic/gin"
)
type myForm struct {
Colors []string `form:"colors[]"` /* 定义一个结构体 myForm,其中包含一个 Colors 字段,
用于接收表单提交的 colors[] 参数*/
}
func main() {
r := gin.Default()
r.LoadHTMLGlob("views/*") // 加载 views 目录下的 HTML 模板文件
r.GET("/", indexHandler) // 处理根路径的 GET 请求,调用 indexHandler 函数
r.POST("/", formHandler) // 处理根路径的 POST 请求,调用 formHandler 函数
r.Run(":8080") // 启动 Gin 服务器监听在 8080 端口
}
func indexHandler(c *gin.Context) {
c.HTML(200, "form.html", nil) // 在响应中渲染 form.html 模板并返回 200 状态码
}
func formHandler(c *gin.Context) {
var fakeForm myForm
c.Bind(&fakeForm) // 绑定请求参数到 myForm 结构体
c.JSON(200, gin.H{"color": fakeForm.Colors}) /* 返回 JSON 格式的响应,
包含 fakeForm 结构体中的 Colors 字段值*/
}
1.2.3. 知识点
-
r.LoadHTMLGlob("views/*")
: 加载views目录下的HTML模板文件,用于后续的HTML模板渲染。LoadHTMLGlob()方法接受一个模板文件路径的参数,使用通配符*可以匹配指定目录下的所有文件。 -
r.GET("/", indexHandler)
: 注册一个处理根路径的GET请求的路由,当客户端发送GET请求到根路径"/"时,Gin会调用indexHandler函数进行处理。indexHandler函数是自定义的处理函数,用于处理根路径的GET请求。 -
r.POST("/", formHandler)
: 注册一个处理根路径的POST请求的路由,当客户端发送POST请求到根路径"/"时,Gin会调用formHandler函数进行处理。formHandler函数是自定义的处理函数,用于处理根路径的POST请求。
1.2.4. 代码示意
-
定义了一个名为myForm的结构体,其中包含一个Colors字段,用于接收表单提交的
colors[]
参数。Colors字段使用了form:"colors[]"
标签,表示在表单中使用colors[]作为参数名。 -
在main()函数中创建了一个Gin引擎实例,并设置了默认的中间件。
-
使用r.LoadHTMLGlob("views/*")加载了views目录下的HTML模板文件,用于后续的HTML模板渲染。
-
使用r.GET("/", indexHandler)注册了一个处理根路径的GET请求的路由,当客户端发送GET请求到根路径"/"时,Gin会调用indexHandler函数进行处理。
-
使用r.POST("/", formHandler)注册了一个处理根路径的POST请求的路由,当客户端发送POST请求到根路径"/"时,Gin会调用formHandler函数进行处理。
-
定义了indexHandler函数,用于处理根路径的GET请求。该函数通过调用c.HTML()方法,在响应中渲染名为form.html的HTML模板,并返回200状态码。
-
定义了formHandler函数,用于处理根路径的POST请求。该函数首先定义了一个名为fakeForm的myForm类型的变量,然后通过调用c.Bind()方法,将请求参数绑定到fakeForm结构体中。最后,通过调用c.JSON()方法,返回一个JSON格式的响应,包含fakeForm结构体中的Colors字段值。
1.3. 绑定查询字符串或表单数据
1.3.1. 完整代码
package main
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string `form:"name"` // 表单参数 "name"
Address string `form:"address"` // 表单参数 "address"
Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
// 表单参数 "birthday",使用指定的时间格式和转换为 UTC 时间
}
func main() {
route := gin.Default() // 创建默认的 Gin 路由引擎
route.GET("/testing", startPage) // 注册 GET 请求处理函数 "/testing"
route.Run(":8085") // 启动 Gin 服务并监听在 8085 端口
}
func startPage(c *gin.Context) {
var person Person // 创建一个 Person 结构体变量用于存储请求参数
// 使用 Gin 的 ShouldBind 方法将请求参数绑定到 Person 结构体变量上
// 如果请求是 GET 请求,则使用 Form 绑定引擎(query)进行参数绑定
// 如果请求是 POST 请求,则首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)绑定引擎
// 详细绑定规则可以参考:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
if err := c.ShouldBind(&person); err == nil {//绑定成功时返回nil,反之返回错误信息
log.Println(person.Name) // 输出绑定后的 Name 字段值
log.Println(person.Address) // 输出绑定后的 Address 字段值
log.Println(person.Birthday)// 输出绑定后的 Birthday 字段值
} else {
log.Println("Error:", err)
}
c.String(200, "Success") // 返回 "Success" 字符串作为响应
}
1.3.2. 知识点
-
ShouldBind 是 Gin 框架提供的一个方法,用于将 HTTP 请求中的参数绑定到一个结构体变量上。ShouldBind 方法会根据请求中的参数名称和目标结构体的字段名称进行自动匹配,并将对应的参数值绑定到结构体字段上。在示例代码中的 Person 结构体定义中使用了 form 标签来指定参数的来源和绑定规则,如 form:"name"、form:"address" 和 form:"birthday" time_format:"2006-01-02" time_utc:"1"。
-
这样,在调用 ShouldBind 方法时,Gin 会根据请求中的表单参数名称和结构体字段的标签进行自动匹配和绑定。需要注意的是,ShouldBind 方法在绑定参数时会根据目标结构体字段的类型和标签进行自动转换和验证,如果绑定失败会返回错误信息。因此,在使用 ShouldBind 方法时,需要进行错误处理,可以通过判断返回的错误是否为 nil 来判断参数绑定是否成功。如果绑定失败,可以通过错误信息来获取详细的错误原因,并做相应的处理。
1.3.3. 代码示意
-
定义了一个 Person 结构体,用于存储请求参数。Person 结构体包含了三个字段:Name、Address 和 Birthday,分别对应请求中的表单参数 "name"、"address" 和 "birthday"。
-
创建了一个 Gin 路由引擎 route,使用了默认的中间件。
-
注册了一个 HTTP GET 请求的处理函数 startPage,该处理函数通过调用 Gin 的 ShouldBind 方法将请求参数绑定到 Person 结构体变量 person 上。ShouldBind 方法会根据请求的类型(GET 或 POST)和请求的 content-type 自动选择合适的绑定引擎(Form 或 JSON/XML 绑定引擎)进行参数绑定。
-
在 startPage 函数中,通过打印 person 结构体的字段值来验证参数绑定是否成功。
-
最后,返回了一个 HTTP 200 OK 响应,内容为 "Success" 字符串。
总的来说,这段代码实现了一个简单的 HTTP GET 请求处理函数,使用 Gin 框架来绑定请求参数并处理请求。当请求到达 /testing 路径时,会触发 startPage 函数,将请求参数绑定到 Person 结构体变量,并输出字段值。如果参数绑定失败,则输出错误信息。最后返回一个 "Success" 字符串作为响应。
1.3.4. 测试
http://localhost:8085/testing?name=ft&address=China&birthday=2023-04-11
若响应成功,可以看到页面中会显示Success;
若响应成功,且信息匹配成功,终端中会打印出信息。
1.4. 绑定 Uri到结构体
1.4.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
)
// Person 结构体用于绑定 URI 参数
type Person struct {
ID string `uri:"id" binding:"required,uuid"` // 将 URI 中的 id 参数绑定到 ID 字段,并指定必填和 UUID 格式验证
Name string `uri:"name" binding:"required"` // 将 URI 中的 name 参数绑定到 Name 字段,并指定必填验证
}
func main() {
route := gin.Default() // 创建默认的 Gin 路由引擎
// 定义路由处理函数
route.GET("/:name/:id", func(c *gin.Context) {
var person Person
// 使用 ShouldBindUri 方法将 URI 参数绑定到 Person 结构体
if err := c.ShouldBindUri(&person); err != nil { //绑定成功,返回nil
c.JSON(400, gin.H{"msg": err.Error()}) // 如果绑定失败,返回错误信息
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID}) // 如果绑定成功,返回绑定后的值
})
route.Run(":8088")
}
1.4.2. 知识点
-
c.ShouldBindUri(&person): 这是 *gin.Context 上下文对象的方法,用于将 URI 参数绑定到结构体变量 person 上。ShouldBindUri 方法会自动根据结构体字段的 uri 标签和 URI 路径中的参数名称进行匹配和绑定。在示例代码中,ID 字段的 uri 标签指定了参数名称为 id,Name 字段的 uri 标签指定了参数名称为 name。同时,结构体字段的 binding 标签还指定了参数的验证规则,例如 required 表示参数必填,uuid 表示参数的值必须是 UUID 格式。
-
c.JSON(400, gin.H{"msg": err.Error()})
: 这是 *gin.Context 上下文对象的方法,用于构建一个 JSON 格式的 HTTP 响应。其中,400 表示 HTTP 状态码为 400(Bad Request),gin.H{"msg": err.Error()} 表示响应的 JSON 数据,包含一个名为 msg 的字段,值为错误信息的字符串。 -
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
: 这是 *gin.Context 上下文对象的方法,用于构建一个 JSON 格式的 HTTP 响应。其中,200 表示 HTTP 状态码为 200(OK),gin.H{"name": person.Name, "uuid": person.ID} 表示响应的 JSON 数据,包含两个字段,分别为 name 和 uuid,值分别为 person.Name 和 person.ID 的值,即绑定后的 URI 参数值。
1.4.3. 示例概述
上述代码使用了 Gin 框架来创建一个 HTTP 服务器,并定义了一个 GET 请求处理函数,该处理函数绑定了两个 URI 参数(:name 和 :id)到一个自定义的结构体 Person 的字段上。具体而言,代码实现了以下几个功能:
- 导入了 github.com/gin-gonic/gin 包,该包是 Gin 框架的核心库,提供了用于构建 Web 应用的 HTTP 路由和中间件功能。
- 定义了一个名为 Person 的结构体,该结构体包含了两个字段 ID 和 Name,分别用于绑定 URI 参数 id 和 name。
- 创建了一个默认的 Gin 路由引擎实例 route,用于处理 HTTP 请求和路由。
- 定义了一个路由处理函数,处理 HTTP GET 请求,该处理函数绑定了路由路径中的 :name 和 :id 参数到 Person 结构体的字段上,并在绑定成功后返回绑定后的值,否则返回错误信息。
- 使用 c.ShouldBindUri(&person) 方法将 URI 参数绑定到 Person 结构体的字段上。ShouldBindUri 方法会根据结构体字段的标签信息来解析和验证 URI 参数,并将解析后的值赋给结构体字段。
- 如果 URI 参数绑定失败(例如,缺少必填参数或参数格式不符合预期),则通过 c.JSON 方法返回 HTTP 状态码 400(Bad Request)和错误信息。
- 如果 URI 参数绑定成功,则通过 c.JSON 方法返回 HTTP 状态码 200(OK)和绑定后的值,包括 Person 结构体中的 Name 和 ID 字段的值。
- 最后,通过 route.Run(":8088") 启动 HTTP 服务器,监听在本地的 8088 端口上。一旦有请求到达该端口,将会由 Gin 路由引擎处理并调用相应的路由处理函数。
1.4.4. 测试
网页:
http://localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
http://localhost:8088/thinkerou/not-uuid
终端:
Invoke-WebRequest -Uri "http://localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3" -Method GET
Invoke-WebRequest -Uri "http://localhost:8088/thinkerou/not-uuid" -Method GET
1.5. 绑定 url 查询字符串到结构体
1.5.1. 完整代码
package main
import (
"log"
"github.com/gin-gonic/gin"
)
type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
func main() {
router := gin.Default()
// 定义路由 "/testing",并将请求交给 startPage 函数处理
router.Any("/testing", startPage)
router.Run(":8085") // 启动 HTTP 服务器并监听在端口 8085
}
func startPage(c *gin.Context) {
var person Person
// 使用 ShouldBindQuery 方法将请求中的 Query String 绑定到 person 对象
if c.ShouldBindQuery(&person) == nil {
log.Println("====== Only Bind By Query String ======")
log.Println(person.Name)
log.Println(person.Address)
}
c.String(200, "Success") // 返回 "Success" 字符串作为响应
}
1.5.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并定义了一个路由处理函数 startPage 用于处理 "/testing" 的请求,可以处理 GET、POST、PUT、DELETE 等任何请求方法。在路由处理函数中,通过 c.ShouldBindQuery() 方法将请求中的 Query String 绑定到 person 对象,其中 person.Name 和 person.Address 分别对应了请求中的 "name" 和 "address" 参数。
接着使用 log.Println() 方法输出绑定后的字段值到控制台。
最后,通过调用 c.String() 方法返回状态码 200 和 "Success" 字符串作为响应,表示处理成功。HTTP 服务器监听在端口 8085,并通过 router.Run() 启动。
1.6. 绑定路径中的参数
1.6.1. 完整代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// 这个处理函数将匹配 /user/john,但不会匹配 /user/ 或 /user
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
// 这个处理函数将匹配 /user/john/ 和 /user/john/send
// 如果没有其他路由匹配 /user/john,它将重定向到 /user/john/
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)
})
router.Run(":8080")
}
1.6.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并定义了两个路由处理函数。第一个处理函数通过路由 "/user/:name" 匹配 URL 中的 ":name" 参数,并将其值绑定到变量 name 中,然后通过 c.String() 方法返回 "Hello %s" 字符串,其中 %s 会被 name 的值替代。
第二个处理函数通过路由 "/user/:name/action" 匹配 URL 中的 ":name" 参数和 "action" 参数,"*action" 表示匹配 "/user/:name" 后面的任意路径,将其值绑定到变量 name 和 action 中,然后通过 c.String() 方法返回包含 name 和 action 的消息字符串。
在 Gin 框架中,路由参数可以通过 :paramName 的方式定义,表示在 URL 中匹配该位置的任意字符串,并将其值作为参数传递给路由处理函数。同时,Gin 还支持通配符 "*",可以匹配任意路径片段,用于处理较为复杂的路由需求。
1.7. 绑定表单数据
1.7.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message") // 获取表单中的 "message" 字段值
nick := c.DefaultPostForm("nick", "anonymous") // 获取表单中的 "nick" 字段值,如果为空则默认为 "anonymous"
c.JSON(200, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
router.Run(":8080") // 启动 HTTP 服务器并监听在端口 8080
}
1.7.2. 知识点
-
c.PostForm("message"): 使用 c 上下文对象的 PostForm 方法获取表单中名为 "message" 的字段的值。
-
c.DefaultPostForm("nick", "anonymous"): 使用 c 上下文对象的 DefaultPostForm 方法获取表单中名为 "nick" 的字段的值,如果该字段为空,则返回默认值 "anonymous"。
1.7.3. 示例概述
-
导入了 "github.com/gin-gonic/gin" 包,引入 Gin 框架的依赖。
-
创建了一个 Gin 路由引擎,使用了默认的中间件。
-
定义了一个处理 POST 请求的路由 "/form_post",并传入一个处理函数。
-
在处理函数中,使用了 c.PostForm 方法获取表单中的 "message" 字段值,并保存到变量 message 中。
-
使用了 c.DefaultPostForm 方法获取表单中的 "nick" 字段值,并保存到变量 nick 中,如果 "nick" 字段为空,则默认值为 "anonymous"。
-
使用了 c.JSON 方法返回 JSON 格式的响应,包含了三个键值对: "status"、"message" 和 "nick"。
-
调用 router.Run 方法启动 HTTP 服务器并监听在端口 8080,等待客户端的请求。
1.8. 解析POST 表单中的 URL 参数
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=manu&message=this_is_great
1.8.1. 完整代码
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
id := c.Query("id") // 获取 URL 查询参数 "id"
page := c.DefaultQuery("page", "0") // 获取 URL 查询参数 "page",如果不存在则默认为 "0"
name := c.PostForm("name") // 获取 POST 表单字段 "name"
message := c.PostForm("message") // 获取 POST 表单字段 "message"
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
})
router.Run(":8080")
}
1.8.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并定义了一个处理 POST 请求的路由处理函数。该处理函数从请求中获取 URL 查询参数 "id" 和 "page",以及 POST 表单字段 "name" 和 "message" 的值,并将其打印输出。
使用 c.Query() 方法可以获取 URL 查询参数的值,其中参数为参数名。而使用 c.PostForm() 方法可以获取 POST 表单字段的值,同样参数为字段名。如果 POST 表单中没有指定的字段,可以使用 c.DefaultPostForm() 方法设置一个默认值。
最后,通过调用 router.Run() 方法在 ":8080" 上启动 HTTP 服务器并监听请求。Gin 框架的 Run() 方法会自动选择可用的端口并启动服务器。
1.9. 解析查询字符串参数
1.9.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// 使用现有的基础请求对象解析查询字符串参数。
// 示例 URL: /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest") // 获取查询参数 "firstname",如果不存在则默认为 "Guest"
lastname := c.Query("lastname") // 获取查询参数 "lastname"
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
1.9.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并定义了一个处理 GET 请求的路由处理函数。该处理函数从 URL 查询参数 "firstname" 和 "lastname" 的值,并将其组合成字符串后作为响应返回。
使用 c.Query() 方法可以获取 URL 查询参数的值,其中参数为参数名。而使用 c.DefaultQuery() 方法可以获取 URL 查询参数的值,并设置一个默认值,当查询参数不存在时使用。这在示例中用于获取 "firstname" 的值,并设置默认值为 "Guest"。
Gin 框架的 Run() 方法会自动选择可用的端口并启动 HTTP 服务器,通过调用 router.Run(":8080") 在 ":8080" 上启动 HTTP 服务器并监听请求。
1.10. 解析reader 读取数据
1.10.1. 完整代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的 Gin 路由引擎
router := gin.Default()
// 定义处理路由 /someDataFromReader 的 GET 请求的处理函数
router.GET("/someDataFromReader", func(c *gin.Context) {
// 发起 HTTP GET 请求获取远程文件
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
// 如果请求出错或者响应状态码不是 200 OK,则返回 503 Service Unavailable 状态码
c.Status(http.StatusServiceUnavailable)
return
}
// 获取响应的 Body、ContentLength 和 ContentType
reader := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
// 构建额外的响应头
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="gopher.png"`,
}
// 使用 c.DataFromReader 方法将文件数据作为响应返回给客户端
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
// 启动 HTTP 服务器并监听在 :8080 地址上
router.Run(":8080")
}
1.10.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并注册了一个处理路由 "/someDataFromReader" 的 GET 请求的处理函数。
-
gin.Default() 创建了一个默认的 Gin 路由引擎。
-
router.GET("/someDataFromReader", func(c *gin.Context) { ... }) 定义了处理路由 "/someDataFromReader" 的 GET 请求的处理函数。函数体中的代码会在接收到 "/someDataFromReader" 的 GET 请求时执行。
-
http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png") 发起了一个 HTTP GET 请求,获取远程文件的内容。如果请求出错或者响应状态码不是 200 OK,会返回 503 Service Unavailable 状态码给客户端。
-
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders) 使用 Gin 提供的 c.DataFromReader 方法将文件数据作为响应返回给客户端。http.StatusOK 是响应的状态码,contentLength 是文件的长度,contentType 是文件的 MIME 类型,reader 是文件的内容,extraHeaders 是一些额外的响应头,这里设置了 Content-Disposition 头,指定文件名为 "gopher.png"。
-
router.Run(":8080") 启动 HTTP 服务器并监听在 ":8080" 地址上,开始接受客户端的请求。
总体来说,这段代码实现了一个简单的 HTTP 服务器,通过处理 GET 请求获取远程文件内容,并将文件数据作为响应返回给客户端,同时设置了文件的 Content-Disposition 头,指定文件名为 "gopher.png"。
1.11. 解析模型绑定和验证
1.11.1. 完整代码
// Login 是一个结构体,用于绑定登录请求的 JSON、XML、HTML 表单数据
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"` // User 字段,对应请求中的 "user" 参数,使用 form、json、xml 格式进行绑定,并设置为必需字段
Password string `form:"password" json:"password" xml:"password" binding:"required"` // Password 字段,对应请求中的 "password" 参数,使用 form、json、xml 格式进行绑定,并设置为必需字段
}
func main() {
router := gin.Default() // 创建一个默认的 Gin 路由实例
// 绑定 JSON 请求 ("/loginJSON")
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err != nil { // 使用 ShouldBindJSON 方法将请求中的 JSON 数据绑定到 Login 结构体,并检查是否出现错误
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) // 若绑定失败,返回错误信息到客户端
return
}
if json.User != "manu" || json.Password != "123" { // 检查登录信息是否匹配
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) // 若登录信息不匹配,返回未授权状态码和错误信息到客户端
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) // 登录成功,返回状态码和登录成功信息到客户端
})
// 绑定 XML 请求 ("/loginXML")
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil { // 使用 ShouldBindXML 方法将请求中的 XML 数据绑定到 Login 结构体,并检查是否出现错误
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) // 若绑定失败,返回错误信息到客户端
return
}
if xml.User != "manu" || xml.Password != "123" { // 检查登录信息是否匹配
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) // 若登录信息不匹配,返回未授权状态码和错误信息到客户端
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) // 登录成功,返回状态码和登录成功信息到客户端
})
// 绑定 HTML 表单 (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// 根据 Content-Type Header 推断使用哪个绑定器。
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// 监听并在 0.0.0.0:8080 上启动服务
router.Run(":8080")
}
1.11.2. 代码解释
-
定义了一个名为 Login 的结构体,其中包含了 User 和 Password 两个字段,用于接收登录请求中的用户和密码信息。结构体中使用了标签(tag)来指定了字段在不同请求中的绑定方式,如 form:"user" 表示在 HTML 表单中使用 user 字段名进行绑定,json:"user" 表示在 JSON 中使用 user 字段名进行绑定,xml:"user" 表示在 XML 中使用 user 字段名进行绑定。
-
在 main 函数中,创建了一个默认的 Gin 路由实例,并注册了三个路由处理函数分别处理 /loginJSON、/loginXML、/loginForm 这三个不同的路由。
-
/loginJSON 路由处理函数通过 c.ShouldBindJSON(&json) 方法将请求体中的 JSON 数据绑定到 json 结构体变量中,如果绑定失败,则返回错误信息;如果绑定成功,则判断 json.User 和 json.Password 是否符合预期的值,如果不符合,则返回未授权的状态码和错误信息,否则返回登录成功的状态码和信息。
-
/loginXML 路由处理函数通过 c.ShouldBindXML(&xml) 方法将请求体中的 XML 数据绑定到 xml 结构体变量中,如果绑定失败,则返回错误信息;如果绑定成功,则判断 xml.User 和 xml.Password 是否符合预期的值,如果不符合,则返回未授权的状态码和错误信息,否则返回登录成功的状态码和信息。
-
/loginForm 路由处理函数通过 c.ShouldBind(&form) 方法根据请求头中的 Content-Type 判断请求体中的数据类型,并将数据绑定到 form 结构体变量中,如果绑定失败,则返回错误信息;如果绑定成功,则判断 form.User 和 form.Password 是否符合预期的值,如果不符合,则返回未授权的状态码和错误信息,否则返回登录成功的状态码和信息。
-
最后通过 router.Run(":8080") 启动了服务,监听在 0.0.0.0:8080 地址上。
1.12. 解析路由参数
1.12.1. 完整代码
func main() {
// 创建一个默认的 gin 路由引擎
router := gin.Default()
// 注册一个处理器函数,处理匹配到的路由 /user/:name
// :name 表示该路由段为参数,可以匹配任何字符串,并将其作为参数传递给处理器函数
router.GET("/user/:name", func(c *gin.Context) {
// 从路由参数中获取名字参数值
name := c.Param("name")
// 在响应中返回 "Hello {name}" 字符串
c.String(http.StatusOK, "Hello %s", name)
})
// 注册一个处理器函数,处理匹配到的路由 /user/:name/*action
// :name 和 *action 都表示参数,:name 匹配一个字符串,*action 匹配多个字符串
// 如果没有其他路由匹配 /user/:name,该处理器函数将处理该请求并进行重定向到 /user/:name/
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)
})
// 启动 HTTP 服务器并监听在 :8080 上
router.Run(":8080")
}
1.12.2. 代码解释
-
第一个处理器函数处理了路由 /user/:name。:name 表示该路由段为参数,可以匹配任何字符串,并将其作为参数传递给处理器函数。在处理器函数中,通过 c.Param("name") 获取路由参数中的 name 值,并使用 c.String(http.StatusOK, "Hello %s", name) 在响应中返回 "Hello {name}" 字符串。
-
第二个处理器函数处理了路由 /user/:name/action。:name 和 action 都表示参数,其中 :name 匹配一个字符串,*action 匹配多个字符串。在处理器函数中,通过 c.Param("name") 和 c.Param("action") 获取路由参数中的 name 和 action 值,并根据这些参数值构建消息字符串。然后,使用 c.String(http.StatusOK, message) 在响应中返回该消息字符串。需要注意的是,如果没有其他路由匹配 /user/:name,该处理器函数将处理该请求并进行重定向到 /user/:name/。
最后,通过调用 router.Run(":8080") 启动 HTTP 服务器并监听在 :8080 端口上。一旦服务器启动成功,它将开始监听来自客户端的 HTTP 请求,并根据注册的路由处理器函数来处理这些请求。Gin 框架提供了很多方便的方法来处理路由、请求和响应,使得构建 HTTP 服务器变得简单和高效。
1.13. 解析request body 到不同结构体
1.13.1. 完整代码
一般通过调用 c.Request.Body 方法绑定数据,但不能多次调用这个方法。
type formA struct {
Foo string `json:"foo" xml:"foo" binding:"required"`
}
type formB struct {
Bar string `json:"bar" xml:"bar" binding:"required"`
}
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// 尝试使用 c.ShouldBind() 方法将请求体绑定到 formA 结构体对象
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 如果绑定失败,则尝试将请求体绑定到 formB 结构体对象
} else if errB := c.ShouldBind(&objB); errB == nil {
c.String(http.StatusOK, `the body should be formB`)
// 如果两次绑定都失败,则执行其他逻辑
} else {
...
}
}
1.13.2. 代码解释
以上代码是一个使用 Gin 框架的 HTTP 请求处理函数 SomeHandler,它通过两次调用 c.ShouldBind() 方法尝试将请求体绑定到两个不同的结构体对象 formA 和 formB。首先尝试将请求体绑定到 formA 对象,如果绑定成功,则返回一个字符串 "the body should be formA"。如果绑定失败,则尝试将请求体绑定到 formB 对象,如果绑定成功,则返回一个字符串 "the body should be formB"。如果两次绑定都失败,则执行其他逻辑。需要注意的是,c.ShouldBind() 方法使用了 c.Request.Body,一旦读取,请求体将变为 EOF,无法再次使用,因此在使用该方法时需要注意不可重用的特性。
2. 响应信息
2.1. 响应AsciiJSON
使用 AsciiJSON 生成具有转义的非 ASCII 字符的 ASCII-only JSON。
2.1.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default() // 创建一个 Gin 路由引擎
r.GET("/someJSON", func(c *gin.Context) { // 注册一个 GET 路由,并定义处理函数
data := map[string]interface{}{ // 定义一个 map 类型的数据,用于构造 JSON 响应
"lang": "GO语言", // 设置 "lang" 键的值为 "GO语言"
"tag": "<br>", // 设置 "tag" 键的值为 "<br>"
}
c.AsciiJSON(http.StatusOK, data) // 使用 c.AsciiJSON() 方法将 map 数据转换为 ASCII JSON 格式,并作为响应发送给客户端
})
r.Run(":8080") // 启动 Gin web 服务器,监听在 8080 端口
}
2.1.2. 知识点
-
gin.Default(): 这是 Gin 框架的一个方法,用于创建一个带有默认中间件的路由引擎,包括了 logger 和 recovery 中间件,用于记录请求日志和在出现 panic 时进行恢复,防止程序崩溃。
-
r.GET("/someJSON", ...):路由注册方法,指定一个路径为"/someJSON"的GET请求,当客户端请求"some/JSON"路径时,会调用后面的处理函数来处理该请求。
-
fun(c *gin.Context) {...}:这是一个匿名函数,作为路由处理函数,接收一个gin.Context参数。
-
gin.Context:是Gin框架的一个上下文对象,用于在处理HTTP请求和生成HTTP响应时传递和管理数据。每当 Gin 框架处理一个HTTP 请求时,都会创建一个新的 gin.Context 对象,并将其传递给注册的路由处理函数。它包含了很多有用的方法和属性,用于访问HTTP请求的各种信息,如请求路径、HTTP方法、球请求参数、请求头、请求体等。
-
data := map[string]interface{}{...}:创建了一个map[string]interface{}类型的变量data,用于构造要返回的JSON数据。这里使用了一个简单的map,包含了连个键值对,分别是"lang"和"tag"。
-
c.AsciiJSON(http.StatusOK, data):这是gin.Context的方法,用于将data变量的内容作为ASCII JSON格式的响应返回给客户端。http.StatusOK是HTTP状态码,表示请求成功,这里将其作为响应的状态码。data 变量作为响应的主体内容,通过 ASCII JSON 格式进行序列化,并设置了响应的 Content-Type为 "application/json; charset=utf-8"。最终,这个处理函数会将生成的响应发送给客户端。
2.1.3. 示例概述
-
导入了 gin 和 net/http 包,引入了 Gin 框架和标准库中处理 HTTP 请求和响应的功能。
-
创建了一个默认的 Gin 路由引擎 r,作为 HTTP 请求的入口。
-
注册了一个 GET 路由,路径为 "/someJSON",并定义了一个处理函数,处理函数使用 c.AsciiJSON() 方法将一个定义好的 map 数据转换为 ASCII JSON 格式,并作为响应发送给客户端。
-
使用 r.Run() 方法启动了一个 Gin web 服务器,并监听在 8080 端口,等待客户端的 HTTP 请求。
-
当客户端通过浏览器或其他 HTTP 客户端访问 "/someJSON" 路径时,服务器会返回一个 JSON 格式的响应,包含了定义好的 data 数据,其中 "lang" 键的值为 "GO语言","tag" 键的值为 " "。这个响应的内容类型为 ASCII JSON。
2.2. 响应SecureJSON
使用 SecureJSON 防止 json 劫持。如果给定的结构是数组值,则默认预置 "while(1)," 到响应体。
2.2.1. 完整代码
func main() {
r := gin.Default() // 创建一个默认的 Gin 实例
// 你也可以使用自己的 SecureJSON 前缀
// r.SecureJsonPrefix(")]}',\n")
r.GET("/someJSON", func(c *gin.Context) { // 注册处理路由 "/someJSON" 的 GET 请求的处理函数
names := []string{"lena", "austin", "foo"} // 创建一个字符串切片 names
// 将 names 切片作为 JSON 响应返回给客户端
// 使用 http.StatusOK 表示 HTTP 响应的状态码为 200 OK
// 并在返回的 JSON 数据前加上安全前缀,防止 JSON 劫持攻击
c.SecureJSON(http.StatusOK, names)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
2.2.2. 代码解释
这段代码使用 Gin 框架创建一个 HTTP 服务器,并定义了一个路由处理函数。代码的主要逻辑如下:
-
使用 gin.Default() 创建一个默认的 Gin 实例 r,它包含了一些常用的中间件,如日志、恢复等。
-
注册一个处理路由 /someJSON 的 GET 请求的处理函数。该处理函数接收一个 gin.Context 对象 c,用于处理 HTTP 请求和构建 HTTP 响应。
-
在处理函数中,创建一个字符串切片 names,包含了三个字符串元素。
-
使用 c.SecureJSON(http.StatusOK, names) 将 names 切片作为 JSON 响应返回给客户端。这里使用了 http.StatusOK 表示 HTTP 响应的状态码为 200 OK,并且在返回的 JSON 数据前加上了安全前缀,防止 JSON 劫持攻击。
-
调用 r.Run(":8080") 启动 HTTP 服务器,监听在 0.0.0.0:8080 地址上。
这段代码的功能是创建一个简单的 HTTP 服务器,当客户端通过 /someJSON 路径发送 GET 请求时,返回一个包含三个字符串元素的 JSON 数组作为响应。服务器监听在 0.0.0.0:8080 地址上,并使用 Gin 框架处理路由和构建 HTTP 响应。
2.3. 响应JSONP
使用 JSONP 向不同域的服务器请求数据。如果查询参数存在回调,则将回调添加到响应体中。
2.3.1. 完整代码
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/JSONP", func(c *gin.Context) {
data := map[string]interface{}{
"foo": "bar",
}
// 获取 callback 参数值
callback := c.Query("callback")
// 使用 JSONP 格式返回数据
c.JSONP(http.StatusOK, gin.H{
"callback": callback,
"data": data,
})
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
2.3.2. 知识点
c.Query("callback"): c.Query 方法用于获取 HTTP 请求中的查询参数。这里使用 c.Query("callback") 获取了名为 "callback" 的查询参数的值
c.JSONP(http.StatusOK, gin.H{...}): c.JSONP 方法用于将 JSON 数据以 JSONP 格式返回给客户端。http.StatusOK 表示 HTTP 响应状态码为 200,gin.H{...} 表示要返回的 JSON 数据。这里返回了一个包含 "callback" 和 "data" 两个字段的 JSON 数据,其中 "callback" 字段的值为上一步获取的查询参数 "callback" 的值,"data" 字段的值为一个包含 "foo" 字段的 map 数据。
2.4. 响应PureJSON
通常,JSON 使用 unicode 替换特殊 HTML 字符,例如 < 变为 \ u003c。如果要按字面对这些字符进行编码,则可以使用 PureJSON。Go 1.6 及更低版本无法使用此功能。
2.4.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 提供 unicode 实体
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// 提供字面字符
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
2.4.2. 代码解释
这段代码使用了 Gin 框架创建了一个 HTTP 服务器,并定义了三个路由处理函数。其中 /json 路由使用 c.JSON() 方法返回 JSON 格式的响应体,其中包含一个名为 "html" 的字段,值为 "Hello, world!"。c.JSON() 方法会将响应体序列化为 JSON 格式并设置相应的 Content-Type 头,使得返回的响应体在客户端以 JSON 格式展示。
而 /purejson 路由使用 c.PureJSON() 方法同样返回 JSON 格式的响应体,但不会对响应体中的特殊字符进行转义,保留原始字符。这在某些情况下可能有用,例如当需要返回包含 HTML 标签等特殊字符的响应体时。
最后,通过调用 r.Run() 方法在 0.0.0.0:8080 上启动 HTTP 服务器并监听请求。Gin 框架的 Run() 方法会自动选择可用的端口并启动服务器。
2.5. XML/JSON/YAML/ProtoBuf 渲染
2.5.1. 完整代码
func main() {
r := gin.Default() // 创建一个默认的 Gin 路由实例
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
// 处理 "/someJSON" 路由的 GET 请求,返回一个 JSON 响应
// 使用 gin.H 快捷方式创建一个 map[string]interface{} 类型的数据作为 JSON 数据
// JSON 数据包含 "message" 和 "status" 两个字段,值分别为 "hey" 和 http.StatusOK
// HTTP 状态码设置为 http.StatusOK
})
r.GET("/moreJSON", func(c *gin.Context) {
// 可以使用结构体来定义 JSON 数据
var msg struct {
Name string `json:"user"` // 可以使用 `json:"..."` 标签定义 JSON 字段名
Message string
Number int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// 定义一个结构体变量 msg,设置其字段的值
// 注意 msg.Name 在 JSON 中变成了 "user"
// 将输出:{"user": "Lena", "Message": "hey", "Number": 123}
c.JSON(http.StatusOK, msg)
// 处理 "/moreJSON" 路由的 GET 请求,返回一个 JSON 响应
// 使用结构体变量 msg 作为 JSON 数据
// JSON 数据包含三个字段,分别为 "user"、"Message" 和 "Number",对应结构体的字段值
// HTTP 状态码设置为 http.StatusOK
})
r.GET("/someXML", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
// 处理 "/someXML" 路由的 GET 请求,返回一个 XML 响应
// 使用 gin.H 快捷方式创建一个 map[string]interface{} 类型的数据作为 XML 数据
// XML 数据包含 "message" 和 "status" 两个字段,值分别为 "hey" 和 http.StatusOK
// HTTP 状态码设置为 http.StatusOK
})
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
// 处理 "/someYAML" 路由的 GET 请求,返回一个 YAML 响应
// 使用 gin.H 快捷方式创建一个 map[string]interface{} 类型的数据作为 YAML 数据
// YAML 数据包含 "message" 和 "status" 两个字段,值分别为 "hey" 和 http.StatusOK
// HTTP 状态码设置为 http.StatusOK
})
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{int64(1), int64(2)}
label := "test"
// 定义一个切片 reps 和一个字符串 label
// 用于创建一个 protoexample.Test 结构体变量 data
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
// 请注意,数据在响应中变为二进制数据
// 将输出被 protoexample.Test protobuf 序列化了的数据
c.ProtoBuf(http.StatusOK, data)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
2.6. 静态文件服务
2.6.1. 完整代码
func main() {
router := gin.Default() // 创建一个 Gin 路由引擎实例
// 注册静态文件路由,将请求 "/assets" 的静态文件从当前目录下的 "./assets" 目录中返回给客户端
router.Static("/assets", "./assets")
// 注册基于文件系统的静态文件路由,将请求 "/more_static" 的静态文件从指定的文件系统路径 "my_file_system" 中返回给客户端
router.StaticFS("/more_static", http.Dir("my_file_system"))
// 注册单个文件的静态文件路由,将请求 "/favicon.ico" 的静态文件从当前目录下的 "./resources/favicon.ico" 文件返回给客户端
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
// 监听并在 0.0.0.0:8080 上启动服务
router.Run(":8080")
}
2.6.2. 代码解释
以上代码使用 Gin 框架创建了一个简单的静态文件服务器。通过注册静态文件路由,可以将指定目录下的静态文件返回给客户端,例如图片、CSS、JavaScript 等资源文件,以供客户端的浏览器进行访问和加载。这对于构建前端应用或者提供静态资源文件的需求非常有用。在代码中使用了不同的静态文件路由注册方式,包括 router.Static()、router.StaticFS() 和 router.StaticFile(),用于处理不同的静态文件请求。最后,通过调用 router.Run(":8080") 启动 HTTP 服务器,监听地址为 "0.0.0.0:8080",并开始接收并处理客户端的请求。
2.7. 静态资源嵌入
2.7.1. 完整代码
func main() {
r := gin.New() // 创建一个新的 Gin 路由实例
t, err := loadTemplate() // 调用 loadTemplate() 函数加载模板
if err != nil {
panic(err) // 如果加载模板过程中出现错误,则触发 panic
}
r.SetHTMLTemplate(t) // 将加载好的模板设置到 Gin 路由实例的 HTML 模板中
r.GET("/", func(c *gin.Context) { // 定义路由处理函数,处理 HTTP GET 请求,路径为 "/"
c.HTML(http.StatusOK, "/html/index.tmpl", nil) // 返回 HTML 响应,使用加载的模板渲染数据,并设置 HTTP 状态码为 200
})
r.Run(":8080") // 启动 HTTP 服务器,监听在端口 8080 上
}
// loadTemplate 加载由 go-assets-builder 嵌入的模板
func loadTemplate() (*template.Template, error) {
t := template.New("") // 创建一个空的模板
for name, file := range Assets.Files { // 遍历由 go-assets-builder 嵌入的文件
if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
continue // 如果文件是目录或者不是以 .tmpl 后缀结尾,则跳过
}
h, err := ioutil.ReadAll(file) // 读取文件内容
if err != nil {
return nil, err // 如果读取文件内容过程中出现错误,则返回错误
}
t, err = t.New(name).Parse(string(h)) // 将文件内容解析为模板,并将其命名为 name,与之前的模板形成模板链
if err != nil {
return nil, err // 如果解析过程中出现错误,则返回错误
}
}
return t, nil // 返回加载好的模板和 nil 错误,表示加载模板成功
}
2.7.2. 代码解释
上述代码是一个简单的 Go 语言程序,使用了 Gin 框架和 go-assets-builder 库来创建一个 HTTP 服务器,并加载嵌入在程序中的模板文件进行渲染。下面对代码进行详细的分析:
-
func main() 是 Go 语言程序的入口函数,程序从这里开始执行。
-
gin.New() 创建了一个新的 Gin 路由实例,用于处理 HTTP 请求和定义路由。
-
loadTemplate() 函数用于加载由 go-assets-builder 嵌入的模板文件,并返回解析后的模板和可能的错误。
-
r.SetHTMLTemplate(t) 将加载好的模板设置到 Gin 路由实例的 HTML 模板中,以便后续使用模板进行渲染。
-
r.GET("/", func(c *gin.Context) { ... }) 定义了一个路由处理函数,处理根路径的 HTTP GET 请求。当用户访问根路径时,Gin 框架会调用这个处理函数来处理请求。
-
c.HTML(http.StatusOK, "/html/index.tmpl", nil) 在路由处理函数中调用 c.HTML() 方法返回一个 HTML 响应。这个方法使用加载的模板渲染数据,并设置 HTTP 状态码为 200(http.StatusOK)。
-
r.Run(":8080") 启动 HTTP 服务器,监听在 8080 端口上,开始接收和处理 HTTP 请求。
-
loadTemplate() 函数遍历由 go-assets-builder 嵌入的文件,读取文件内容并解析为模板,并将其命名为文件名,形成一个模板链。最终返回加载好的模板和可能的错误。
总的来说,这段代码通过 Gin 框架创建了一个简单的 HTTP 服务器,加载了由 go-assets-builder 嵌入的模板文件,用于渲染 HTML 响应。这种方式可以将模板文件嵌入到 Go 语言程序中,方便部署和分发,同时避免了外部文件的依赖和管理。
2.8. HTTP2 server 推送
2.8.1. 完整代码
package main
import (
"html/template"
"log"
"github.com/gin-gonic/gin"
)
// 定义一个 HTML 模板
var html = template.Must(template.New("https").Parse(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script>
</head>
<body>
<h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))
func main() {
r := gin.Default() // 创建一个默认的 Gin 路由引擎实例
r.Static("/assets", "./assets") // 将静态文件夹 "./assets" 注册为 "/assets" 的静态资源路径
r.SetHTMLTemplate(html) // 设置 HTML 模板
r.GET("/", func(c *gin.Context) { // 定义一个 HTTP GET 方法的路由 "/"
if pusher := c.Writer.Pusher(); pusher != nil { // 判断是否支持服务器推送
// 使用 pusher.Push() 做服务器推送
if err := pusher.Push("/assets/app.js", nil); err != nil {
log.Printf("Failed to push: %v", err) // 如果推送失败,则记录日志
}
}
c.HTML(200, "https", gin.H{ // 返回 HTML 模板,并设置状态码和数据
"status": "success",
})
})
// 监听并在 https://127.0.0.1:8080 上启动服务,使用 TLS 加密
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}
2.8.2. 知识点
-
var html = template.Must(template.New("https").Parse(...)): 定义了一个名为 html 的 HTML 模板。template.Must 是一个辅助函数,用于将模板解析为 template.Template 对象,并在解析过程中检查是否出错。
-
r.Static("/assets", "./assets"): 将静态文件夹 "./assets" 注册为 "/assets" 的静态资源路径,这样当客户端请求 "/assets/app.js" 时,会返回位于本地文件系统的 "./assets/app.js" 文件作为响应。
-
r.SetHTMLTemplate(html): 设置之前定义的 html 模板为 Gin 框架的 HTML 模板。
-
r.GET("/", func(c *gin.Context) { ... }): 定义一个 HTTP GET 方法的路由 "/",并传入一个请求处理函数,该函数会在收到请求时执行。在这个例子中,请求处理函数中通过 c.Writer.Pusher() 检查是否支持服务器推送。如果支持,通过 pusher.Push("/assets/app.js", nil) 进行服务器推送,推送的资源路径为 "/assets/app.js"。然后使用 c.HTML() 返回之前定义的 HTML 模板,并设置状态码为 200 和数据为 gin.H{"status": "success"}。
-
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key"): 启动 HTTP 服务器,并监听在 8080 端口,并使用 TLS 加密进行安全通信。第二个参数 "./testdata/server.pem" 是 TLS 证书文件的路径,第三个参数 "./testdata/server.key" 是 TLS 私钥文件的路径,用于在服务器端进行 TLS 握手和加密通信。这样,服务器将通过 HTTPS 监听客户端的请求。
2.8.3. 示例概述
-
定义 HTML 模板:使用 template.Must 函数创建了一个名为 "https" 的 HTML 模板,用于返回给客户端。
-
创建 Gin 路由引擎实例:使用 gin.Default() 函数创建了一个默认的 Gin 路由引擎实例。
-
注册静态文件路径:使用 r.Static() 方法将静态文件夹 "./assets" 注册为 "/assets" 的静态资源路径,使得客户端可以通过访问 "/assets" 路径来获取静态文件。
-
设置 HTML 模板:使用 r.SetHTMLTemplate() 方法设置了之前定义的 HTML 模板。
-
定义根路由处理函数:使用 r.GET() 方法定义了一个 HTTP GET 方法的根路由处理函数,该处理函数会返回之前定义的 HTML 模板,并在响应头中设置了支持服务器推送的信息。
-
启动 HTTPS 服务器:使用 r.RunTLS() 方法在 "https://127.0.0.1:8080" 地址上启动了一个监听 TLS 加密的 HTTPS 服务器,使用了之前定义的 TLS 证书和私钥文件。
3. server运行
3.1. 优雅地重启或停止
3.1.1. 完整代码
// +build go1.8
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个默认的 Gin 路由实例
router := gin.Default()
// 注册一个处理函数到 "/" 路径的 GET 请求
router.GET("/", func(c *gin.Context) {
// 模拟一个长任务,暂停 5 秒
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
// 创建一个 http.Server 实例,配置监听地址和 Gin 路由作为处理器
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// 启动一个 goroutine 监听和处理 HTTP 请求
go func() {
// 启动 HTTP 服务器,监听和处理请求
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
// 创建一个超时上下文,设置 5 秒超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 关闭服务器,等待未完成的请求完成或超时
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
3.1.2. 知识点
-
srv := &http.Server{ Addr: ":8080", Handler: router, }创建了一个 http.Server 实例,用于配置 HTTP 服务器的监听地址和请求处理器.
-
Addr: ":8080":指定了服务器监听的地址和端口号。在这里,服务器会监听在本地的 8080 端口上,表示可以通过访问 http://localhost:8080 来与服务器进行通信。
-
Handler: router:指定了服务器的请求处理器。在这里,使用了 Gin 框架创建的路由实例 router 作为服务器的请求处理器。Gin 路由可以用于注册不同的 HTTP 请求处理函数,根据请求的 URL 路径和 HTTP 方法来进行路由和处理。
-
-
srv.ListenAndServe():这是 http.Server 的方法,用于启动服务器并开始监听和处理请求。它会一直在后台运行,直到服务器关闭或发生错误。
-
err := srv.ListenAndServe():将 srv.ListenAndServe() 的返回值赋给变量 err,用于捕获可能发生的错误。
-
err != nil && err != http.ErrServerClosed:通过条件判断语句检查 err 是否不为空且不等于 http.ErrServerClosed,即判断是否有错误发生且错误不是因为服务器被关闭导致的。
-
log.Fatalf("listen: %s\n", err):如果有错误发生,使用 log.Fatalf() 方法将错误信息格式化并输出到标准错误(stderr),然后调用 log.Fatal() 方法终止程序的运行。log.Fatalf() 是 log 包的一个函数,它会在输出错误信息后直接调用 os.Exit(1) 终止程序。
-
quit := make(chan os.Signal):创建了一个用于接收信号的通道 quit,该通道的类型是 os.Signal,用于接收操作系统发送的信号。
-
signal.Notify(quit, os.Interrupt):通过 signal 包的 Notify 函数,将 os.Interrupt(表示中断信号,通常由用户在终端中按下 Ctrl+C 产生)注册到 quit 通道,表示当收到中断信号时,将向 quit 通道发送该信号。
-
<-quit:通过从 quit 通道中接收信号,代码会一直阻塞在这里,直到接收到中断信号。一旦收到中断信号,代码会继续执行下一行。
-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second):通过 context.WithTimeout 函数创建了一个带有超时的上下文 ctx,超时时间设置为 5 秒。context.Background() 返回一个空的上下文作为父上下文。
-
defer cancel():使用 defer 语句延迟调用 cancel() 函数,确保在函数执行完毕后取消上下文,以避免资源泄漏。
-
if err := srv.Shutdown(ctx); err != nil:调用 srv.Shutdown 方法来优雅地关闭服务器,传入之前创建的带有超时的上下文 ctx。该方法会等待服务器处理完当前的请求后再关闭服务器,以实现优雅关闭。
-
log.Fatal("Server Shutdown:", err):如果服务器关闭过程中发生错误,使用 log.Fatal 输出错误信息并终止程序运行。
3.1.3. 示例概述
-
首先,创建了一个默认的 Gin 路由实例,注册了一个处理函数到 "/" 路径的 GET 请求,其中模拟了一个长任务,暂停 5 秒,然后返回 "Welcome Gin Server" 字符串作为响应。
-
接着,创建了一个 http.Server 实例,配置了监听地址为 ":8080",并将 Gin 路由作为处理器。
-
然后,通过启动一个 goroutine 来监听和处理 HTTP 请求,调用 srv.ListenAndServe() 方法来启动 HTTP 服务器。
-
接下来,通过使用 os/signal 包来等待中断信号(如 Ctrl+C),一旦接收到中断信号,服务器将开始优雅地关闭。
-
创建了一个超时上下文 ctx,设置了 5 秒的超时时间,并通过 srv.Shutdown() 方法来优雅地关闭服务器,等待未完成的请求完成或超时。
-
最后,使用 log.Fatal() 输出错误信息并终止程序运行,或者输出 "Server exiting" 消息表示服务器成功退出。
3.2. 路由组
3.2.1. 完整代码
func main() {
router := gin.Default() // 创建一个默认的 Gin 路由引擎实例
// 简单的路由组: v1
v1 := router.Group("/v1") // 创建一个名为 "/v1" 的路由组
{
v1.POST("/login", loginEndpoint) // 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/login",并将该路由与名为 loginEndpoint 的处理函数绑定
v1.POST("/submit", submitEndpoint) // 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/submit",并将该路由与名为 submitEndpoint 的处理函数绑定
v1.POST("/read", readEndpoint) // 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/read",并将该路由与名为 readEndpoint 的处理函数绑定
}
// 简单的路由组: v2
v2 := router.Group("/v2") // 创建一个名为 "/v2" 的路由组
{
v2.POST("/login", loginEndpoint) // 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/login",并将该路由与名为 loginEndpoint 的处理函数绑定
v2.POST("/submit", submitEndpoint) // 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/submit",并将该路由与名为 submitEndpoint 的处理函数绑定
v2.POST("/read", readEndpoint) // 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/read",并将该路由与名为 readEndpoint 的处理函数绑定
}
router.Run(":8080") // 启动 HTTP 服务器并监听在端口 8080
}
3.2.2. 知识点
router.Group("/v1"){...}: 这是创建了一个名为 "/v1" 的路由组。路由组可以用来对一组路由进行分组,方便管理和配置共享的中间件。
3.2.3. 示例概述
-
func main(): 这是 Go 语言的入口函数,是程序的入口点。
-
router := gin.Default(): 创建了一个默认的 Gin 路由引擎实例。gin.Default() 函数返回一个默认配置的 Gin 路由引擎,包含了常用的中间件,如日志、恢复等。
-
v1 := router.Group("/v1"): 创建了一个名为 "/v1" 的路由组。路由组用于将多个路由进行分组管理,可以对这些路由应用相同的中间件、路由组级别的处理函数等。
-
v1.POST("/login", loginEndpoint): 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/login",并将该路由与名为 loginEndpoint 的处理函数绑定。这表示当客户端发送 HTTP POST 请求到 "/v1/login" 时,将调用 loginEndpoint 函数来处理该请求。
-
v1.POST("/submit", submitEndpoint): 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/submit",并将该路由与名为 submitEndpoint 的处理函数绑定。这表示当客户端发送 HTTP POST 请求到 "/v1/submit" 时,将调用 submitEndpoint 函数来处理该请求。
-
v1.POST("/read", readEndpoint): 在 "/v1" 路由组下定义了一个 HTTP POST 方法的路由 "/read",并将该路由与名为 readEndpoint 的处理函数绑定。这表示当客户端发送 HTTP POST 请求到 "/v1/read" 时,将调用 readEndpoint 函数来处理该请求。
-
v2 := router.Group("/v2"): 创建了一个名为 "/v2" 的路由组,类似于上面的 "/v1" 路由组。
-
v2.POST("/login", loginEndpoint): 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/login",并将该路由与名为 loginEndpoint 的处理函数绑定。
-
v2.POST("/submit", submitEndpoint): 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/submit",并将该路由与名为 submitEndpoint 的处理函数绑定。
-
v2.POST("/read", readEndpoint): 在 "/v2" 路由组下定义了一个 HTTP POST 方法的路由 "/read",并将该路由与名为 readEndpoint 的处理函数绑定。
-
router.Run(":8080"): 启动 HTTP 服务器并监听在端口 8080。一旦服务器启动,就会开始接受来自客户端的请求,并根据定义的路由规则和处理函数来处理这些请求。
3.3. 支持 Let’s Encrypt
3.3.1. 完整代码
一行代码支持 LetsEncrypt HTTPS servers 示例。
package main
import (
"log"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // 创建一个 Gin 路由引擎实例
// 注册 "/ping" 路由的 GET 方法处理函数
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
// 使用 Autotls 启动 HTTP 服务器并监听域名 "example1.com" 和 "example2.com"
log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}
自定义 autocert manager 示例。
package main
import (
"log"
"github.com/gin-gonic/autotls"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/acme/autocert"
)
func main() {
r := gin.Default() // 创建一个 Gin 路由引擎实例
// 注册 "/ping" 路由的 GET 方法处理函数
r.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
// 创建一个 Autocert 管理器,用于配置 Let's Encrypt 自动证书管理
m := autocert.Manager{
Prompt: autocert.AcceptTOS, // 接受服务条款
HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), // 允许的域名白名单
Cache: autocert.DirCache("/var/www/.cache"), // 证书缓存目录
}
// 使用 Autotls 启动 HTTP 服务器并监听域名 "example1.com" 和 "example2.com",并配置自动证书管理器
log.Fatal(autotls.RunWithManager(r, &m))
}
3.3.2. 代码解释
以上代码使用 Gin 框架创建了一个简单的 HTTP 服务器,并注册了一个 "/ping" 路由的 GET 方法处理函数,用于处理 "/ping" 路径的请求,并返回 "pong" 字符串。然后,通过调用 autotls.Run() 方法使用 Autotls 启动 HTTP 服务器并监听域名 "example1.com" 和 "example2.com",这将自动为这两个域名配置 HTTPS,并使用 Let's Encrypt 证书进行 TLS 加密。如果启动服务器出现错误,会通过 log.Fatal() 方法记录并输出错误信息,并终止程序的运行。
以上代码使用 Gin 框架创建了一个简单的 HTTP 服务器,并注册了一个 "/ping" 路由的 GET 方法处理函数,用于处理 "/ping" 路径的请求,并返回 "pong" 字符串。然后,通过创建一个 Autocert 管理器来配置 Let's Encrypt 自动证书管理,包括接受服务条款、允许的域名白名单和证书缓存目录等参数。最后,通过调用 autotls.RunWithManager() 方法使用 Autotls 启动 HTTP 服务器并监听域名 "example1.com" 和 "example2.com",并配置自动证书管理器。如果启动服务器出现错误,会通过 log.Fatal() 方法记录并输出错误信息,并终止程序的运行。
3.3.3. 完整代码
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// 通过 c.ShouldBindBodyWith() 方法将请求体绑定到 formA 对象,并指定绑定的格式为 JSON
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 如果绑定失败,则尝试将存储在上下文中的请求体绑定到 formB 对象,并指定绑定的格式为 JSON
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// 如果 JSON 格式绑定失败,则尝试将存储在上下文中的请求体绑定到 formB 对象,并指定绑定的格式为 XML
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
c.String(http.StatusOK, `the body should be formB XML`)
// 如果两次绑定都失败,则执行其他逻辑
} else {
...
}
}
3.3.4. 代码解释
以上代码是一个使用 Gin 框架的 HTTP 请求处理函数 SomeHandler,它通过三次调用 c.ShouldBindBodyWith() 方法尝试将存储在上下文中的请求体绑定到两个不同的结构体对象 formA 和 formB,并可以指定绑定的格式为 JSON 或 XML。首先尝试将请求体绑定到 formA 对象,并指定绑定的格式为 JSON,如果绑定成功,则返回一个字符串 "the body should be formA"。如果绑定失败,则尝试将存储在上下文中的请求体绑定到 formB 对象,并指定绑定的格式为 JSON,如果绑定成功,则返回一个字符串 "the body should be formB JSON"。如果 JSON 格式绑定失败,则尝试将存储在上下文中的请求体绑定到 formB 对象,并指定绑定的格式为 XML,如果绑定成功,则返回一个字符串 "the body should be formB XML"。如果三次绑定都失败,则执行其他逻辑。需要注意的是,c.ShouldBindBodyWith() 方法可以用于复用存储在上下文中的请求体,并且可以指定不同的绑定格式。
3.4. 使用 HTTP 方法
3.4.1. 完整代码
func main() {
// 禁用控制台颜色
// gin.DisableConsoleColor()
// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
router := gin.Default()
// 注册不同的 HTTP 方法对应的处理函数
router.GET("/someGet", getting) // 处理 HTTP GET 请求
router.POST("/somePost", posting) // 处理 HTTP POST 请求
router.PUT("/somePut", putting) // 处理 HTTP PUT 请求
router.DELETE("/someDelete", deleting) // 处理 HTTP DELETE 请求
router.PATCH("/somePatch", patching) // 处理 HTTP PATCH 请求
router.HEAD("/someHead", head) // 处理 HTTP HEAD 请求
router.OPTIONS("/someOptions", options) // 处理 HTTP OPTIONS 请求
// 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。
router.Run()
// router.Run(":3000") 可以硬编码指定端口号
}
3.4.2. 代码解释
以上代码使用 Gin 框架创建了一个 HTTP 服务器,并注册了不同的 HTTP 方法对应的处理函数。每个处理函数在接收到对应的 HTTP 请求时会被调用,从而进行相应的处理逻辑。router.Run() 函数会在默认端口 8080 上启动 HTTP 服务,可以通过设置环境变量 PORT 来修改端口号。如果需要固定端口号,可以通过 router.Run(":3000") 来指定特定的端口号
3.5. 重定向
3.5.1. 完整代码
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(200, gin.H{"hello": "world"})
})
3.5.2. 代码解释
当访问 /test 时,请求的 URL 路径被修改为 /test2,然后再次处理请求。由于 /test2 路径有对应的路由处理函数,所以会返回 {"hello": "world"} JSON 响应。
3.6. 运行多个服务
3.6.1. 完整代码
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
var (
g errgroup.Group // 全局 errgroup.Group 对象,用于管理多个 Goroutine
)
// 创建并返回一个 Gin 实例,作为 server01 的路由处理器
func router01() http.Handler {
e := gin.New() // 创建一个新的 Gin 实例
e.Use(gin.Recovery()) // 添加 Recovery 中间件,用于处理请求中的错误
e.GET("/", func(c *gin.Context) { // 定义根路径的 GET 请求处理函数
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 01",
},
)
})
return e
}
// 创建并返回一个 Gin 实例,作为 server02 的路由处理器
func router02() http.Handler {
e := gin.New() // 创建一个新的 Gin 实例
e.Use(gin.Recovery()) // 添加 Recovery 中间件,用于处理请求中的错误
e.GET("/", func(c *gin.Context) { // 定义根路径的 GET 请求处理函数
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"error": "Welcome server 02",
},
)
})
return e
}
func main() {
server01 := &http.Server{
Addr: ":8080", // server01 监听的地址和端口
Handler: router01(), // server01 使用 router01 作为路由处理器
ReadTimeout: 5 * time.Second, // server01 读取超时时间
WriteTimeout: 10 * time.Second, // server01 写入超时时间
}
server02 := &http.Server{
Addr: ":8081", // server02 监听的地址和端口
Handler: router02(), // server02 使用 router02 作为路由处理器
ReadTimeout: 5 * time.Second, // server02 读取超时时间
WriteTimeout: 10 * time.Second, // server02 写入超时时间
}
// 使用 errgroup 管理多个 Goroutine,分别启动 server01 和 server02
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil { // 等待两个 Goroutine 结束
log.Fatal(err) // 如果有错误发生,则输出错误日志
}
}
3.6.2. 代码解释
这段代码使用 Gin 框架创建了两个独立的 HTTP 服务器,分别监听不同的端口。router01 和 router02 函数分别定义了两个 Gin 路由处理器,分别处理根路径的 GET 请求,并返回不同的 JSON 响应。
通过 errgroup 包管理两个 Goroutine,分别启动了 server01 和 server02 的监听操作。每个服务器都使用了不同的监听地址和端口,以及不同的路由处理器。
如果有错误发生,errgroup.Wait() 方法会等待两个 Goroutine 结束,并返回错误。如果返回的错误不为空,则使用 log.Fatal() 输出错误日志并终止程序运行。
这段代码的主要作用是创建并同时启动两个独立的 HTTP 服务器,它们分别监听不同的端口,并使用 Gin 框架处理路由请求。使用 errgroup 包可以方便地管理多个 Goroutine,并在任何一个 Goroutine 发生错误时及时捕获和处理。这种方式可以有效地提高服务器的可靠性和稳定性。
3.7. 设置和获取 Cookie
3.7.1. 完整代码
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default() // 创建一个 Gin 路由引擎实例
router.GET("/cookie", func(c *gin.Context) { // 注册 "/cookie" 路由的 GET 方法处理函数
cookie, err := c.Cookie("gin_cookie") // 获取名为 "gin_cookie" 的 Cookie
if err != nil { // 如果获取 Cookie 发生错误,则设置一个默认值
cookie = "NotSet"
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true) // 设置名为 "gin_cookie" 的 Cookie 值为 "test",过期时间为 3600 秒,路径为 "/",域名为 "localhost",不限制 HTTPS 连接,允许 JS 访问
}
fmt.Printf("Cookie value: %s \n", cookie) // 在控制台打印 Cookie 的值
})
router.Run() // 启动 HTTP 服务器,开始接收并处理客户端的请求
}
3.7.2. 代码解释
以上代码使用 Gin 框架创建了一个简单的 HTTP 服务器,并注册了一个 "/cookie" 路由的 GET 方法处理函数。在处理函数中,通过 c.Cookie("gin_cookie") 获取名为 "gin_cookie" 的 Cookie 值,并在控制台打印出来。如果获取 Cookie 发生错误(例如不存在),则设置一个默认值,并通过 c.SetCookie() 方法设置名为 "gin_cookie" 的 Cookie 值为 "test",过期时间为 3600 秒,路径为 "/",域名为 "localhost",不限制 HTTPS 连接,允许 JS 访问。最后,通过调用 router.Run() 启动 HTTP 服务器,监听默认的地址和端口(0.0.0.0:8080),并开始接收并处理客户端的请求。
4. server中间件
4.1. 自定义中间件
4.1.1. 完整代码
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now() // 记录当前时间
c.Set("example", "12345") // 设置一个名为 "example" 的自定义变量到 Gin 上下文中
// 请求前的处理逻辑
c.Next() // 执行下一个处理函数
// 请求后的处理逻辑
latency := time.Since(t) // 计算请求耗时
log.Print(latency) // 打印请求耗时
status := c.Writer.Status() // 获取响应状态码
log.Println(status) // 打印响应状态码
}
}
func main() {
r := gin.New() // 创建一个新的 Gin 路由引擎实例
r.Use(Logger()) // 使用自定义的 Logger 中间件
r.GET("/test", func(c *gin.Context) {
example := c.MustGet("example").(string) // 从 Gin 上下文中获取之前设置的 "example" 变量
// 打印:"12345"
log.Println(example)
})
r.Run(":8080") // 启动 HTTP 服务器,监听在指定的地址和端口,并使用创建的路由引擎处理请求
}
4.1.2. 知识点
-
time.Now(): 这是 Go 语言中的时间包 time 中的函数,用于获取当前时间。在这段代码中,用于记录请求处理开始的时间 t,以便后续计算请求耗时。
-
c.Set(key string, value interface{}): 这是 Gin 框架中 Context 对象的方法,用于在 Gin 上下文中设置一个自定义的键值对。在这段代码中,使用该方法将一个名为 "example" 的自定义变量设置到 Gin 上下文中,值为字符串 "12345"。
-
c.Next(): 这是 Gin 框架中 Context 对象的方法,用于执行下一个处理函数。在这段代码中,用于将请求传递给下一个处理函数,通常在中间件中使用,确保请求能够继续向后处理。
-
time.Since(t): 这是 Go 语言中的时间包 time 中的函数,用于计算当前时间与给定时间 t 的时间差。在这段代码中,用于计算请求耗时,即当前时间与请求处理开始时间 t 的时间差。
-
log.Print(v ...interface{}): 这是 Go 语言中的日志包 log 中的函数,用于将日志消息打印到标准输出。在这段代码中,用于打印请求耗时 latency和用于打印响应状态码。
-
c.Writer.Status(): 这是 Gin 框架中 Context 对象的方法,用于获取响应状态码。在这段代码中,用于获取响应状态码,以便后续打印到日志中。
4.1.3. 示例执行过程
-
在 main() 函数中,首先创建了一个新的 Gin 路由引擎实例 r。
-
使用 r.Use(Logger()) 将自定义的 Logger 中间件注册到路由引擎 r 中,以便在处理请求时自动调用。
-
定义了一个处理 HTTP GET 请求的路由处理函数,路径为 "/test"。在这个处理函数中,使用 c.MustGet("example") 从 Gin 上下文中获取之前在 Logger 中间件中设置的名为 "example" 的自定义变量,并将其转换为字符串类型。
-
在路由处理函数中,使用日志包 log 将获取到的 "example" 变量打印到日志中。
-
使用 r.Run(":8080") 启动 HTTP 服务器,监听在地址 ":8080" 并使用创建的路由引擎 r 处理请求。
-
当有请求到达时,Gin 框架会依次执行注册到路由引擎的中间件和路由处理函数。
-
Logger() 中间件会在请求前记录当前时间,并设置一个名为 "example" 的自定义变量到 Gin 上下文中。然后,它会调用 c.Next() 执行下一个处理函数,即路由处理函数。
-
路由处理函数会从 Gin 上下文中获取 "example" 变量,并将其打印到日志中。
-
当路由处理函数执行完毕后,控制权会返回到 Logger() 中间件,它会计算请求耗时,并将请求耗时和响应状态码打印到日志中。
-
最后,HTTP 服务器会根据定义的路由引擎 r 处理请求,并在指定的地址和端口上监听。
4.2. 中间件中使用 Goroutine
4.2.1. 完整代码
func main() {
r := gin.Default()
// 定义 /long_async 路由
r.GET("/long_async", func(c *gin.Context) {
// 创建在 goroutine 中使用的副本
cCp := c.Copy()
go func() {
// 用 time.Sleep() 模拟一个长任务。
time.Sleep(5 * time.Second)
// 请注意您使用的是复制的上下文 "cCp",这一点很重要
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})
// 定义 /long_sync 路由
r.GET("/long_sync", func(c *gin.Context) {
// 用 time.Sleep() 模拟一个长任务。
time.Sleep(5 * time.Second)
// 因为没有使用 goroutine,不需要拷贝上下文
log.Println("Done! in path " + c.Request.URL.Path)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
4.2.2. 知识点
-
c.Copy():创建一个上下文的副本,用于在 goroutine 中使用。在多个 goroutine 中处理请求时,需要使用上下文的副本,以避免竞态条件和并发访问的问题。
-
go func() { ... }():这段代码是一个匿名的 Go 语言协程(goroutine),通过 go 关键字开启一个新的并发执行的函数。这个函数中的逻辑很简单,首先通过 time.Sleep(5 * time.Second) 模拟一个耗时的操作,即暂停当前 goroutine 的执行 5 秒钟。在这个暂停期间,当前的 goroutine 不会继续执行,而是让出 CPU 时间给其他 goroutine,从而实现了并发执行。当 time.Sleep() 结束后,该 goroutine 会继续执行剩下的代码。这段代码通过调用 log.Println() 在日志中打印一条消息,内容为 "Done! int path " + cCp.Request.URL.Path。其中,cCp 是在创建这个 goroutine 时通过 c.Copy() 创建的当前请求上下文的副本,用于记录请求的路径。
-
time.Sleep():模拟一个长时间运行的任务,暂停当前 goroutine 的执行一段时间,以模拟耗时的操作。在这段代码中,使用 time.Sleep(5 * time.Second) 暂停当前 goroutine 的执行 5 秒。
-
c.Request.URL.Path:获取当前请求的路径,用于在日志中记录处理请求的路径。
-
通过分别访问long_async目录和long_sync目录,会发现:前者每次刷新页面,后台就会立即打印日志,过五秒后才会打印输出路径;反观后者,每次刷新页面需要过五秒,后台才会同时输出日志和打印路径。long_async当前的 goroutine 不会继续执行,而是让出 CPU 时间给其他 goroutine,从而实现了并发执行。
4.2.3. 示例概述
-
使用 gin.Default() 创建了一个默认的 Gin 引擎 r,用于处理 HTTP 请求和响应。
-
定义了两个路由:
-
/long_async 路由,使用 r.GET 方法注册了一个处理 GET 请求的处理函数。在这个处理函数中,通过 c.Copy() 创建了一个上下文副本 cCp,然后使用 go 关键字启动了一个 goroutine,在 goroutine 中使用 time.Sleep() 模拟了一个长时间运行的任务,最后在日志中打印了请求路径信息。
-
/long_sync 路由,同样使用 r.GET 方法注册了一个处理 GET 请求的处理函数。在这个处理函数中,也使用了 time.Sleep() 模拟了一个长时间运行的任务,但没有使用 goroutine,因此不需要拷贝上下文,直接通过 c 访问了请求路径信息,并在日志中打印了请求路径信息。
-
-
使用 r.Run(":8080") 启动了一个 web 服务器,监听在本地的 8080 端口上,等待接收来自客户端的请求。
4.3. 使用 BasicAuth 中间件
4.3.1. 完整代码
// 模拟一些私人数据
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
func main() {
r := gin.Default()
// 路由组使用 gin.BasicAuth() 中间件
// gin.Accounts 是 map[string]string 的一种快捷方式
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// /admin/secrets 端点
// 触发 "localhost:8080/admin/secrets
authorized.GET("/secrets", func(c *gin.Context) {
// 获取用户,它是由 BasicAuth 中间件设置的
// 使用 c.MustGet() 方法获取经过 BasicAuth 中间件处理后的用户信息
user := c.MustGet(gin.AuthUserKey).(string)
if secret, ok := secrets[user]; ok {
// 根据用户信息获取对应的私人数据
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
// 如果用户信息不存在于私人数据中,返回默认的信息
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
4.3.2. 代码解释
-
定义了一个 secrets 变量,模拟了一些私人数据,使用了 gin.H 来表示 key-value 结构的 map。
-
创建了一个默认的 Gin 路由引擎 r。
-
使用 r.Group() 方法创建了一个路由组 /admin,并在该路由组上应用了 gin.BasicAuth() 中间件。gin.BasicAuth() 中间件用于进行 HTTP 基本身份验证,需要传入一个 gin.Accounts 类型的参数,其中包含了允许访问的用户名和密码的键值对。
-
在路由组中定义了一个 GET 请求的处理函数,处理 /admin/secrets 路径的请求。在该处理函数中,使用 c.MustGet() 方法获取经过 gin.BasicAuth() 中间件处理后的用户信息,其中 gin.AuthUserKey 是 Gin 框架中用于存储经过认证的用户信息的上下文键名。
-
根据用户信息从 secrets 变量中获取对应的私人数据,如果存在则返回用户信息和私人数据,否则返回默认的信息。
-
使用 r.Run() 方法启动 HTTP 服务器,监听在 0.0.0.0:8080 地址上。
4.4. 使用中间件
4.4.1. 完整代码
func main() {
// 新建一个没有任何默认中间件的路由
r := gin.New()
// 全局中间件
// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
// 默认情况下 gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
r.Use(gin.Recovery())
// 你可以为每个路由添加任意数量的中间件。
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// 认证路由组
// authorized := r.Group("/", AuthRequired())
// 和使用以下两行代码的效果完全一样:
authorized := r.Group("/")
// 路由组中间件! 在此例中,我们在 "authorized" 路由组中使用自定义创建的
// AuthRequired() 中间件
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
// 嵌套路由组
testing := authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
4.4.2. 代码解释
-
创建 Gin 路由:代码通过 gin.New() 函数创建了一个新的 Gin 路由实例,并赋值给变量 r。这个路由实例没有任何默认的中间件。
-
全局中间件:代码通过 r.Use() 函数注册了两个全局中间件,分别是 gin.Logger() 和 gin.Recovery()。gin.Logger() 用于记录日志,gin.Recovery() 用于恢复从处理函数中的 panic。
-
路由和处理函数:代码通过 r.GET()、r.POST() 等方法注册了多个路由,并指定了相应的处理函数。例如,r.GET("/benchmark", MyBenchLogger(), benchEndpoint) 注册了一个 GET 请求的路由 "/benchmark",并使用了自定义的中间件 MyBenchLogger() 和处理函数 benchEndpoint()。
-
路由组和嵌套路由组:代码通过 r.Group() 方法创建了一个名为 "authorized" 的路由组,并在其中注册了多个路由和处理函数。这些路由和处理函数都位于 "authorized" 路由组下,并且共享路由组中注册的中间件 AuthRequired(),用于认证授权。另外,代码还演示了如何在路由组内创建嵌套的路由组,例如 testing := authorized.Group("testing")。
-
启动 HTTP 服务:代码通过 r.Run(":8080") 启动了一个 HTTP 服务,并监听在 0.0.0.0:8080 上。这将使服务在这个地址上接受来自客户端的请求。
4.5. 不使用默认的中间件
4.5.1. 完整代码
r := gin.New()
代替
// Default 使用 Logger 和 Recovery 中间件
r := gin.Default()
4.5.2. 代码解释
-
导入了 Gin 框架和 net/http 包,以及一个自定义的 Protocol Buffers 文件包 protoexample。
-
创建了一个 Gin 的默认引擎实例 r,用于处理 HTTP 请求。
-
定义了路由处理函数,通过 r.GET() 方法将路由路径和处理函数绑定起来。
-
第一个路由处理函数处理 "/someJSON" 路径的 GET 请求,返回一个 JSON 格式的响应,其中包含一个 "message" 字段和一个 "status" 字段,值分别为 "hey" 和 http.StatusOK。
-
第二个路由处理函数处理 "/moreJSON" 路径的 GET 请求,返回一个 JSON 格式的响应,其中包含一个结构体 msg,结构体有三个字段,分别为 "Name"、"Message" 和 "Number",通过结构体字面量赋值后作为响应的内容。
-
第三个路由处理函数处理 "/someXML" 路径的 GET 请求,返回一个 XML 格式的响应,其中包含一个 "message" 字段和一个 "status" 字段,值分别为 "hey" 和 http.StatusOK。
-
第四个路由处理函数处理 "/someYAML" 路径的 GET 请求,返回一个 YAML 格式的响应,其中包含一个 "message" 字段和一个 "status" 字段,值分别为 "hey" 和 http.StatusOK。
-
第五个路由处理函数处理 "/someProtoBuf" 路径的 GET 请求,返回一个 Protocol Buffers 格式的响应,其中包含一个自定义的 Test 结构体,结构体有两个字段,分别为 "Label" 和 "Reps",通过结构体字面量赋值后作为响应的内容。
-
最后通过 r.Run() 方法启动 HTTP 服务器,在地址 0.0.0.0:8080 上监听请求。
5. server配置
5.1. 自定义HTTP配置
5.1.1. 完整代码
直接使用
func main() {
router := gin.Default() // 创建一个 Gin 路由引擎实例,使用默认的中间件:日志和恢复中间件
http.ListenAndServe(":8080", router) // 启动 HTTP 服务器,监听在端口 8080 上,使用创建的路由引擎处理请求
}
或
func main() {
router := gin.Default() // 创建一个 Gin 路由引擎实例,使用默认的中间件:日志和恢复中间件
// 创建一个自定义的 http.Server 实例
s := &http.Server{
Addr: ":8080", // 监听的地址和端口
Handler: router, // 使用创建的路由引擎处理请求
ReadTimeout: 10 * time.Second, // 读取请求超时时间
WriteTimeout: 10 * time.Second, // 写入响应超时时间
MaxHeaderBytes: 1 << 20, // 最大请求头大小
}
s.ListenAndServe() // 启动 HTTP 服务器,监听在指定的地址和端口,并使用创建的路由引擎处理请求
}
5.1.2. 知识点
-
http.ListenAndServe(":8080", router): 这是 Go 标准库 net/http 提供的一个方法,用于启动一个 HTTP 服务器并监听指定的地址和端口,并使用传入的路由引擎处理请求。在这里,我们指定监听地址为空字符串,表示监听所有可用的网卡,端口为 8080。router 是之前创建的 Gin 路由引擎实例,用于处理请求。
-
s := &http.Server{...}这段代码创建了一个 http.Server 实例,并设置了以下配置项:
- Addr:指定服务器监听的地址和端口,这里设置为 :8080,表示监听在本地的 8080 端口。
- Handler:设置处理请求的路由引擎,这里使用了之前创建的 router,即 Gin 路由引擎。
- ReadTimeout:设置读取请求的超时时间,这里设置为 10 秒,表示如果在 10 秒内没有读取到完整的请求,服务器会关闭连接。
- WriteTimeout:设置写入响应的超时时间,这里设置为 10 秒,表示如果在 10 秒内没有完成响应的写入操作,服务器会关闭连接。
- MaxHeaderBytes:设置最大请求头的大小,这里设置为 1 << 20,即 1MB,表示请求头的大小不超过 1MB。
-
这些配置项可以根据具体的需求进行调整,用于自定义 HTTP 服务器的行为,例如设置超时时间、限制请求头大小等。在创建完 http.Server 实例后,可以通过调用 ListenAndServe() 方法来启动服务器,开始监听指定的地址和端口,并使用设置的路由引擎来处理请求。
-
s.ListenAndServe(): 这是 http.Server 的一个方法,用于启动 HTTP 服务器,并监听在指定的地址和端口,使用创建的路由引擎处理请求。在这里,s 是之前创建的自定义 http.Server 实例,通过调用 ListenAndServe() 方法来启动服务器。
5.1.3. 示例概述
-
创建一个默认的 Gin 路由引擎实例,使用了默认的中间件包括日志和恢复中间件。
-
创建一个自定义的 http.Server 实例,设置了监听的地址和端口为 :8080,使用之前创建的路由引擎 router 来处理请求,设置了读取请求的超时时间为 10 秒,写入响应的超时时间为 10 秒,最大请求头大小为 1MB。
-
调用 ListenAndServe() 方法启动 HTTP 服务器,开始监听指定的地址和端口,并使用创建的路由引擎来处理请求。
-
这段代码的目的是创建一个自定义的 HTTP 服务器,使用 Gin 框架来处理请求,并设置了一些自定义的配置项,如超时时间和最大请求头大小。最后通过调用 ListenAndServe() 方法启动服务器,使其开始监听客户端请求。
5.2. 控制Log高亮输出
默认是会高亮(当然这基于你使用的 TTY)。
5.2.1. 完整代码
- 如果你不想使用日志高亮:
func main() {
// 关闭高亮
gin.DisableConsoleColor()
// 创建一个带有默认中间件(logger和recovery)的gin路由器
router := gin.Default()
// 处理GET请求,路径为"/ping"
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") // 向客户端返回字符串"pong",状态码为200
})
// 启动HTTP服务器,监听在端口8080上,等待客户端请求
router.Run(":8080")
}
- 手动设置高亮:
func main() {
// 强制开启日志高亮显示
gin.ForceConsoleColor()
// 创建一个带有默认中间件(logger和recovery)的gin路由器
router := gin.Default()
// 处理GET请求,路径为"/ping"
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong") // 向客户端返回字符串"pong",状态码为200
})
// 启动HTTP服务器,监听在端口8080上,等待客户端请求
router.Run(":8080")
}
5.2.2. 知识点
-
gin.ForceConsoleColor(): 这是 Gin 框架的一个方法,用于强制开启日志的高亮显示。它可以让 Gin 框架在控制台输出的日志信息更加醒目和易读。
-
gin.DisableConsoleColor(): 这是 Gin 框架的一个方法,用于关闭日志的高亮显示。当调用了这个方法后,Gin 框架在控制台输出的日志信息将不再具有高亮效果,变为普通的文本显示。
5.3. 自定义 Log 文件
5.3.1. 完整代码
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
func main() {
router := gin.New() // 创建一个新的 Gin 路由引擎实例,不使用默认的中间件
// 添加自定义的日志中间件,将日志格式化为自定义格式
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// 自定义日志格式
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP, //客户端 IP 地址
param.TimeStamp.Format(time.RFC1123), //时间戳
param.Method, //请求方法
param.Path, //请求路径
param.Request.Proto, //请求协议版本
param.StatusCode, //响应状态码
param.Latency, //请求耗时
param.Request.UserAgent(), //客户端 UserAgent 信息
param.ErrorMessage, // 错误信息(若有错误发生时)
)
}))
router.Use(gin.Recovery()) // 添加恢复中间件,用于恢复从 panic 中恢复
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080") // 启动 HTTP 服务器,监听在指定的地址和端口,并使用创建的路由引擎处理请求
}
5.3.2. 知识点
-
router.Use(): 此方法用于向 Gin 路由引擎添加中间件。中间件是处理 HTTP 请求的函数,在请求到达实际的路由处理器之前被执行。在这段代码中,使用 router.Use() 添加了两个中间件函数:
-
router.Use(): 此方法用于向 Gin 路由引擎添加中间件。中间件是处理 HTTP 请求的函数,在请求到达实际的路由处理器之前被执行。在这段代码中,使用 router.Use() 添加了两个中间件函数:
-
gin.LoggerWithFormatter(): 这个中间件函数用于以自定义格式记录 HTTP 请求日志。param 参数是 gin.LogFormatterParams 类型的实例,包含了有关 HTTP 请求和响应的信息。在给定的代码中,param 被用于格式化一个自定义的日志字符串,其中包括了客户端 IP 地址、时间戳、HTTP 方法、请求路径、协议版本、响应状态码、请求耗时、客户端 UserAgent 信息和错误消息(如果有的话)。
-
gin.Recovery(): 这个中间件函数用于从可能在处理 HTTP 请求时发生的 panic 中恢复。如果发生 panic,这个中间件会从中恢复,并向客户端返回一个 500 内部服务器错误的响应。
5.3.3. 示例概述
-
导入了 fmt、github.com/gin-gonic/gin 和 time 包。
-
在 main() 函数中,创建了一个新的 Gin 路由引擎实例 router,并使用 gin.New() 方法创建了一个不使用默认中间件的路由引擎。
-
使用 router.Use() 方法添加了两个自定义中间件:
- gin.LoggerWithFormatter() 中间件,用于将日志格式化为自定义格式。在回调函数中,通过 gin.LogFormatterParams 参数获取了请求和响应的信息,然后使用自定义的格式将这些信息组合成一个日志字符串。
- gin.Recovery() 中间件,用于从 panic 中恢复。这将确保在处理请求时发生 panic 时,服务器不会崩溃,而是能够从中恢复,并返回一个 500 内部服务器错误的响应。
-
使用 router.GET() 方法定义了一个处理 HTTP GET 请求的路由,路径为 "/ping"。在回调函数中,使用 c.String() 方法向客户端发送一个状态码为 200 的 "pong" 字符串响应。
-
最后,使用 router.Run() 方法启动了 HTTP 服务器,监听在地址 ":8080" 上,并使用创建的路由引擎处理传入的请求。
这段代码的功能是创建一个简单的 HTTP 服务器,监听在端口 8080,当访问 "/ping" 路径时,返回一个 "pong" 的响应。同时,还添加了自定义的日志中间件和恢复中间件,以增强服务器的健壮性和可靠性。
5.4. 定义路由日志的格式
5.4.1. 完整代码
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // 创建一个 Gin 实例作为路由引擎
// 设置 DebugPrintRouteFunc 函数,用于在控制台打印路由信息
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
}
// 定义 POST /foo 路由,处理函数返回 JSON 响应 "foo"
r.POST("/foo", func(c *gin.Context) {
c.JSON(http.StatusOK, "foo")
})
// 定义 GET /bar 路由,处理函数返回 JSON 响应 "bar"
r.GET("/bar", func(c *gin.Context) {
c.JSON(http.StatusOK, "bar")
})
// 定义 GET /status 路由,处理函数返回 JSON 响应 "ok"
r.GET("/status", func(c *gin.Context) {
c.JSON(http.StatusOK, "ok")
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run()
}
5.4.2. 知识点
-
gin.DebugPrintRouteFunc: 这是 Gin 框架中的一个变量,用于设置在控制台打印路由信息的函数。您在代码中通过给该变量赋值一个函数来自定义路由信息的打印方式。在您的示例中,设置了一个自定义的打印函数,用于在控制台输出 HTTP 方法、URL 路径、处理函数名和处理函数的数量。其中,httpMethod 是 HTTP 请求方法(如 "GET"、"POST" 等),absolutePath 是路由的完整路径,handlerName 是处理函数的名称,nuHandlers 是处理函数的数量,通过调用 log.Printf 函数将路由调试打印的信息输出到控制台。
-
r.GET("/bar", func(c *gin.Context) { ... }): 这是 Gin 框架中的一个方法,用于定义一个处理 HTTP GET 请求的路由。在这里,您定义了一个处理 /bar 路径的 GET 请求的路由,并传入一个匿名函数作为处理函数。可以在浏览器中输入网址localhost:8080/bar访问。
-
r.POST("/foo", func(c gin.Context) { ... }): 这是 Gin 框架中的一个方法,用于定义一个处理 HTTP POST 请求的路由。在这里,您定义了一个处理 /foo 路径的 POST 请求的路由,并传入一个匿名函数作为处理函数。这个匿名函数会在请求到达时被调用,其中 c gin.Context 是处理函数的参数,表示当前请求的上下文。不能在浏览器中直接输入网址访问,因为这里的是POST请求,需要在客户端输入curl -X POST http://localhost:8080/foo或者在 PowerShell 中发送 HTTP 请求并指定请求方法访问Invoke-RestMethod -Uri 'http://localhost:8080/foo' -Method Post
5.4.3. 示例概述
-
导入了所需的包,包括日志处理、HTTP 相关的包以及 Gin 框架的包。
-
在 main 函数中创建了一个 Gin 实例 r,作为路由引擎。
-
使用 gin.DebugPrintRouteFunc 函数设置了一个自定义的路由调试打印函数,用于在控制台打印路由信息。
-
定义了一个处理 POST 请求的路由 /foo,当该路由接收到 POST 请求时,会执行传入的匿名函数,该函数会在 HTTP 响应中返回 JSON 格式的响应 "foo"。
-
定义了一个处理 GET 请求的路由 /bar,当该路由接收到 GET 请求时,会执行传入的匿名函数,该函数会在 HTTP 响应中返回 JSON 格式的响应 "bar"。
-
定义了一个处理 GET 请求的路由 /status,当该路由接收到 GET 请求时,会执行传入的匿名函数,该函数会在 HTTP 响应中返回 JSON 格式的响应 "ok"。
-
调用 r.Run() 启动 HTTP 服务,监听地址为 0.0.0.0:8080。
5.5. 如何记录日志
5.5.1. 完整代码
func main() {
// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
gin.DisableConsoleColor()
// 记录到文件。
f, _ := os.Create("gin.log") // 创建一个名为 "gin.log" 的文件用于记录日志
gin.DefaultWriter = io.MultiWriter(f) // 设置 Gin 的默认日志写入器为同时写入文件 f
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default() // 创建一个默认的 Gin 路由引擎实例
router.GET("/ping", func(c *gin.Context) { // 定义一个 HTTP GET 方法的路由 "/ping"
c.String(200, "pong") // 在请求处理函数中返回字符串 "pong"
})
router.Run(":8080") // 启动 HTTP 服务器并监听在端口 8080
}
5.5.2. 知识点
gin.DisableConsoleColor(): 禁用控制台颜色输出,这样在将日志写入文件时不会包含控制台颜色信息。
f, _ := os.Create("gin.log"): 创建一个名为 "gin.log" 的文件用于记录日志,并将文件对象保存在变量 f 中。如果创建文件出现错误,错误会被忽略。
gin.DefaultWriter = io.MultiWriter(f): 将文件 f 设置为 Gin 框架的默认日志写入器,这样日志将同时写入文件 f。
wd, err := os.Getwd()
if err != nil {
fmt.Println("获取当前工作目录失败:", err)
return
}
logFilePath := wd + "/gin.log"
fmt.Println("gin.log 文件路径:", logFilePath)
可以用这段代码获取当前工作目录,gin.log文件就在该路径下。
5.5.3. 示例概述
-
调用 gin.DisableConsoleColor() 禁用控制台颜色输出,因为日志将被写入文件,不需要控制台颜色。
-
调用 os.Create("gin.log") 创建一个名为 "gin.log" 的文件,用于记录日志,并将返回的文件对象赋值给变量 f。
-
将 f 设置为 Gin 框架的默认日志写入器,通过调用 gin.DefaultWriter = io.MultiWriter(f)。这样,Gin 框架的日志将同时写入 gin.log 文件和控制台(如果需要同时写入文件和控制台,可以注释掉当前行,将下一行的注释取消)。
-
调用 gin.Default() 创建一个默认的 Gin 路由引擎实例,并将返回的引擎对象赋值给变量 router。
-
使用 router.GET("/ping", ...) 定义一个 HTTP GET 方法的路由 "/ping",并传入一个请求处理函数,该函数会在收到请求时返回字符串 "pong"。
-
调用 router.Run(":8080") 启动 HTTP 服务器,并监听在端口 8080,等待接收客户端请求。一旦接收到请求,就会调用之前定义的请求处理函数进行处理。
6. 文件上传下载
6.1. Multipart/Urlencoded 绑定
6.1.1. 完整代码
package main
import (
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `form:"user" binding:"required"` // 表单字段 user,使用 binding:"required" 标签表示必填
Password string `form:"password" binding:"required"` // 表单字段 password,使用 binding:"required" 标签表示必填
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
// 使用 c.ShouldBind 方法将请求数据绑定到 LoginForm 结构体
var form LoginForm
if c.ShouldBind(&form) == nil { // 如果绑定成功
if form.User == "user" && form.Password == "password" { // 判断用户名和密码是否正确
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(401, gin.H{"status": "missing params"}) // 如果请求数据缺少必填字段,则返回错误状态
}
})
router.Run(":8080") // 启动 HTTP 服务器并监听在端口 8080
}
6.1.2. 知识点
-
type LoginForm struct { ... }: LoginForm 是一个自定义的结构体类型,用于表示登录表单的数据结构。结构体中包含了两个字段:User 和 Password,分别对应表单中的 "user" 和 "password" 字段。
-
router.POST("/login", func(c *gin.Context) { ... }): router.POST 方法注册了一个 HTTP POST 方法的路由,路由路径为 "/login"。当客户端发送 HTTP POST 请求到 "/login" 路径时,Gin 会调用传入的匿名函数作为该路由的处理函数。
-
c.ShouldBind(&form): c.ShouldBind 方法用于将 HTTP 请求中的数据绑定到指定的结构体对象。这里使用 c.ShouldBind(&form) 将请求数据绑定到 form 变量,该变量是上一步定义的 LoginForm 结构体对象。
-
c.JSON(200, gin.H{...}): c.JSON 方法用于将 JSON 数据作为响应发送给客户端。200 表示 HTTP 响应状态码为 200,gin.H{...} 表示要返回的 JSON 数据。这里返回了一个包含 "status" 字段的 JSON 数据,字段的值根据登录表单的用户名和密码进行判断而定。
-
c.JSON(401, gin.H{...}): 同样地,c.JSON 方法也可以用于返回 HTTP 响应状态码为 401 的错误响应。这里通过 c.JSON(401, gin.H{"status": "unauthorized"}) 和 c.JSON(401, gin.H{"status": "missing params"}) 分别返回了未授权和缺少必填字段的错误信息。
6.1.3. 示例概述
-
定义了一个 LoginForm 结构体,用于绑定 POST 请求中的表单数据。该结构体包含了两个字段,分别是 User 和 Password,使用了 Gin 框架的表单绑定标签进行验证,要求这两个字段都是必填的。
-
在 main 函数中创建了一个 Gin 路由实例 router。
-
设置了一个 POST 路由 "/login",并定义了处理函数。在该处理函数中,使用 c.ShouldBind 方法将请求数据绑定到 LoginForm 结构体。
-
如果绑定成功,即表单数据满足要求,那么进一步判断用户名和密码是否正确,如果正确,则返回 HTTP 状态码 200 和一个 JSON 格式的成功消息;否则,返回 HTTP 状态码 401 和一个 JSON 格式的未授权消息。
-
如果请求数据缺少必填字段,即绑定失败,那么返回 HTTP 状态码 401 和一个 JSON 格式的缺少参数消息。
-
最后,通过调用 router.Run(":8080") 启动 HTTP 服务器并监听在端口 8080,等待客户端请求。
6.2. 单文件上传
6.2.1. 完整代码
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
// 定义上传文件的接口路由为 "/upload",使用 POST 方法
router.POST("/upload", func(c *gin.Context) {
// 获取上传的文件对象
file, _ := c.FormFile("file") // 通过 c.FormFile() 方法获取上传的文件,"file" 是上传文件的字段名
log.Println(file.Filename) // 打印上传文件的文件名
// 上传文件至指定目录
// c.SaveUploadedFile(file, dst) // 使用 c.SaveUploadedFile() 方法将文件保存到指定的目录
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) // 返回上传成功的信息
})
router.Run(":8080") // 启动 Gin 服务器,监听在 8080 端口
}
6.2.2. 代码解释
-
首先,创建了一个默认的 Gin 路由引擎 router := gin.Default()。
-
设置了一个上传文件的接口路由为 "/upload",并使用 POST 方法处理文件上传请求:router.POST("/upload", func(c gin.Context) {...})。这里使用了匿名函数作为路由处理函数,接收一个 c gin.Context 参数,用于处理 HTTP 请求和响应。
-
在路由处理函数中,通过 c.FormFile("file") 获取上传的文件对象。c.FormFile 方法是 Gin 框架提供的用于获取上传文件的方法,其中 "file" 是上传文件的字段名。如果没有找到对应的文件字段,会返回错误。
-
使用 log.Println(file.Filename) 打印上传文件的文件名。这里只是简单地将文件名打印到控制台,实际应用中可能需要根据需求进行文件名的处理。
-
可以通过 c.SaveUploadedFile(file, dst) 将文件保存到指定目录。其中 file 是上传的文件对象,dst 是目标保存路径。在实际应用中,需要根据需求设置合适的目标保存路径,并处理文件名重复等情况。
-
使用 c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename)) 返回上传成功的信息。这里使用了 Gin 框架提供的 c.String 方法,设置 HTTP 响应状态码为 200,返回一个字符串作为响应体,其中包含上传文件的文件名。
-
最后,通过 router.Run(":8080") 启动 Gin 服务器,监听在 8080 端口,等待接收文件上传请求。
6.3. 多文件上传
6.3.1. 完整代码
func main() {
// 创建默认的 Gin 路由引擎
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
// 设置文件上传的接口路由为 "/upload",使用 POST 方法处理文件上传请求
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 服务器,监听在 8080 端口,等待接收文件上传请求
router.Run(":8080")
6.3.2. 代码解释
-
创建 Gin 路由引擎:通过 gin.Default() 创建了一个默认的 Gin 路由引擎,用于处理 HTTP 请求。
-
设置文件上传接口:使用 router.POST("/upload", ...) 将文件上传接口设置为 "/upload",并指定 HTTP 方法为 POST。这意味着该接口将处理客户端发送的 POST 请求。
-
获取上传的 multipart form 数据:在接口的处理函数中,使用 c.MultipartForm() 方法获取上传的 multipart form 数据,其中 c 是 *gin.Context 类型的请求上下文对象。
-
获取上传的文件对象列表:从 multipart.Form 结构体中的 File 字段获取上传的文件对象列表。这里使用了 "upload[]" 作为文件字段的名称,表示可以上传多个文件,且字段名为 "upload[]"。
-
处理上传的文件:通过遍历文件对象列表,可以对每个文件进行处理,例如获取文件名、保存文件到指定目录、处理文件名重复等。这里的处理逻辑通过注释进行了简单说明,实际应用中需要根据需求进行具体处理。
-
返回上传成功的信息:通过 c.String() 方法返回上传成功的信息,包含上传的文件数量。
-
启动 HTTP 服务器:使用 router.Run(":8080") 启动 HTTP 服务器,监听在 8080 端口,等待接收文件上传请求。
需要注意的是,这段代码还有一部分注释掉的代码,包括了设置 router.MaxMultipartMemory 限制内存大小的功能。可以根据实际需求取消注释并设置合适的内存限制。同时,这段代码还没有处理文件上传过程中可能出现的错误情况,如文件大小限制、文件类型限制等,需要根据具体需求进行完善。
7. TODO
7.1. 自定义验证器//TODO
可能有问题。略
7.1.1. 完整代码
package main
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
)
// Booking contains binded and validated data.
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
date, ok := fl.Field().Interface().(time.Time)
if ok {
today := time.Now()
if today.After(date) {
return false
}
}
return true
}
func main() {
route := gin.Default()
// 注册自定义验证器函数
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
// 定义路由
route.GET("/bookable", getBookable)
// 启动服务
route.Run(":8085")
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
7.1.2. 知识点
-
type Booking struct: 定义了一个结构体类型 Booking,用于存储从请求中绑定和验证后的数据。
-
var bookableDate validator.Func: 定义了一个自定义验证器函数 bookableDate,用于验证日期是否可预订。该函数会在后续的验证中使用。
-
binding.Validator.Engine(): 获取 Gin 框架中的验证器引擎实例,用于注册自定义验证器函数。
-
v.RegisterValidation("bookabledate", bookableDate): 使用 v.RegisterValidation() 方法将自定义验证器函数 bookableDate 注册到 Gin 的验证器引擎中,并指定了验证器的名称为 "bookabledate"。这样,在后续的验证中就可以通过该名称来使用这个自定义验证器函数。
-
var bookableDate validator.Func: 定义了一个自定义验证器函数 bookableDate,用于验证日期是否可预订。该函数会在后续的验证中使用。
-
c.ShouldBindWith(&b, binding.Query): 在 getBookable 处理函数中,使用 c.ShouldBindWith() 方法将请求中的数据绑定到 b 变量,并使用 binding.Query 标签指定了从查询参数中获取数据。这样,请求中的数据就会被绑定到 Booking 结构体的字段,并且会进行后续的验证。
-
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}): 在验证失败时,使用 c.JSON() 方法将一个 JSON 格式的错误响应发送给客户端,其中包含了一个键值对,键为 "error",值为验证失败的错误信息。HTTP 状态码被设置为 http.StatusBadRequest,表示请求参数错误。
-
自定义验证器函数,用于验证日期是否可预订,具体实现如下:
-
func(fl validator.FieldLevel) bool: 这是一个函数类型,参数为 validator.FieldLevel,返回值为 bool。validator.FieldLevel 是一个接口,用于在验证器中获取字段的相关信息,如字段的值、字段的标签等。
-
fl.Field().Interface().(time.Time): 使用 fl.Field() 方法获取字段的值,并使用 Interface() 方法将其转换为 interface{} 类型。然后使用类型断言将其转换为 time.Time 类型的值。这样,我们可以在函数中使用字段的实际值进行验证。
-
today := time.Now(): 使用 time.Now() 函数获取当前的时间,并将其赋值给 today 变量,表示当前的日期时间。
-
if today.After(date) { return false }: 使用 today.After() 方法比较当前时间和字段值的时间,如果当前时间晚于字段值的时间,则返回 false,表示验证失败,日期不可预订。
-
return true: 如果验证通过,即当前时间不晚于字段值的时间,返回 true,表示日期可预订。
-
总的来说,这个函数的实现逻辑是通过比较当前时间和字段值的时间来判断日期是否可预订,如果当前时间晚于字段值的时间,则认为日期不可预订,返回 false,否则返回 true。
7.1.3. 示例概述
-
定义了一个 Booking 结构体,包含了两个字段 CheckIn 和 CheckOut,分别表示入住日期和离店日期。这两个字段使用了 Gin 的 form 标签指定了它们在请求参数中的名称,并使用了 binding 标签来指定了验证规则。
-
定义了一个名为 bookableDate 的自定义验证器函数,用于验证日期是否可预订。这个函数在验证器中注册为 bookabledate,并在验证过程中获取字段的实际值,比较当前时间和字段值的时间来判断日期是否可预订。
-
在 main 函数中,创建了一个 Gin 的默认引擎,用于处理 HTTP 请求。然后使用 binding.Validator.Engine() 方法获取到验证器引擎,并使用 v.RegisterValidation() 方法注册了自定义验证器函数 bookableDate。
-
定义了一个名为 getBookable 的处理函数,用于处理 /bookable 路由的 GET 请求。在这个处理函数中,首先创建了一个 Booking 结构体实例 b,然后使用 c.ShouldBindWith() 方法将请求参数绑定到 b 上,并进行验证。如果验证通过,则返回 HTTP 状态码为 200 的 JSON 响应,表示预订日期有效;否则,返回 HTTP 状态码为 400 的 JSON 响应,表示验证失败并返回错误信息。
-
启动了 Gin 服务,并监听在 8085 端口上。