之前在探究 golang 是如何实现重定向的 中提到了两个必要条件。

responseheaderlocationrequest类型
CheckRedirect

定义如下,

type Client struct {
	// ...

	// CheckRedirect specifies the policy for handling redirects.
	// If CheckRedirect is not nil, the client calls it before
	// following an HTTP redirect. The arguments req and via are
	// the upcoming request and the requests made already, oldest
	// first. If CheckRedirect returns an error, the Client's Get
	// method returns both the previous Response (with its Body
	// closed) and CheckRedirect's error (wrapped in a url.Error)
	// instead of issuing the Request req.
	// As a special case, if CheckRedirect returns ErrUseLastResponse,
	// then the most recent response is returned with its body
	// unclosed, along with a nil error.
	//
	// If CheckRedirect is nil, the Client uses its default policy,
	// which is to stop after 10 consecutive requests.
	CheckRedirect func(req *Request, via []*Request) error

  // ...
}
req *Requestreqeustvia []*Requestrequest
Checkredirecterror=nil
// ...
			if ref := refererForURL(reqs[len(reqs)-1].URL, req.URL); ref != "" {
				req.Header.Set("Referer", ref)
			}
			// golang 1.17.1 net/http/client.go #L691
			err = c.checkRedirect(req, reqs)

			// Sentinel error to let users select the
			// previous response, without closing its
			// body. See Issue 10069.
			// 特殊错误处理
			if err == ErrUseLastResponse {
				return resp, nil
			}

			// ...
			if err != nil {
				// Special case for Go 1 compatibility: return both the response
				// and an error if the CheckRedirect function failed.
				// See https://golang.org/issue/3795
				// The resp.Body has already been closed.
				ue := uerr(err)
				ue.(*url.Error).URL = loc
				return resp, ue
			}
// ...

重定向规则执行逻辑

// checkRedirect calls either the user's configured CheckRedirect
// function, or the default.
func (c *Client) checkRedirect(req *Request, via []*Request) error {
  // 1.
	fn := c.CheckRedirect
  // 2. 
	if fn == nil {
		fn = defaultCheckRedirect
	}
  // 3.
	return fn(req, via)
}
fnnil

禁止重定向规则

CheckRedirect = nilreturn err
// neverRedirect
func (c *Client) neverRedirect(req *Request, via []*Request) error {
  return errors.New("forbidden redirect")
}

默认重定向规则

默认重定向规则中, 只对重定向次数进行了检查。 超过 10次就返回一个错误。

func defaultCheckRedirect(req *Request, via []*Request) error {
	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

用户自定义重定向规则

首先 创建一个 创建重定向规则

  1. 修改重定向可以执行的次数
  2. 在重定向时, 为 header 添加新字段

func userCheckRedirect(req *http.Request, via []*http.Request) error {
  // 1. 只能执行3次重定向
	if len(via) >= 3 {
		return errors.New("stopped after 3 redirects")
	}

	// 2. 发生重定向时, 向 header 中加入 Hello
	req.Header.Set("Hello", "golang Redirect")
	return nil
}

其次, 创建 http client 时使用规则

	client := &http.Client{
		CheckRedirect: userCheckRedirect,
	}

测试

这里仅列出简单的代码片段, 完整代码在最后。

/ping2
	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping2`,
		http.NoBody,
	)
HeaderHello
/ping2
/ping1307/ping2
	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping1`,
		http.NoBody,
	)
userCheckRedirectHeaderHello/ping2

事实也是如此

/ping1HeaderHello/ping2

测试通过。

完整测试代码

server.go

package main

import (
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/tangx/ginbinder"
)

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

	r.POST("/ping1", handler)
	r.POST("/ping2", handler2)
	_ = r.Run(":80")
}

type Params struct {
	Hello string `header:""`
}

func handler(c *gin.Context) {
  // 参数检查
	_ = checkHeader(c)
  // 重定向到 /ping2
	c.Redirect(http.StatusTemporaryRedirect, "/ping2")
}

func handler2(c *gin.Context) {
  // 参数检查
	p := checkHeader(c)
  // 展示结果
	c.JSON(http.StatusOK, p)
}

// checkHeader 获取并当前请求参数
func checkHeader(c *gin.Context) *Params {

	// 显示当前请求 URL 路径
	u := c.Request.URL
	fmt.Printf(`
	========in %s==========
	`, u.String())

	// 读取参数, 将 header 中的值绑定到 p 中
	p := &Params{}
	ginbinder.ShouldBindRequest(c, p)

  // 打印结果
	fmt.Printf("%+v\n", p)

	return p
}

client.go

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"testing"
)

// userCheckRedirect 用户自定义重定向检查规则
func userCheckRedirect(req *http.Request, via []*http.Request) error {
  // 允许 3 次重定向
	if len(via) >= 3 {
		return errors.New("stopped after 3 redirects")
	}

  // 为重定向的请求体添加新的 Header 字段
	req.Header.Set("Hello", "golang Redirect")
	return nil
}

// 重定向测试
func Test_POST(t *testing.T) {
	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping1`,
		http.NoBody, 
	)
	Panic(err)

  // 初始化 http client ,并使用用户自定义重定向检查方法
	client := &http.Client{
		CheckRedirect: userCheckRedirect,
	}

  // 发送请求
	resp, err := client.Do(req)
	Panic(err)
	defer resp.Body.Close()

  // 读取结果
	data, err := ioutil.ReadAll(resp.Body)
	Panic(err)
	fmt.Printf("%s\n", data)
}

// 错误处理
func Panic(err error) {
	if err != nil {
		panic(err)
	}
}