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
gin框架
httprouter
KoaRestful API

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()rr.Get("/ping", ...)r.Run()r.Run(":9999")

2.路由

GETPOSTPUTPATCHDELETEOPTIONSAny
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()DefaultLoggerRecoveryUseHandlerFunc
// 其中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()
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()
c.Next()c.Next()c.Abort()
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