使用 Go 搭建 Web 后端,最简单的就是使用原生的 net/http
库了,下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "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
使用起来特别方便(真的很方便!),并且支持中间件,异常处理,路由组等等,下面用代码演示一下如何使用吧。
快速入门 首先我们需要下载gin,我们要保证设置了 GO111MODULE=on
,然后进行下载
1 2 go mod init gin-demo go get -u github.com/gin-gonic/gin
下面是一个简单的web服务,我们使用 gin.Default()
创建一个默认的路由引擎,然后使用 GET
方式注册一个路由,负责接受来自客户端的 GET
请求,并且返回一个 JSON
数据给客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "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() }
使用curl进行测试结果如下,符合预期
1 2 $ curl http://localhost:8080/ping {"message" :"pong" }
使用HTTP方法 1 2 3 4 5 6 7 8 9 10 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
方法进行创建,路由组可以使得路由结构更加清晰
1 2 3 4 5 6 7 8 9 router := gin.Default() 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{}
的简写,最后会被序列化返回给客户端。
1 2 3 4 5 6 router := gin.Default() router.GET("/ping" , func (c *gin.Context) { c.JSON(200 , gin.H{ "message" : "pong" , }) })
返回HTML内容 首先需要加载 HTML
模板文件,然后使用 c.HTML
方法将数据注入到指定的模板文件中
1 2 3 4 5 6 7 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
1 2 3 4 5 6 7 {{ define "posts/index.tmpl" }} <html><h1> {{ .title }} </h1> <p>Using posts/index.tmpl</p> </html> {{ end }}
返回字符串 String方法即可
1 2 3 router.GET("String" , func (c *gin.Context) { c.String(http.StatusOK, "Hello World" ) })
获取查询参数 对于简单的 query
参数,使用 GetQuery
和 DefaultQuery
方法就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 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" , }) } })
1 2 3 4 5 $ 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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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" , }) } })
1 2 3 4 5 $ 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" }}
获取表单内容 1 2 3 4 5 6 7 8 9 10 router.POST("/form" , func (c *gin.Context) { name := c.PostForm("name" ) list := c.PostFormArray("list" ) m := c.PostFormMap("map" ) c.JSON(http.StatusOK, gin.H{ "name" : name, "list" : list, "map" : m, }) })
1 2 $ 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" }
文件上传 单文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 router := gin.Default() router.MaxMultipartMemory = 8 << 20 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" , }) })
多文件 1 2 3 4 5 6 7 8 9 10 11 router.POST("/upload/multiple" , func (c *gin.Context) { form, _ := c.MultipartForm() files := form.File["upload[]" ] for _, file := range files { log.Println(file.Filename) c.SaveUploadedFile(file, dst) }
使用中间件 中间件可以对经过一个路由的请求进行预处理,比如说鉴权,记录日志等等, gin
中创建一个中间件十分简单,我们只需要返回一个 gin.Handler
的类型就好了。下面就创建了一个简单的中间件来计算路由花费的时间
1 2 3 4 5 6 7 8 func Cost () gin.HandlerFunc{ return func (c *gin.Context) { start := time.Now() c.Next() fmt.Println("total time: " , time.Now().Sub(start)) } }
使用中间件有多种方式,一种是全局中间件,一种是路由组中间件,一种单个路由中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 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
为例讲解如何使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type LoginReq struct { 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
还有很多方面的特性,这里只是简单的介绍了如何去使用,更多的使用方法可以参考文档 ,个人之前也用过写了一个小项目 。