net/http标准库
在讲述框架之前,先来说说Go语言的内置net/http包,其实net/http已经为我们提供了基础的路由函数组合和丰富的功能函数,如果你只是需要简单的API编写,net/http就完全足够了。一个简单的web服务程序:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 该方法接收一个路由匹配的字符串,以及一个 func(ResponseWriter, *Request) 类型的函数
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8000", nil)) // 监听本地8000端口
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) // r.URL.Path 输出url的路径
}
浏览器访问结果:

来进阶一下,看看如何解析常见的请求参数类型,以及如何返回json格式。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
func main() {
// 该方法接收一个路由匹配的字符串,以及一个 func(ResponseWriter, *Request) 类型的函数
http.HandleFunc("/", handler)
http.HandleFunc("/get", handleGet)
http.HandleFunc("/postJson", handlePostJson)
http.HandleFunc("/postForm", handlePostForm)
http.HandleFunc("/responseJson", handleResponseJson)
log.Fatal(http.ListenAndServe(":8000", nil)) // 监听本地8000端口
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) // r.URL.Path 输出url的路径
}
// 处理GET请求查询参数
func handleGet(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
id := query.Get("id")
fmt.Fprintf(w, "GET: id=%s\n", id)
}
// 处理application/json类型的POST请求
func handlePostJson(w http.ResponseWriter, r *http.Request) {
// 根据请求body创建一个json解析器实例
decoder := json.NewDecoder(r.Body)
// 用于存放参数key=value数据
var params map[string]string
// 解析参数 存入map
decoder.Decode(¶ms)
fmt.Fprintf(w, "POST json: username=%s, password=%s\n", params["username"], params["password"])
}
// 处理application/x-www-form-urlencoded类型的POST请求
func handlePostForm(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.Form.Get("username")
password := r.Form.Get("password")
fmt.Fprintf(w, "POST form-urlencoded: username=%s, password=%s\n", username, password)
}
// 返回JSON数据格式
func handleResponseJson(w http.ResponseWriter, r *http.Request) {
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
res := Response{
200,
"success",
"admin",
}
json.NewEncoder(w).Encode(res) // 关键
}
/get
请求结果:

/postJson
请求结果:

/postForm
请求结果:
/responseJson
请求结果:

