前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang http.Client 为什么传入文件描述符就无法重定向

golang http.Client 为什么传入文件描述符就无法重定向

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

本文基于 golang 1.17.1 的 net/http

在使用 对象存储 的时候遇到一个问题, gin 在使用重定向的时候

代码语言:javascript
复制
c.Redirect(307, "http://s3.example.com/path/2/object")

请求体中的 io.Reader 参数

  • 使用 文件描述符 作为参数, 能发送原始请求, 但不能发起重定向请求
  • 将文件内容读取出来, 放在 bytes.Buffer 中, 就能事项 重定向

跟踪 http/net 包源代码后, 疑惑得到了解决。

注意: 跟着高亮跳转

307 / 308 的必要条件

跟随 http.Client.Do 发送请求

找到, 发送第一次请求收到 response 后, http.Client 会判断是否跟随重定向。

点击跳转后, 可以看到 307 / 308 重定向有两个必要条件。

  • 其一, resp header 中必须有 Location 字段, 指定 下一条 的目的地。
  • 其二, 本次 请求体中, 必须有 GetBody 方法,且outgoingLength (请求体) 长度不为 0。

outgoingLength 要求

  • request body 不能为 nil 或者 NoBody 空请求体
  • 请求体长度不能等于 0

因此是否能执行跳转, 还是从本身上来说,就需要药依赖 request 是否满足重定向的条件

Request 请求体的定义

那么, request 请求体中的 GetBody 方法 和 ContentLength 怎么来呢?

点击跟随后,

ContentLength

找到 body 不为 nil 的地方, 可以看到有 支持 3 种 实现了 io.Reader 的 类型 可以改变 ContentLength

  1. *bytes.Buffer
  2. *bytes.Reader
  3. *strings.Reader

注意, default 分支里面什么代码都没有,但有说 设置了什么值为 -1 , 返回前面, 可以看到 req.outgoingLength() 的默认值就是 -1

req.GetBody

同样的, 在每个 case 分支中, 也设置各类 reader 类型的 GetBody 方法。而这正是

对去其他非支持的类型, 则返回了一个 NoBody

这里可以看到, 由于返回的 NoBodyhttp.Client 在 重定向请求检查 不会通过。

总结

Golang 实现重定向的必要条件

  • request body 是所支持的三种类型之一
  • response 中含有 LocationHeader

因此

*os.File 实现了 io.Reader 接口, 所以能正常执行第一次请求。也能获取含有 Location 的 header。

但是 *os.File 不是 net/http.Client 默认支持的 重定向 的 body 类型。

原始代码如下

main.go

代码语言:javascript
复制
// main.go

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"

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

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

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

type Params struct {
	Body struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	} `body:"" mime:"json"`
}

func handler(c *gin.Context) {

	data, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		panic(err)
	}

	fmt.Printf("======= ping1 ========\n%s \n", data)

	c.Redirect(http.StatusTemporaryRedirect, "/ping2")
}

func handler2(c *gin.Context) {
	data, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		panic(err)
	}

	fmt.Printf("======= ping2 ========\n%s \n", data)
}

client.go

代码语言:javascript
复制
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"testing"
)

func Test_put(t *testing.T) {
	f, err := os.Open("go.mod") // 创建文件句柄对象
	Panic(err)
	defer f.Close()
	// body, _ := ioutil.ReadAll(f)

	req, err := http.NewRequest(
		"POST",
		`http://127.0.0.1:80/ping1`,
		// bytes.NewBuffer(body), // 问题在这里, 好像不能直接传 文件句柄 对象。
		f,
	)
	Panic(err)

	req.Header.Set("Content-Type", "multipart/form-data")

	proxyUrl, _ := url.Parse(`http://127.0.0.1:8080`)
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxyUrl),
		},
	}

	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-15,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 307 / 308 的必要条件
  • Request 请求体的定义
    • ContentLength
      • req.GetBody
      • 总结
      • 原始代码如下
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档