Introduction to Gin Framework
Gin is a simple, fast and powerful web framework for building web applications of all sizes. Its design concept is simplicity, efficiency and ease of use, which can help developers quickly build high-performance Web services. Whether building API services, web applications or microservices, Gin is a good choice. It has a martini-like API but much better performance, up to 40 times faster thanks to httprouter. If you need performance and good productivity, you'll love Gin.
Realize simple web program without gin framework
package main
import (
"fmt"
"net/http"
)
func main() {
// 注册路由处理函数
http.HandleFunc("/hello", sayhello)
// 启动HTTP服务器并监听指定端口
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Printf("错误:%v\n", err)
return
}
}
// sayhello 是处理 /hello 路由的函数
func sayhello(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "<h1>Hello</h1>")
}
After running the above code, enter the URL localhost:9090 in the browser and return the content <h1>Hello</h1>
download and installGin
go get -u github.com/gin-gonic/gin
First Gin example
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// 创建默认的Gin引擎
r := gin.Default()
// 定义路由和处理函数
r.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "hello world",
})
})
// 启动HTTP服务器并监听指定端口,默认在0.0.0.0:8080启动服务
err := r.Run(":9090")
if err != nil {
return
}
}
RESTful API
REST has nothing to do with technology, and represents a software architecture style. REST is the abbreviation of Representational State Transfer. The Chinese translation is "representational state transfer" or "representational state transfer".
Recommended reading Ruan Yifeng understands RESTful architecture
To put it simply, the meaning of REST is that when the client interacts with the Web server, the four request methods in the HTTP protocol are used to represent different actions.
GET
used to obtain resourcesPOST
to create new resourcesPUT
used to update resourcesPATCH
Used to update some resourcesDELETE
used to delete resources
As long as the API program follows the REST style, it can be called a RESTful API. At present, in the architecture where the front and back ends are separated, the front and back ends basically interact through RESTful APIs.
For example, we now want to write a conference management system, we can query, create, update and delete conferences, etc. When we write programs, we need to design the way and path for the client browser to interact with our Web server. According to experience, we usually design the following mode:
request method | URL | meaning |
---|---|---|
GET | /meeting | Query meeting information |
POST | /create_meeting | Create meeting minutes |
POST | /update_meeting | Update meeting information |
POST | /delete_meeting | Delete meeting information |
For the same requirements, we design the RESTful API as follows:
request method | URL | meaning |
---|---|---|
GET | /meeting | Query meeting information |
POST | /meeting | Create meeting minutes |
PUT | /meeting | Update meeting information |
DELETE | /meeting | Delete meeting information |
The Gin framework supports the development of RESTful APIs.
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// GET请求,获取资源
r.GET("/meeting", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg": "GET",
})
})
// POST请求,新建资源
r.POST("/meeting", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg": "POST",
})
})
// PUT请求,更新资源
r.PUT("/meeting", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg": "PUT",
})
})
// PATCH请求,部分更新资源
r.PATCH("/meeting", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg": "PATCH",
})
})
// DELETE请求,删除资源
r.DELETE("/meeting", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg": "DELETE",
})
})
// 启动HTTP服务器并监听指定端口
err := r.Run(":9090")
if err != nil {
return
}
}
When developing a RESTful API, we usually use Postman as a client testing tool.
go template syntax
package main
import (
"errors"
"fmt"
"html/template"
"net/http"
)
type UserInfo struct {
Name string
Gender string
Age int
}
func sayHello(w http.ResponseWriter, _ *http.Request) {
// 自定义函数
admire := func(name string, gender string) (string, error) {
var praise string
if gender == "男" {
praise = "真帅气!!!!!!!!"
} else if gender == "女" {
praise = "真漂亮!!!!!!!!"
} else {
return "", errors.New("invalid gender")
}
return name + praise, nil
}
// 解析指定文件生成模板对象(并注册自定义函数)
tmpl, err := template.New("hello.tmpl").Funcs(template.FuncMap{
"admire": admire}).ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
// 利用给定数据渲染模板,并将结果写入w
user1 := UserInfo{
Name: "小王子",
Gender: "男",
Age: 17,
}
user2 := map[string]interface{
}{
"name": "小公主",
"gender": "女",
"age": 19,
}
hobbylist := []string{
"跑步",
"听音乐",
"学习",
}
err = tmpl.Execute(w, map[string]interface{
}{
"user1": user1,
"user2": user2,
"hobby": hobbylist,
})
if err != nil {
return
}
}
func qianTao(w http.ResponseWriter, _ *http.Request) {
tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "小王子",
Gender: "男",
Age: 17,
}
err = tmpl.Execute(w, user)
if err != nil {
return
}
}
func main() {
http.HandleFunc("/", sayHello)
http.HandleFunc("/demo", qianTao)
err := http.ListenAndServe(":9090", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}
Below is hello.tmpl
the sample file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
<p>Hello {
{.user1.Name}}</p>
<p>性别:{
{.user1.Gender}}</p>
<p>年龄:{
{.user1.Age}}</p>
<br>
<p>Hello {
{.user2.name}}</p>
<p>性别:{
{.user2.gender}}</p>
<p>年龄:{
{.user2.age}}</p>
{
{/*自定义变量*/}}
{
{ $a := 100 }}
{
{ $b := .user1.Age }}
<hr>
{
{/*移除空格*/}}
<p>年龄:{
{- .user2.age -}}</p>
<hr>
{
{/*条件判断*/}}
{
{ if $a}}
{
{$a}}
{
{else}}
a 不存在
{
{end}}
<hr>
{
{ if lt .user1.Age 18}}
未成年
{
{else}}
上大学了
{
{end}}
<hr>
{
{range $index,$hobby :=.hobby}}
<p>{
{$index}}------{
{$hobby}}</p>
{
{else}}
没有爱好
{
{end}}
<hr>
{
{/*with作用域*/}}
{
{with .user1}}
<p>Hello {
{.Name}}</p>
<p>性别:{
{.Gender}}</p>
<p>年龄:{
{.Age}}</p>
{
{end}}
<hr>
{
{index .hobby 2}}
<hr>
{
{/*自定义函数*/}}
{
{admire .user1.Name .user1.Gender}}
{
{admire .user2.name .user2.gender}}
</body>
predefined function
When executing a template, functions are looked up in two function dictionaries: first the template function dictionary, then the global function dictionary. Generally, functions are not defined in templates, but functions are added to templates using the Funcs method.
The predefined global functions are as follows:
and
函数返回它的第一个empty参数或者最后一个参数;
就是说"and x y"等价于"if x then y else x";所有参数都会执行;
or
返回第一个非empty参数或者最后一个参数;
亦即"or x y"等价于"if x then x else y";所有参数都会执行;
not
返回它的单个参数的布尔值的否定
len
返回它的参数的整数类型长度
index
执行结果为第一个参数以剩下的参数为索引/键指向的值;
如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
print
即fmt.Sprint
printf
即fmt.Sprintf
println
即fmt.Sprintln
html
返回与其参数的文本表示形式等效的转义HTML。
这个函数在html/template中不可用。
urlquery
以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
这个函数在html/template中不可用。
js
返回与其参数的文本表示形式等效的转义JavaScript。
call
执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2);
其中Y是函数类型的字段或者字典的值,或者其他类似情况;
call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
comparison function
Boolean functions will treat any type of zero value as false and all others as true.
The following is a collection of binary comparison operations defined as functions:
eq 如果arg1 == arg2则返回真
ne 如果arg1 != arg2则返回真
lt 如果arg1 < arg2则返回真
le 如果arg1 <= arg2则返回真
gt 如果arg1 > arg2则返回真
ge 如果arg1 >= arg2则返回真
In order to simplify multi-parameter equality detection, eq (only eq) can accept 2 or more parameters, it will compare the first parameter with the remaining parameters in turn, and return the result of the following formula:
{
{eq arg1 arg2 arg3}}
Comparison functions only work on primitive types (or redefined primitive types, such as "type Celsius float32"). However, integers and floating point numbers cannot be compared to each other.
custom function
For specific examples, refer to the main.go file mentioned in the above template syntax
func sayHello(w http.ResponseWriter, _ *http.Request) {
// 自定义函数
admire := func(name string, gender string) (string, error) {
var praise string
if gender == "男" {
praise = "真帅气!!!!!!!!"
} else if gender == "女" {
praise = "真漂亮!!!!!!!!"
} else {
return "", errors.New("invalid gender")
}
return name + praise, nil
}
// 解析指定文件生成模板对象(并注册自定义函数)
tmpl, err := template.New("hello.tmpl").Funcs(template.FuncMap{
"admire": admire}).ParseFiles("./hello.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
}
transfer
{
{admire .user1.Name .user1.Gender}}
{
{admire .user2.name .user2.gender}}
template nesting
func qianTao(w http.ResponseWriter, _ *http.Request) {
tmpl, err := template.ParseFiles("./t.tmpl", "./ul.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfo{
Name: "小王子",
Gender: "男",
Age: 17,
}
err = tmpl.Execute(w, user)
if err != nil {
return
}
t.tmpl file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>tmpl test</title>
</head>
<body>
<h1>测试嵌套template语法</h1>
<hr>
{
{template "ul.tmpl"}}
<hr>
{
{template "ol.tmpl"}}
</body>
</html>
{
{ define "ol.tmpl"}}
<ol>
<li>吃饭</li>
<li>睡觉</li>
<li>打豆豆</li>
</ol>
{
{end}}
<div>你好,{
{.Name}}!</div>
ul.html file
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>
template inheritance
main.go document
package main
import (
"fmt"
"html/template"
"net/http"
)
func index(w http.ResponseWriter, _ *http.Request) {
//定义模板
//解析模板
tmpl, err := template.ParseFiles("./base.tmpl", "./index.tmpl")
if err != nil {
fmt.Printf("parse error: %v\n", err)
return
}
msg := "hello world"
//渲染模板
err = tmpl.ExecuteTemplate(w, "index.tmpl", msg)
if err != nil {
return
}
}
func base(w http.ResponseWriter, _ *http.Request) {
tmpl, err := template.ParseFiles("./base.tmpl")
if err != nil {
fmt.Printf("parse error: %v\n", err)
return
}
msg := "这是base页面"
//渲染模板
err = tmpl.Execute(w, msg)
if err != nil {
return
}
}
func main() {
http.HandleFunc("/index", index)
http.HandleFunc("/base", base)
err := http.ListenAndServe(":9000", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}
base.tmpl file
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>模板继承</title>
<style>
{
margin: 0
;
}
.nav {
height: 50px;
width: 100%;
position: fixed;
top: 0;
background-color: burlywood;
}
.main {
margin-top: 50px;
}
.menu {
width: 20%;
height: 100%;
position: fixed;
left: 0;
background-color: cornflowerblue;
}
.center {
text-align: center;
}
</style>
</head>
<body>
<div class="nav"></div>
<div class="main">
<div class="menu"></div>
<div class="content center">
{
{.}}
{
{block "content" .}}
{
{end}}
</div>
</div>
</body>
</html>
index.tmpl file
{
{/*继承根模板*/}}
{
{template "base.tmpl" .}}
{
{/*重新定义模板*/}}
{
{define "content"}}
<h1>这是index页面</h1>
{
{end}}
If our template names conflict, for example, a index.tmpl
template is defined under different business lines, we can solve it by the following two methods.
- Use
{ {define 模板名}}
the statement at the beginning of the template file to explicitly name the template. - You can store the template files in
templates
different directories under the folder, and then usetemplate.ParseGlob("templates/**/*.tmpl")
the parsing template.
template supplement
Modify the default identifier
The template engine of the Go standard library uses curly braces {
{
and }}
as identifiers, and many front-end frameworks (such as Vue
and AngularJS
) also use {
{
and }}
as identifiers, so when we use the Go language template engine and the above front-end frameworks at the same time, there will be conflicts. At this time We need to modify the identifier, modify the front end or modify the Go language. Here's how to modify the default identifiers of the Go language template engine:
template.New("t.tmpl").Delims("{[", "]}").ParseFiles("./t.tmpl")
Finally when we render
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义模板函数</title>
</head>
<body>
<h1>姓名: {[.Name]}</h1>
<h1>性别: {[.Gender]}</h1>
<h1>年龄: {[.Age]}</h1>
</body>
</html>
The difference between text/template and html/tempalte
html/template
It is aimed at scenarios where HTML content needs to be returned, and some risky content will be escaped during the template rendering process to prevent cross-site scripting attacks (XSS).
For example, I define the following template file:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
{
{.}}
</body>
</html>
At this time, a piece of JS code is passed in and used html/template
to render the file, and the escaped JS content will be displayed on the page.
But in some scenarios, if we believe the content entered by the user and don't want to escape, we can write a safe function by ourselves and manually return a template.HTML
type of content. Examples are as follows:
func xss(w http.ResponseWriter, r *http.Request){
tmpl,err := template.New("xss.tmpl").Funcs(template.FuncMap{
"safe": func(s string)template.HTML {
return template.HTML(s)
},
}).ParseFiles("./xss.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
jsStr := `<script>alert('123')</script>`
err = tmpl.Execute(w, jsStr)
if err != nil {
fmt.Println(err)
}
}
In this way, we only need to use the safe function we defined after the content that does not need to be escaped in the template file.
{
{ . | safe }}
Gin rendering
We first define a folder for storing template files , and then define a folder and a folder templates
in it according to the business . The content of the file is as follows:posts
users
posts/index.html
{
{define "posts/index.tmpl"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="/xxx/index.css">
<title>posts/index</title>
</head>
<body>
{
{.title |safe}}
</body>
<script src="/xxx/index.js"></script>
</html>
{
{end}}
users/index.html
The content of the file is as follows:
{
{define "users/index.tmpl"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>users/index</title>
</head>
<body>
{
{.title}}
</body>
</html>
{
{end}}
LoadHTMLGlob()
The or method is used in the Gin framework LoadHTMLFiles()
to render HTML templates.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
func main() {
r := gin.Default()
// 设置静态文件路由,表示以/xxx开头的静态文件都会去statics目录下找
r.Static("/xxx", "./statics")
// 设置模板函数
r.SetFuncMap(template.FuncMap{
"safe": func(s string) template.HTML {
return template.HTML(s)
},
})
// 加载模板文件
r.LoadHTMLGlob("templates/**/*")
// 处理/posts/index请求
r.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "<a href= https://uestcwxy.love>wxy的博客</a>",
})
})
// 处理/users/index请求
r.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "https://uestcwxy.top",
})
})
// 启动服务器
err := r.Run(":9000")
if err != nil {
fmt.Println("服务器启动失败")
}
}
Use template inheritance
The Gin framework uses a single template by default. If you need to use block template
functions, you can use "github.com/gin-contrib/multitemplate"
the library to implement them. The specific examples are as follows:
First of all, suppose we have the following template files under the templates folder in our project directory, which home.tmpl
are index.tmpl
inherited from base.tmpl
:
templates
├── includes
│ ├── home.tmpl
│ └── index.tmpl
├── layouts
│ └── base.tmpl
└── scripts.tmpl
Then we define a loadTemplates
function as follows:
func loadTemplates(templatesDir string) multitemplate.Renderer {
// 创建一个新的 multitemplate.Renderer 实例
r := multitemplate.NewRenderer()
// 加载 layouts 目录下的模板文件
layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
// 加载 includes 目录下的模板文件
includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
// 为 layouts/ 和 includes/ 目录生成 templates map
for _, include := range includes {
// 创建 layouts 的副本
layoutCopy := make([]string, len(layouts))
copy(layoutCopy, layouts)
// 将 layouts 和 include 组合成一个文件切片
files := append(layoutCopy, include)
// 将文件切片添加到 multitemplate.Renderer 实例中
r.AddFromFiles(filepath.Base(include), files...)
}
return r
}
we are in main
the function
func indexFunc(c *gin.Context) {
// 渲染 index.tmpl 模板并返回给客户端
c.HTML(http.StatusOK, "index.tmpl", nil)
}
func homeFunc(c *gin.Context) {
// 渲染 home.tmpl 模板并返回给客户端
c.HTML(http.StatusOK, "home.tmpl", nil)
}
func main() {
// 创建一个默认的 Gin 引擎实例
r := gin.Default()
// 加载模板文件,并将返回的 multitemplate.Renderer 实例赋值给 Gin 引擎的 HTMLRender 字段
r.HTMLRender = loadTemplates("./templates")
// 设置路由处理函数,处理 /index 请求
r.GET("/index", indexFunc)
// 设置路由处理函数,处理 /home 请求
r.GET("/home", homeFunc)
// 启动服务器,监听默认端口
r.Run()
}
Supplementary file path handling
Regarding the paths of template files and static files, we need to set them according to the requirements of the company/project. The path of the currently executing program can be obtained using the following function.
import (
"os"
"path/filepath"
)
func getCurrentPath() string {
// 获取可执行文件的路径
if ex, err := os.Executable(); err == nil {
// 返回可执行文件的目录路径
return filepath.Dir(ex)
}
// 如果获取路径失败,则返回当前目录路径
return "./"
}
JSON rendering
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someJSON", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
"name": "wxy",
})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 方法二:使用结构体
type msg struct {
Name string `json:"name"`
Message string `json:"message"`
Age int `json:"age"`
}
data := msg{
"121", "hh", 18}
c.JSON(http.StatusOK, data)
})
err := r.Run(":9090")
if err != nil {
return
}
}
XML rendering
Note the need to use named struct types.
func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someXML", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.XML(http.StatusOK, gin.H{
"message": "Hello world!"})
})
r.GET("/moreXML", func(c *gin.Context) {
// 方法二:使用结构体
type MessageRecord struct {
Name string
Message string
Age int
}
var msg MessageRecord
msg.Name = "小王子"
msg.Message = "Hello world!"
msg.Age = 18
c.XML(http.StatusOK, msg)
})
r.Run(":8080")
}
YMAL rendering
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{
"message": "ok", "status": http.StatusOK})
})
protobuf rendering
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{
int64(1), int64(2)}
label := "test"
// protobuf 的具体定义写在 testdata/protoexample 文件中。
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
// 请注意,数据在响应中变为二进制数据
// 将输出被 protoexample.Test protobuf 序列化了的数据
c.ProtoBuf(http.StatusOK, data)
})
Get parameters
get querystring parameter
querystring
Refers to ?
the parameters carried behind in the URL, for example: /user/search?username=wxy&address=沙河校区
. The method to obtain the querystring parameter of the request is as follows:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/search", func(c *gin.Context) {
username := c.DefaultQuery("username", "wxy")
//username := c.Query("username")
address := c.Query("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
err := r.Run()
if err != nil {
return
}
}
Get form parameters
When the data requested by the front end is submitted through the form form, for example, sending /user/search
a POST request to China, the method of obtaining the request data is as follows:
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "wxy")
username, ok := c.GetPostForm("username")
if !ok {
username = "hhh"
}
//username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
err := r.Run(":8080")
if err != nil {
return
}
}
get json parameter
When the data requested by the front end is submitted through JSON, such as /json
sending a POST request to China, the method of obtaining the request parameters is as follows:
r.POST("/json", func(c *gin.Context) {
// 注意:下面为了举例子方便,暂时忽略了错误处理
b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
// 定义map或结构体
var m map[string]interface{
}
// 反序列化
_ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m)
})
For a more convenient way to obtain request parameters, see the parameter binding section below.
Get the path parameter
The parameters of the request are passed through the URL path, for example: /user/search/wxy/沙河校区
. The way to obtain the parameters in the request URL path is as follows.
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/search/:username/:address", func(c *gin.Context) {
username := c.Param("username")
address := c.Param("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
err := r.Run(":8080")
if err != nil {
return
}
}
parameter binding
Content-Type
In order to obtain request-related parameters more conveniently and improve development efficiency, we can identify the request data type based on the request and use the reflection mechanism to automatically extract the request parameters such as QueryString
, form表单
, JSON
, XML
etc. into the structure. The following sample code demonstrates .ShouldBind()
a powerful feature that can automatically extract data of type and type based on the request JSON
, form表单
and QueryString
bind the value to the specified structure object.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
// Login 结构体用于绑定JSON数据
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 处理绑定JSON的示例请求 ({"user": "wxy", "password": "123456"})
r.POST("/loginJSON", func(c *gin.Context) {
var login Login
// 将请求中的JSON数据绑定到Login结构体
if err := c.ShouldBind(&login); err == nil {
fmt.Printf("登录信息:%#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()})
}
})
// 处理绑定form表单的示例请求 (user=q1mi&password=123456)
r.POST("/loginForm", func(c *gin.Context) {
var login Login
// 根据请求的Content-Type自动选择绑定器进行绑定
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()})
}
})
// 处理绑定QueryString的示例请求 (/loginQuery?user=q1mi&password=123456)
r.GET("/loginForm", func(c *gin.Context) {
var login Login
// 根据请求的Content-Type自动选择绑定器进行绑定
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()})
}
})
// 监听并在0.0.0.0:8080上提供服务
err := r.Run(":8080")
if err != nil {
return
}
}
ShouldBind
The function selects the appropriate binding engine for data binding based on the method and content type of the request. For GET
requests, only query parameter binding is used; for POST
requests, JSON or XML data binding is preferred, and if not JSON or XML, form data binding is used. This makes it easy to parse and bind the data in the request into a structure so that it can be used when processing the request.
File Upload
single file upload
File upload front-end page code:
<!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>
Part of the code of the backend gin framework:
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
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()
}
Multiple file uploads
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("./upload/%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()
}
redirect
HTTP redirection
HTTP redirection is easy. Both internal and external redirection are supported.
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com/")
})
// Listen and serve on 0.0.0.0:8080
err := r.Run(":8080")
if err != nil {
return
}
}
route redirection
Route redirection, use HandleContext
:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
// 指定重定向的URL
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"hello": "world"})
})
// Listen and serve on 0.0.0.0:8080
err := r.Run(":8080")
if err != nil {
return
}
}
Gin Routing
common routing
r.GET("/index", func(c *gin.Context) {
...})
r.GET("/login", func(c *gin.Context) {
...})
r.POST("/login", func(c *gin.Context) {
...})
Any
In addition, there is a method that can match all request methods as follows:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.Any("/user", func(c *gin.Context) {
switch c.Request.Method {
case http.MethodGet:
c.JSON(http.StatusOK, gin.H{
"method": http.MethodGet})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{
"method": http.MethodPost})
}
})
err := r.Run()
if err != nil {
fmt.Println(err.Error())
}
}
Add a handler for a route that does not have a handler configured. By default, it returns a 404 code. The following code returns a views/404.html
page for all requests that do not match a route.
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})
routing group
We can group routes that share a common URL prefix into a route group. It is customary to use a {}
pair of routes wrapped in the same group, this is just for clarity, {}
there is no difference in function whether you use the package or not.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
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) {
})
}
err := r.Run()
if err != nil {
fmt.Println(err.Error())
}
}
Routing groups also support nesting, for example:
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) {
...})
}
Usually we use route grouping to divide business logic or divide API versions.
routing principle
The routing principle of the Gin framework is dynamic routing implemented using a prefix tree. It uses a customized version of httprouter, and its routing principle is a tree structure that uses a lot of common prefixes, basically a compact Trie tree (or just a Radix Tree).
Gin middleware
The Gin framework allows developers to add the user's own hook (Hook) function in the process of processing the request. This hook function is called middleware. Middleware is suitable for processing some common business logic, such as login authentication, permission verification, data paging, logging, time-consuming statistics, etc.
Define middleware
Middleware in Gin must be a gin.HandlerFunc
type.
Record interface time-consuming middleware
For example, we define a middleware that counts the time spent on requests like the following code.
// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Set("name", "wxy") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
// 调用该请求的剩余处理程序
c.Next()
// 不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
log.Println(cost)
}
}
Middleware that logs the response body
Sometimes we may want to record the response data returned to the client under certain circumstances. At this time, we can write a middleware to handle it.
type bodyLogWriter struct {
gin.ResponseWriter // 嵌入gin框架ResponseWriter
body *bytes.Buffer // 我们记录用的response
}
// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
w.body.Write(b) // 我们记录一份
return w.ResponseWriter.Write(b) // 真正写入响应
}
// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
blw := &bodyLogWriter{
body: bytes.NewBuffer([]byte{
}), ResponseWriter: c.Writer}
c.Writer = blw // 使用我们自定义的类型替换默认的
c.Next() // 执行业务逻辑
fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}
Cross domain middleware cors
It is recommended to use the community’s https://github.com/gin-contrib/cors library, a line of code to solve the cross-domain problem under the front-end and back-end separation architecture.
Note: This middleware needs to be registered before the business processing function.
This library supports various commonly used configuration items, and the specific usage methods are as follows.
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// CORS for https://foo.com and https://github.com origins, allowing:
// - PUT and PATCH methods
// - Origin header
// - Credentials share
// - Preflight requests cached for 12 hours
router.Use(cors.New(cors.Config{
AllowOrigins: []string{
"https://foo.com"}, // 允许跨域发来请求的网站
AllowMethods: []string{
"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的请求方法
AllowHeaders: []string{
"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{
"Content-Length"},
AllowCredentials: true,
AllowOriginFunc: func(origin string) bool {
// 自定义过滤源站的方法
return origin == "https://github.com"
},
MaxAge: 12 * time.Hour,
}))
router.Run()
}
Of course, you can simply use the default configuration like the sample code below to allow all cross-origin requests.
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
Register middleware
In the gin framework, we can add any number of middleware for each route.
Register for global routing
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()
}
Individually register for a route
// 给/test2路由单独注册中间件(可注册多个)
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!",
})
})
Register middleware for routing group
There are two ways to register middleware for routing group.
Writing 1:
shopGroup := r.Group("/shop", StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {
...})
...
}
Writing 2:
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
shopGroup.GET("/index", func(c *gin.Context) {
...})
...
}
Middleware Considerations
gin default middleware
gin.Default()
Logger
and middleware are used by default Recovery
, where:
Logger
Middleware writes logsgin.DefaultWriter
even if configuredGIN_MODE=release
.Recovery
The middleware will recover anythingpanic
. If there is a panic, a 500 response code will be written.
If you don't want to use the above two default middleware, you can create gin.New()
a new route without any default middleware.
Use goroutine in gin middleware
handler
When starting a new one in middleware or in goroutine
, the original context (c *gin.Context) cannot be usedc.Copy()
, its read-only copy ( ) must be used.
run multiple services
We can start services on multiple ports, for example:
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,
}
// 借助 errgroup.Group 或者自行开启两个 goroutine 分别启动两个服务
g.Go(func() error {
// 启动 server01 服务
return server01.ListenAndServe()
})
g.Go(func() error {
// 启动 server02 服务
return server02.ListenAndServe()
})
// 等待所有 goroutine 完成,并返回可能发生的错误
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
Refer to a big guy's blog to organize