基本介绍

HTTPClient支持强大的拦截器/中间件特性,该特性使得对于客户端的全局请求拦截及注入成为了可能,例如修改/注入提交参数、修改/注入返回参数、基于客户端的参数校验等等。中间件的注入通过以下方法实现:

  1. func (c *Client) Use(handlers ...HandlerFunc) *Client

在中间件中通过Next方法执行下一步流程,Next方法定义如下:

  1. func (c *Client) Next(req *http.Request) (*Response, error)

中间件类型

HTTPClient中间件功能同HTTPServer的中间件功能类似,同样也是分为了前置中间件和后置中间件两种。

前置中间件

处理逻辑位于Next方法之前,格式形如:

  1. c := g.Client()
  2. c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) {
  3. // 自定义处理逻辑
  4. resp, err = c.Next(r)
  5. return resp, err
  6. })

后置中间件

处理逻辑位于Next方法之后,格式形如:

  1. c := g.Client()
  2. c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.ClientResponse, err error) {
  3. resp, err = c.Next(r)
  4. // 自定义处理逻辑
  5. return resp, err
  6. })

使用示例

我们来一个代码示例更好介绍使用,该示例通过给客户端增加拦截器,对提交的JSON数据注入自定义的额外参数,这些额外参数实现对提交参数的签名生成体积签名相关参数提交,也就是实现一版简单的接口参数安全校验。

服务端

服务端的逻辑很简单,就是把客户端提交的JSON参数按照map解析后再构造成JSON字符串返回给客户端。

往往服务端也需要通过中间件进行签名校验,我这里偷了一个懒,直接返回了客户端提交的数据。体谅一下文档维护作者😸。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func main() {
  7. s := g.Server()
  8. s.Group("/", func(group *ghttp.RouterGroup) {
  9. group.ALL("/", func(r *ghttp.Request) {
  10. r.Response.Write(r.GetMap())
  11. })
  12. })
  13. s.SetPort(8199)
  14. s.Run()
  15. }

客户端

客户端的逻辑是实现基本的客户端参数提交、拦截器注入、签名相关参数注入以及签名参数生成。

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "github.com/gogf/gf/v2/container/garray"
  8. "github.com/gogf/gf/v2/crypto/gmd5"
  9. "github.com/gogf/gf/v2/frame/g"
  10. "github.com/gogf/gf/v2/internal/json"
  11. "github.com/gogf/gf/v2/net/gclient"
  12. "github.com/gogf/gf/v2/os/gctx"
  13. "github.com/gogf/gf/v2/os/gtime"
  14. "github.com/gogf/gf/v2/util/gconv"
  15. "github.com/gogf/gf/v2/util/guid"
  16. "github.com/gogf/gf/v2/util/gutil"
  17. )
  18. const (
  19. appId = "123"
  20. appSecret = "456"
  21. )
  22. // 注入统一的接口签名参数
  23. func injectSignature(jsonContent []byte) []byte {
  24. var m map[string]interface{}
  25. _ = json.Unmarshal(jsonContent, &m)
  26. if len(m) > 0 {
  27. m["appid"] = appId
  28. m["nonce"] = guid.S()
  29. m["timestamp"] = gtime.Timestamp()
  30. var (
  31. keyArray = garray.NewSortedStrArrayFrom(gutil.Keys(m))
  32. sigContent string
  33. )
  34. keyArray.Iterator(func(k int, v string) bool {
  35. sigContent += v
  36. sigContent += gconv.String(m[v])
  37. return true
  38. })
  39. m["signature"] = gmd5.MustEncryptString(gmd5.MustEncryptString(sigContent) + appSecret)
  40. jsonContent, _ = json.Marshal(m)
  41. }
  42. return jsonContent
  43. }
  44. func main() {
  45. c := g.Client()
  46. c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) {
  47. bodyBytes, _ := ioutil.ReadAll(r.Body)
  48. if len(bodyBytes) > 0 {
  49. // 注入签名相关参数,修改Request原有的提交参数
  50. bodyBytes = injectSignature(bodyBytes)
  51. r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
  52. r.ContentLength = int64(len(bodyBytes))
  53. }
  54. return c.Next(r)
  55. })
  56. content := c.ContentJson().PostContent(gctx.New(), "http://127.0.0.1:8199/", g.Map{
  57. "name": "goframe",
  58. "site": "https://goframe.org",
  59. })
  60. fmt.Println(content)
  61. }

运行测试

先运行服务端:

  1. $ go run server.go
  2. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  3. ----------|---------|---------|--------|-------|-------------------|-------------
  4. default | default | :8199 | ALL | / | main.main.func1.1 |
  5. ----------|---------|---------|--------|-------|-------------------|-------------
  6. 2021-05-18 09:23:41.865 97906: http server started listening on [:8199]

再运行客户端:

  1. $ go run client.go
  2. {"appid":"123","name":"goframe","nonce":"12vd8tx23l6cbfz9k59xehk1002pixfo","signature":"578a90b67bdc63d551d6a18635307ba2","site":"https://goframe.org","timestamp":1621301076}
  3. $

可以看到,服务端接受到的参数多了多了几项,包括appid/nonce/timestamp/signature,这些参数往往都是签名校验算法所需要的参数。