前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang http.Client 用户自定义重定向策略

golang http.Client 用户自定义重定向策略

作者头像
老麦
发布2022-12-24 09:47:15
1.5K0
发布2022-12-24 09:47:15
举报
文章被收录于专栏:Go与云原生Go与云原生

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

  1. responseheaderlocation 字段
  2. request 所支持的特殊 类型

其实, 还有一个, CheckRedirect 重定向检查条件。只有满足重定向规则条件, 才能继续执行。

定义如下,

代码语言:javascript
复制
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

  // ...
}
  1. 其中 req *Request 是本次请求携带使用的 reqeust 请求体
  2. via []*Request 是之前重定向使用过的 request 请求体。

简单概括, 就是需要满足 Checkredirect 函数中的条件, 即 返回 error=nil

代码语言:javascript
复制
// ...
			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
			}
// ...

重定向规则执行逻辑

代码语言:javascript
复制
// 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)
}
  1. 将规则赋值给临时变量 fn ,后续规则不会影响到 req 本身。
  2. 如果 没有预设 或者 预设规则 为 nil , 则使用默认规则。
  3. 执行规则

禁止重定向规则

如果不想执行任何重定向,不是将 CheckRedirect = nil , 而是直接 return err

代码语言:javascript
复制
// neverRedirect
func (c *Client) neverRedirect(req *Request, via []*Request) error {
  return errors.New("forbidden redirect")
}

默认重定向规则

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

代码语言:javascript
复制
func defaultCheckRedirect(req *Request, via []*Request) error {
	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

用户自定义重定向规则

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

  1. 修改重定向可以执行的次数
  2. 在重定向时, 为 header 添加新字段
代码语言:javascript
复制

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 时使用规则

代码语言:javascript
复制
	client := &http.Client{
		CheckRedirect: userCheckRedirect,
	}

测试

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

直接请求 /ping2 接口

代码语言:javascript
复制
	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping2`,
		http.NoBody,
	)

无法获取到 Header 里面的 Hello 字段的

重定向到 /ping2 接口

将请求地址修改为 /ping1 并通过 307 重定向到 /ping2

代码语言:javascript
复制
	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping1`,
		http.NoBody,
	)

在重定向检查的时候, 调用了 userCheckRedirect , 应该在 Header 中加入 Hello 字段, 并在 /ping2 中被捕获。

事实也是如此

  • /ping1Header 没有 Hello 字段所以无法获取
  • 但是在 /ping2 中就正常拿到了。

测试通过。

完整测试代码

server.go

代码语言:javascript
复制
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

代码语言:javascript
复制
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)
	}
}

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 熊猫云原生Go 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 重定向规则执行逻辑
    • 禁止重定向规则
    • 默认重定向规则
    • 用户自定义重定向规则
      • 测试
        • 直接请求 /ping2 接口
          • 重定向到 /ping2 接口
          • 完整测试代码
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档