之前在探究 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
}
用户自定义重定向规则
首先 创建一个 创建重定向规则
- 修改重定向可以执行的次数
- 在重定向时, 为 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)
}
}