go get github.com/gin-gonic/gin
示例
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserInfo struct {
Name string
Gender string
Age int
}
func main() {
r := gin.Default()
r.GET("/hello", func(context *gin.Context) {
context.JSON(http.StatusOK, &UserInfo{
Name: "light",
Gender: "male",
Age: 21,
})
})
r.Run()
}
将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello
就能看到一串JSON字符串。
REST是一种软件架构风格,简单来说,就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。
例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,按照RESTful API设计如下:
请求方法 | URL | 含义 |
---|---|---|
GET | /book | 查询数据信息 |
POST | /book | 创建书籍信息 |
PUT | /book | 更新书籍信息 |
DELETE | /book | 删除书籍信息 |
GIn框架支持RESTful API的开发
func main() {
r := gin.Default()
r.GET("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "GET",
})
})
r.POST("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "POST",
})
})
r.PUT("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "PUT",
})
})
r.DELETE("/book", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "DELETE",
})
})
}
这里说的渲染主要是以某种数据格式比如json向客户端发送数据,一般写在回调函数中。然后客户端浏览器会将这些数据渲染在HTML页面中。
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!"})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 方法二:使用结构体
var msg struct {
Name string `json:"user"`
Message string
Age int
}
msg.Name = "张三"
msg.Message = "Hello world!"
msg.Age = 21
c.JSON(http.StatusOK, msg)
})
r.Run(":8080")
}
user.proto:
syntax = "proto3";
option go_package = "./;protoFile";
package protoFile;
message User{
string name = 1;
string gender = 2;
int32 age = 3;
}
r.GET("/proto", func(c *gin.Context) {
data := &protoFile.User{
Name: "dawnlight",
Gender: "男",
Age: 20,
}
c.ProtoBuf(http.StatusOK, data)
})
r.Run(":9090")
querystring指的是URL中?后面携带的参数,例如:/user/search?username=张三&address=上海
。 获取请求的querystring参数的方法如下:
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.GET("/user/search", func(c *gin.Context) {
username := c.DefaultQuery("username", "张三")
//username := c.Query("username")
address := c.Query("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run()
}
当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下所示:
func main() {
//Default返回一个默认的路由引擎
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "张三")
username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:
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)
})
更便利的获取请求参数的方式,参见下面的 参数绑定 小节。
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,
})
})
r.Run(":8080")
}
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
// Binding from JSON
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
router := gin.Default()
// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
router.POST("/loginJSON", func(c *gin.Context) {
var login Login
if err := c.ShouldBind(&login); err == nil {
fmt.Printf("login info:%#v\n", login)
c.JSON(http.StatusOK, gin.H{
"user": login.User,
"password": login.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// 绑定form表单示例 (user=q1mi&password=123456)
router.POST("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的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)
router.GET("/loginForm", func(c *gin.Context) {
var login Login
// ShouldBind()会根据请求的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()})
}
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
示例:
html表单:
<form method="post" action="/user">
<tr>
<label>用户名:label>
<input type="text" name="username"> <br>
tr>
<tr>
<label>密码:label>
<input type="password" name="password" > <br>
tr>
<tr>
<input type="submit" value="提交">
tr>
form>
利用反射来提取表单数据,与结构体绑定在一起:
type UserInfo struct{
Name string `form:"username"`
Pwd string `form:"password"`
}
func main() {
r := gin.Default()
r.LoadHTMLFiles("2formTest\\userInfo.html")
r.GET("/user", func(c *gin.Context) {
c.HTML(http.StatusOK, "userInfo.html", nil)
})
r.POST("/user", func(c *gin.Context) {
var user UserInfo
if err := c.ShouldBind(&user); err == nil {
c.JSON(http.StatusOK, gin.H{
"username" : user.Name,
"password" : user.Pwd,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
r.Run(":9090")
}
文件上传前端页面代码:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传文件title>
head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" multiple>
<input type="submit" value="上传">
form>
body>
html>
后端gin框架部分代码:
func main() {
r := gin.Default()
r.LoadHTMLFiles("3uploadFile/upload.html")
r.GET("/upload", func(c *gin.Context) {
c.HTML(http.StatusOK, "upload.html", nil)
})
r.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("F:\\GoProject\\Gin\\ginDemo1\\UploadFile\\%s", file.Filename)
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message" : fmt.Sprintf("'%s' uploaded!", file.Filename),
})
})
r.Run(":9090")
}
func main() {
r := gin.Default()
r.LoadHTMLFiles("3uploadFile/upload.html")
r.GET("/upload", func(c *gin.Context) {
c.HTML(http.StatusOK, "upload.html", nil)
})
r.POST("/upload", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("F:\\GoProject\\Gin\\ginDemo1\\UploadFile\\%s_%d", file.Filename, index)
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message" : fmt.Sprintf("%d files uploaded!", len(files)),
})
})
r.Run(":9090")
}
HTTP 重定向很容易。 内部、外部重定向均支持。
func main() {
r := gin.Default()
r.GET("/red", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})
r.Run(":9092")
}
路由重定向,使用HandleContext:
r.GET("/test", func(c *gin.Context) {
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message" : "route redirect successful",
})
})
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
此外,还有一个可以匹配所有请求方法的Any方法如下:
r.Any("/test", func(c *gin.Context) {...})
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "views/404.html", nil)
})
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。
func main() {
r := gin.Default()
userGroup := r.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
r.Run()
}
路由组也是支持嵌套的,例如:
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
// 嵌套路由组
xx := shopGroup.Group("xx")
xx.GET("/oo", func(c *gin.Context) {...})
}
通常我们将路由分组用在划分业务逻辑或划分API版本时。
Gin框架中的路由使用的是httprouter这个库。
其基本原理就是构造一个路由地址的前缀树。
我们可以在多个端口启动服务,例如:
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 {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
参考资料:
https://www.liwenzhou.com/posts/Go/Gin_framework/