36

使用 Go 读写请求

 4 years ago
source link: https://www.tuicool.com/articles/vMNjQjq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简介

使用 web 框架, 最基础的事情就是读写请求了,

Gin 支持很多种类的请求参数, 也支持多种格式的响应.

读取请求参数

path 中的参数

使用 Param() 可以获取 path 中的参数.

定义在 path 中的参数有两种格式, 一个是 :name 的以冒号开头的,

另一种是 *action 的以星号开头的.

:name 是必定匹配的, 一定要有值, 不能为空. 下面的代码中, 第一个例子就是如此,

:name 来表示用户的名字, 这样就可以在路径中表示任意的用户名了.

*action 是可选的, 如果不存在, 就会忽略掉, 比如是可以匹配到 /user/john/ 的,

另外 /user/john 会被跳转到 /user/john/ .

// This handler will match /user/john but will not match /user/ or /user
router.GET("/user/:name", func(c *gin.Context) {
  name := c.Param("name")
  c.String(http.StatusOK, "Hello %s", name)
})

// However, this one will match /user/john/ and also /user/john/send
// If no other routers match /user/john, it will redirect to /user/john/
router.GET("/user/:name/*action", func(c *gin.Context) {
  name := c.Param("name")
  action := c.Param("action")
  message := name + " is " + action
  c.String(http.StatusOK, message)
})

query 中的参数

使用 Query()DefaultQuery() 可以获取 query 中的参数,

后者使用第二个参数作为默认值.

// Query string parameters are parsed using the existing underlying request object.
// The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
  firstname := c.DefaultQuery("firstname", "Guest")
  lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

  c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})

from 中的参数

对于 form (表单) 中的参数, 也有和 query 类似的方法, PostFormDefaultPostForm .

router.POST("/form_post", func(c *gin.Context) {
  message := c.PostForm("message")
  nick := c.DefaultPostForm("nick", "anonymous")

  c.JSON(200, gin.H{
    "status":  "posted",
    "message": message,
    "nick":    nick,
  })
})

模型绑定

上面的几种获取参数的方式都比较常规, 我觉得最有用的就是 模型绑定 了.

模型绑定首先要定义一个结构体 struct, struct 需要设置相应的 tag, 就是那些在反引号 ` 里面的字段,

然后就可以用对应的数据填充这个 struct 了, 也就是绑定.

// 绑定 JSON
type Login struct {
    User     string `form:"user" json:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

// 绑定 JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
  var json Login
  if err := c.ShouldBindJSON(&json); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }

  if json.User != "manu" || json.Password != "123" {
    c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})

// 绑定 XML (
//    <?xml version="1.0" encoding="UTF-8"?>
//    <root>
//        <user>user</user>
//        <password>123</password>
//    </root>)
router.POST("/loginXML", func(c *gin.Context) {
  var xml Login
  if err := c.ShouldBindXML(&xml); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }

  if xml.User != "manu" || xml.Password != "123" {
    c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})

// 绑定 HTML 表单 (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
  var form Login
  // 根据 Content-Type Header 推断使用哪个绑定器。
  if err := c.ShouldBind(&form); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }

  if form.User != "manu" || form.Password != "123" {
    c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
    return
  }

  c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})

上面的代码显示了三种不同的绑定, 分别是绑定 JSON 格式的请求体, XML 格式的请求体和普通的表单.

对于 query 也是使用 form tag 进行标记.

另外也可以绑定 Header (使用 header tag) 和 Uri (使用 uri tag) 等.

返回响应

对于请求, 也有多种类型的数据响应格式, 支持 XML, JSON, YAML 和 ProtoBuf.

func main() {
    r := gin.Default()

    // gin.H is a shortcut for 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) {
        // You also can use a struct
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "Lena"
        msg.Message = "hey"
        msg.Number = 123
        // Note that msg.Name becomes "user" in the JSON
        // Will output  :   {"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.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        label := "test"
        // The specific definition of protobuf is written in the testdata/protoexample file.
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        // Note that data becomes binary data in the response
        // Will output protoexample.Test protobuf serialized data
        c.ProtoBuf(http.StatusOK, data)
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

对于 API 服务来说, 这些已经足够用了, 主要还是用 JSON 格式的输出.

如果需要高性能, 可以使用 ProtoBuf, 但这不是人类易读的, 所以通常

来说 JSON 足以满足要求.

总结

主要介绍了如何使用 Gin 读取请求, 并返回响应.

这部分是 web 框架的基础, 框架的好用与否很大程度上

取决于这部分.

当前部分的代码

作为版本 v0.6.0


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK