使用Go搭建Web后端,最简单的就是使用标准的net/http
库了,下面是一个简单的例子
package main
import (
"fmt"
"net/http"
)
func greet(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World!")
}
func main() {
http.HandleFunc("/", greet)
http.ListenAndServe(":8080", nil)
}
运行代码之后,便会在8080端口处监听,接收客户端的请求,然后返回响应,访问http://localhost:8080
即可以访问到对应的内容
对于简单的服务器,使用标准库提供的函数就已经可以解决了,如果更加复杂一点,使用标准库处理就显得力不从心了,这时候我们可以使用网上提供的框架,比如Gin
。
Gin
使用起来特别方便(真的很方便!),并且支持中间件,异常处理,路由组等等,下面用code
演示一下如何使用吧。
快速入门
首先我们需要下载gin,我们要保证设置了GO111MODULE=on
,然后进行下载
go mod init gin-demo
go get -u github.com/gin-gonic/gin
下面是一个简单的web服务,我们使用gin.Default()
创建一个默认的路由引擎,然后使用GET
方式注册一个路由,负责接受来自客户端的GET
请求,并且返回一个JSON
数据给客户端。
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
使用curl进行测试结果如下,符合预期
$ curl http://localhost:8080/ping
{"message":"pong"}
使用HTTP方法
// 使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
router := gin.Default()
router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
使用路由组
使用Group
方法进行创建,路由组可以使得路由结构更加清晰,用大括号包围其实没有其他含义,主要是为了显示这部分路由属于该路由组
router := gin.Default()
// 简单的路由组: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
返回内容
返回JSON内容
gin.Context
提供了JSON
方法, 我们可以直接使用, 第一个参数为返回的状态码,第二个参数为返回的数据,gin.H
其实就是map[string]interface{}
的简写,最后会被序列化返回给客户端。
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
返回HTML内容
首先需要加载HTML
模板文件,然后使用c.HTML
方法将数据注入到指定的模板文件中
router := gin.Default()
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "Posts",
})
})
模板文件posts/index.tmpl
{{ define "posts/index.tmpl" }}
<html><h1>
{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}
返回字符串
String方法即可
router.GET("String", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World")
})
获取查询参数
对于简单的query
参数,使用GetQuery
和DefaultQuery
方法就好了
router.GET("/query", func(c *gin.Context) {
q, exist := c.GetQuery("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})
$ curl http://localhost:8080/query
{"code":400,"message":"q is not exist"}
$ curl http://localhost:8080/query?q=hello
{"data":"hello"}
不过如果需要获取到的query
参数是数组或者map,那么就需要使用其他的方式了,比如数组需要使用GetQueryArray
or QueryArray
和 GetQueryMap
or QueryMap
。
r.GET("/query_array", func(c *gin.Context) {
q, exist := c.GetQueryArray("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})
r.GET("/query_map", func(c *gin.Context) {
q, exist := c.GetQueryMap("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})
$ curl "http://localhost:8080/query_array?q=1&q=2"
{"data":["1","2"]}
$ curl "http://localhost:8080/query_map?q[1]=1&q[2]=2"
{"data":{"1":"1","2":"2"}}
获取表单内容
router.POST("/form", func(c *gin.Context) {
name := c.PostForm("name") // or GetPostForm
list := c.PostFormArray("list") // or GetPostFormArray
m := c.PostFormMap("map") // or GetPostFormMap
c.JSON(http.StatusOK, gin.H{
"name": name,
"list": list,
"map": m,
})
})
$ curl --form name=test --form list=1 --form list=2 --form map[1]=1 --form map[2]=2 http://localhost:8080/form
{"list":["1","2"],"map":{"1":"1","2":"2"},"name":"test"}
文件上传
单文件
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload/single", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
fmt.Println(file.Filename)
// 上传文件至指定目录
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "upload success",
})
})
多文件
router.POST("/upload/multiple", 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)
}
使用中间件
中间件可以对经过一个路由的请求进行预处理,比如说鉴权,记录日志等等, gin
中创建一个中间件十分简单,我们只需要返回一个gin.Handler
的类型就好了。下面就创建了一个简单的中间件来计算路由花费的时间
func Cost() gin.HandlerFunc{
return func(c *gin.Context) {
start := time.Now()
// 一定要调用
c.Next()
fmt.Println("total time: ", time.Now().Sub(start))
}
}
使用中间件有多种方式,一种是全局中间件,一种是路由组中间件,一种单个路由中间件
router := gin.New() // 没有使用任何中间件的路由引擎
// 整个路由
router.Use(Cost())
// 路由组
group := r.Group("/api")
group.Use(Cost())
{
group.GET("/greet", greet)
group.POST("/form", form)
}
// 单个路由,不会影响其他路由
router.POST("/middleware", Cost(), controller)
绑定参数至结构体
在业务中,我们经常需要将用户发送过来的请求绑定到一个特定的结构体中,这样使用起来更加方便,gin
提供了大量的绑定方法,下面以ShouldBind
为例讲解如何使用:
// 首先声明需要绑定参数的结构体
type LoginReq struct {
// form 参数指定需要绑定的表单内容
// binding:"required" 表示不能非空,否则绑定的时候返回错误
// 如果该字段非必须,可以去掉该tag
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
r.POST("/login", func(c *gin.Context) {
var form LoginReq
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": -1,
"message": err.Error(),
})
return
}
fmt.Printf("%#v\n", form)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": form,
})
})
gin
还有很多方面的特性,这里只是简单的介绍了如何去使用,更多的使用方法可以参考文档[1],个人之前也用过写了一个 小项目[2]。
相关链接
文档: https://gin-gonic.com/docs/
[2]小项目: https://github.com/junhaideng/Golang-BBS




