Introduction

HTTPClient supports a powerful interceptor/middleware feature, which makes global request interception and injection for the client possible, such as modifying/injecting submitted parameters, modifying/injecting returned parameters, client-based parameter validation, etc. Middleware injection is implemented through the following method:

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

In middleware, execute the next step of the process through the Next method, which is defined as follows:

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

Types of Middleware

The middleware feature in HTTPClient is similar to the middleware feature in HTTPServer, and is also divided into pre-middleware and post-middleware.

Pre-middleware

Processing logic is before the Next method, formatted as follows:

  1. c := g.Client()
  2. c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) {
  3. // Custom processing logic
  4. resp, err = c.Next(r)
  5. return resp, err
  6. })

Post-middleware

Processing logic is after the Next method, formatted as follows:

  1. c := g.Client()
  2. c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) {
  3. resp, err = c.Next(r)
  4. // Custom processing logic
  5. return resp, err
  6. })

Usage Example

Let’s use a code example to better illustrate usage. This example adds an interceptor to the client, injecting custom additional parameters into the submitted JSON data. These additional parameters implement signature generation for the submitted parameters, essentially achieving a simple API parameter security validation.

Server

The server logic is straightforward, it parses the client’s submitted JSON parameters as a map and then constructs a JSON string to return to the client.

HTTPClient - Middleware - 图1note

Often, the server also needs to perform signature validation through middleware, but here I’ve taken a shortcut and directly returned the data submitted by the client. Please understand the documentation maintainer😸.

  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. }

Client

The client logic implements basic client parameter submission, interceptor injection, signature-related parameter injection, and signature parameter generation.

  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. // Inject unified API signature parameters
  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. // Inject signature-related parameters, modify the original submission parameters of the 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. }

Run Test

First, run the server:

  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]

Then, run the client:

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

You can see that the server received parameters with several additional items, including appid/nonce/timestamp/signature, which are often parameters required by the signature verification algorithm.