暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Go Web开发(Gin框架)简易入门教程

寻寻觅觅的Gopher 2021-06-25
1420

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(&params)
 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请求过程中发生的错误。
  • JSONGin可以解析并验证请求的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做了什么?

  1. 首先,我们使用了gin.Default()
    生成了一个实例,赋值给r
  2. 接下来,我们使用r.Get("/ping", ...)
    声明了一个路由,告诉 Gin 什么样的URL 能触发传入的函数,这个函数返回我们想要显示在用户浏览器中的信息。
  3. 最后用 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



文章转载自寻寻觅觅的Gopher,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论