关于更多net/http
标准库的内容可以直接查看标准库文档:https://studygolang.com/pkgdoc
gin框架
引入官方的介绍:Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter
,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。
特性:
快速:路由不使用反射,基于Radix树,内存占用少。 中间件:HTTP请求,可先经过一系列中间件处理,例如:Logger,Authorization,GZIP等。这个特性和 NodeJs 的 Koa
框架很像。中间件机制也极大地提高了框架的可扩展性。异常处理:服务始终可用,不会宕机。Gin 可以捕获 panic,并恢复。而且有极为便利的机制处理HTTP请求过程中发生的错误。 JSON:Gin可以解析并验证请求的JSON。这个特性对 Restful API
的开发尤其有用。路由分组:例如将需要授权和不需要授权的API分组,不同版本的API分组。而且分组可嵌套,且性能不受影响。 渲染内置:原生支持JSON,XML和HTML的渲染。
1.快速开始
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() // 默认监听本地8080端口,如果需要更改可以使用 r.Run(":9000")
}
浏览器访问:
控制台输出:
在这段代码中gin做了什么?
首先,我们使用了 gin.Default()
生成了一个实例,赋值给r
。接下来,我们使用 r.Get("/ping", ...)
声明了一个路由,告诉 Gin 什么样的URL 能触发传入的函数,这个函数返回我们想要显示在用户浏览器中的信息。最后用 r.Run()
函数来让应用运行在本地服务器上,默认监听端口是 8080,可以传入参数设置端口,例如r.Run(":9999")
即运行在9999端口。
2.路由
路由方法有 GET
, POST
, PUT
, PATCH
, DELETE
和 OPTIONS
,还有Any
,可匹配以上任意类型的请求。
r.GET("/someGet", func)
r.POST("/somePost", func)
r.PUT("/somePut", func)
r.DELETE("/someDelete", func)
r.PATCH("/somePatch", func)
r.HEAD("/someHead", func)
r.OPTIONS("/someOptions", func)
//处理所有的请求方法
r.Any("/any", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"请求类型": c.Request.Method,
})
})
路由参数
// 无参数
r.GET("/", func (c *gin.Context) {
c.String(http.StatusOK, "Hello")
})
// 路径参数,匹配 /path/admin
r.GET("/path/:name", func(c *gin.Context) {
name := c.Param("name")//取得URL路径中参数name的值
c.String(http.StatusOK, "Hello %s", name)
})
// 星号路由参数,匹配所有,不建议使用,如/all/*id
r.GET("/all/*id", func(c *gin.Context) {
id := c.Param("id")
c.String(http.StatusOK, "id is %s", id)
})
// 查询参数,匹配 user?name=xxx&role=xxx,role可选
r.GET("/user", func(c *gin.Context) {
name := c.Query("name")//查询请求URL后面的参数name的值
role := c.DefaultQuery("role", "teacher")//如果获取不到,会赋值默认值"teacher"
c.String(http.StatusOK, "%s is a %s", name, role)
})
// form表单
r.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "000000") // 可设置默认值
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
})
})
// json参数
r.POST("/json", func(c *gin.Context) {
type Body struct {
Email string `json:"email"`
Username string `json:"username"`
}
var body Body
c.ShouldBind(&body)//绑定参数,将参数解析到body结构体中
c.JSON(http.StatusOK, body)
})
// 数组参数,匹配多选业务如 array?answer=xxx&answer=xxx&answer=xxx,key一样,value不同
r.GET("/array", func(c *gin.Context) {
array := c.QueryArray("answer")
c.JSON(http.StatusOK, array)
})
// map参数,字典参数,匹配 map?ids[a]=123&ids[b]=456&ids[c]=789
r.GET("/map", func(c *gin.Context) {
c.JSON(http.StatusOK, c.QueryMap("ids"))
})
路由分组
v1Group := r.Group("/v1")
{
v1Group.GET("/user", func(c *gin.Context) {
c.String(200, "这是v1版本/v1/user")
})
}
v2Group := r.Group("/v2")
{
v2Group.GET("/user", func(c *gin.Context) {
c.String(200, "这是v2版本/v2/user")
})
}
3.输出渲染格式
gin可以很方便的渲染输出数据的格式
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/someString", func(c *gin.Context) {
c.String(http.StatusOK, "string")
})
// gin.H 是 map[string]interface{} 的一种快捷方式
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 你也可以使用一个结构体
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// 注意由于`json:"user"`的关系 msg.Name 在 JSON 中变成了 "user"
// 将输出:{"user": "Lena", "Message": "hey", "Number": 123}
c.JSON(http.StatusOK, msg)
})
r.GET("/someXML", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.Run()
}
4.中间件
回到快速开始中,我们使用gin.Default()
生成了一个实例,查看gin.Default()
的源码,可以发现
Default
函数会默认绑定两个已经准备好的中间件,Logger
和 Recovery
,分别帮助我们打印日志输出和painc处理。从中可以看到,Gin的中间件是通过Use
方法设置的,它接收一个可变参数,所以我们同时可以设置多个中间件。
而一个Gin的中间件,其实就是Gin定义的一个HandlerFunc
它在我们Gin中经常使用,比如:
// 其中func(c *gin.Context){} 就是一个HandlerFunc类型函数
r.GET("/", func(c *gin.Context) {
c.String(200, "hello")
})
我们现在来尝试如何使用中间件:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 创建一个不包含中间件的路由器实例
r := gin.New()
// 全局使用中间件
// 使用gin自带的 Logger 日志中间件
r.Use(gin.Logger())
// 使用gin自带的 Recovery 中间件从任何 panic 恢复,如果出现 panic,它会写一个 500 错误。
r.Use(gin.Recovery())
// 以上代码相当于 r := gin.Default()
// 添加自定义的全局中间件
r.Use(middleware.Cors())
// 单个路由添加中间件,可以添加任意多个
r.GET("/path", middleware.JWT())
// 路由组中添加中间件,中间件只在该路由组中产生作用
// user := r.Group("/user", middleware.AuthRequired())
user := r.Group("/user")
user.Use(middleware.AuthRequired())
{
user.POST("/login", Login)
}
r.Run()
}
学会使用中间件后,如何自定义一个自己的中间件呢?很简单,比如想要自定义一个计算请求时间的requestTime
中间件:
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(requestTime())
r.GET("/", func(c *gin.Context) {
fmt.Println("我到这里了 hello")
c.String(200, "hello")
})
r.Run()
}
func requestTime() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now() // 记录开始时间
fmt.Println("这里被我拦截住了")
fmt.Println("因为调用了c.Next() 我现在要走了")
c.Next() // 立即调用下一个HandlerFunc(会产生调用耗时)
fmt.Println("我又回来了")
fmt.Println(time.Since(start)) // 打印本次请求处理时间差
}
}
可以看到c.Next()
在这里发挥了很大的作用,其中在c.Next()
之前的操作我们一般用来做验证处理,访问是否允许之类的。之后的操作一般就是用来做总结处理,比如格式化输出、响应结束时间,响应时长计算之类的。
在gin中间件中,除了c.Next()
,相对应的还有c.Abort()
,而c.Abort()
的作用则是阻止后续的处理函数,比如检验到是非法请求时,阻断接下来的操作。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(authority())
r.GET("/path/:name", func(c *gin.Context) {
fmt.Println("欢迎")
c.String(http.StatusOK, "Hello %s", c.Param("name"))
})
r.Run()
}
func authority() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("开始-权限控制")
isAdmin := c.Param("name") == "admin"
if isAdmin {
c.Next()
} else {
c.Abort() //不会再执行接下去的HandlerFunc了
c.String(http.StatusOK, "对不起,您不是管理员")
}
fmt.Println("结束-权限控制")
}
}
情况①:

情况②:

程序在调用了c.Abort()
之后,就只会继续往下执行中间件的代码,不会再跳到我们定义的接口HandlerFunc去了,其实就是起到了拦截的作用。
总结一下c.Next()
作用是立即执行下一个HandlerFunc,完后会跳转回来继续执行c.Next()
接下去的代码,
而c.Abort()
则是阻断执行下一个HandlerFunc,仅会执行接下去的代码,常用于权限控制拦截操作。
有时候我们还需要跨中间件取值,可以使用c.Set(key, value)
设置值,使用c.Get(key)
取值
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(authority())
r.GET("/", func(c *gin.Context) {
value, ok := c.Get("key")
if ok {
fmt.Println(value)
}
c.String(http.StatusOK, "Hello")
})
r.Run()
}
func authority() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("key", "你好")
c.Next()
}
}
请求结果:
关于更多gin的信息可以参考文档:https://learnku.com/docs/gin-gonic/2019





