6.1 Go之web框架gin
前言
近来计划用go做一个项目,在选哪个web框架上比较纠结。go的web框架有很多,近几年流行的以性能见长的有gin、iris、echo等,经过查阅资料,及咨询从事go开发的同仁,最终选择用gin框架做。
主要基于以下几点:
- 人多占优(无论从使用人数还是参与维护的人数),这一点很重要。用的人多,不能证明他很好,但起码不差。维护的人多有利于版本的迭代,不至于给自己将来挖坑。
- 文档支持相对比较好,入手成本低。
- gin拥有详尽的出错信息,极为方便调试。
- 采用轻量级的中间件式框架,把网站变成只有api的一个个小服务。
官方地址:https://github.com/gin-gonic/gin 详细文档地址:https://godoc.org/github.com/gin-gonic/gin
安装
下载安装,安装环境 centos7
go get -u github.com/gin-gonic/gin
前提是你已经 安装go。
创建main.go 创建目录
mkdir $GOPATH/src/test
cd $GOPATH/src/test
vim main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
运行
# run example.go and visit 0.0.0.0:8080/ping on browser
$ go run example.go
访问 ip:8080/ping 8080为默认端口,你也可以通过 r.Run(端口号)设置。
路由
示例
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/buildget", BuildGet)//BuildGet 方法,自己建
r.POST("/buildpost", BuildPost)
r.PUT("/buildput", BuildPut)
r.DELETE("/builddelete", BuildDelete)
r.PATCH("/buildpatch", BuildPatch)
r.HEAD("/buildhead", BuildHead)
r.OPTIONS("/buildoptions", BuildOptions)
r.Run(":8282")
}
带参数的路由
示例
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//访问方式路由必须为/buildget/{参数} 不可以为/buildget/或/buildget
r.GET("/buildget/:name", func(c *gin.Context) {
name := c.Param("name") //通过Param获取
c.String(http.StatusOK, "Hello world"+name)
})
//路由支持 /buildget/{参数}/ 或 /buildget/{参数}/{参数}
r.GET("/buildget/:name/*action", func(c *gin.Context) {
name := c.Param("name") //通过Param获取
action := c.Param("action")
c.String(http.StatusOK, "Hello world"+name+" "+action)
})
r.Run(":8282")
}
多路由
示例
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// Simple group: v1 访问方式 /v1/login
v1 := r.Group("/v1")
{
v1.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "v1 login")
})
}
// Simple group: v2 访问方式 /v2/login
v2 := r.Group("/v2")
{
v2.GET("/login", func(c *gin.Context) {
c.String(http.StatusOK, "v2 login")
})
}
r.Run(":8282")
}
参数
获取get方式路由的参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//访问实例 /login?param1=a¶m2=b&ids[1]=f&ids[2]=h
r.GET("/login", func(c *gin.Context) {
param1 := c.DefaultQuery("param1", "c") //设置初始值
param2 := c.Query("param2")
//param2 := c.Request.URL.Query().Get("param2")同上
ids := c.QueryMap("ids") //ids数组
fmt.Println(ids)
c.String(http.StatusOK, "Hello world "+param1+"_"+param2)
})
r.Run(":8282")
}
获取post方式路由的参数
package main
import (
"github.com/gin-gonic/gin"
"fmt"
)
func main() {
r := gin.Default()
//访问实例 /login?param1=a¶m2=b
r.POST("/login", func(c *gin.Context) {
param1 := c.DefaultPostForm("param1", "c") //设置初始值
param2 := c.PostForm("param2")
param3,has :=c.GetPostForm("param2")
names := c.PostFormMap("names")//names 是数组
if !has {
}
fmt.Println( param1,param2,param3,names)
})
r.Run(":8282")
}
上传单个文件
package main
import (
"github.com/gin-gonic/gin"
"path"
"os"
"io"
"time"
)
func main() {
r := gin.Default()
//访问实例
r.POST("/upload", func(c *gin.Context) {
//处理上传文件
//根据当前时间生成目录
timestamp := time.Now().Unix()
tm2 := time.Unix(timestamp, 0)
//创建上传目录
uploadDir := "/images/" + tm2.Format("20060102") + "/"
os.MkdirAll(uploadDir, os.ModePerm)
file, header, err := c.Request.FormFile("file")
if err == nil {
filename := header.Filename
//扩展名
ext := path.Ext(header.Filename)
defer out.Close()
//生成文件名
filefullname := uploadDir + filename + ext
//fmt.Println(file, err, filename)
out, err := os.Create(filefullname)
if err != nil {
}
io.Copy(out, file)
或
//c.SaveUploadedFile(file,filefullname)
}
})
r.Run(":8282")
}
上传多个文件
package main
import (
"github.com/gin-gonic/gin"
"log"
)
func main() {
r := gin.Default()
// Set a lower memory limit for multipart forms (default is 32 MiB)
r.MaxMultipartMemory = 8 << 20 // 8 MiB
//访问实例
r.POST("/upload", func(c *gin.Context) {
//处理上传多个文件
//获取上传的文件组
form, _ := c.MultipartForm()
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
c.SaveUploadedFile(file,file.Filename)
}
})
r.Run(":8282")
}
Bind
很多时候客户端和服务端采用json进行通信,即无论返回的response还是提交的request,其content-type类型都是application/json
的格式。对于gin框架来说,我们就需要bind功能来解决这个问题。
示例
type User struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func Login(c *gin.Context) {
var u User
err :=c.BindJSON(&u)
fmt.Println(err)
fmt.Println(u)
}
先建一个结构体user,再使用BindJSON绑定,将request中的Body中的数据按照JSON格式解析到u结构体中。
需要注意:
- binding:”required” 字段对应的参数未必传没有会抛出错误,非banding的字段,对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。
- 结构体字段类型和所传参数类型要一致。
Bind的实现都在gin/binding
里面. 这些内置的Bind都实现了Binding
接口, 主要是Bind()
函数.
- context.BindJSON() 支持MIME为application/json的解析
- context.BindXML() 支持MIME为application/xml的解析
- context.BindYAML() 支持MIME为application/x-yaml的解析
- context.BindQuery() 只支持QueryString的解析, 和Query()函数一样
- context.BindUri() 只支持路由变量的解析
- Context.Bind() 支持所有的类型的解析, 这个函数尽量还是少用(当QueryString, PostForm, 路由变量在一块同时使用时会产生意想不到的效果), 目前测试Bind不支持路由变量的解析, Bind()函数的解析比较复杂, 这部分代码后面再看
针对content-type类型是application/json
的格式的数据,也可以通过以下方式获取。
示例
jsonstr, _ := ioutil.ReadAll(c.Request.Body)
fmt.Println(string(jsonstr))
var data map[string]interface{}
err := json.Unmarshal(jsonstr, &data)
if err != nil {
fmt.Println(err)
}
fmt.Println(data)
获取header、body信息
body,_ := ioutil.ReadAll(c.Request.Body)
fmt.Println(string(body))
for k,v :=range c.Request.Header {
fmt.Println(k,v)
}
获取ip
func GetClientIp(c *gin.Context) string{
ip:=c.ClientIP()
if ip=="::1" {
ip="127.0.0.1"
}
return ip
}
参考:
https://www.haohongfan.com/post/2019-01-21-gin-03/
http://www.okyes.me/2016/05/03/go-gin.html
https://www.jianshu.com/p/a31e4ee25305
links