本文基于 golang 1.17.1 的
net/http
在使用 对象存储 的时候遇到一个问题, gin
在使用重定向的时候
c.Redirect(307, "http://s3.example.com/path/2/object")
请求体中的 io.Reader
参数
bytes.Buffer
中, 就能事项 重定向跟踪 http/net
包源代码后, 疑惑得到了解决。
注意: 跟着高亮跳转
跟随 http.Client.Do
发送请求
找到, 发送第一次请求收到 response 后, http.Client
会判断是否跟随重定向。
点击跳转后, 可以看到 307 / 308
重定向有两个必要条件。
Location
字段, 指定 下一条 的目的地。GetBody
方法,且outgoingLength (请求体)
长度不为 0。outgoingLength
要求
nil
或者 NoBody
空请求体0
因此是否能执行跳转, 还是从本身上来说,就需要药依赖 request 是否满足重定向的条件
那么, request
请求体中的 GetBody
方法 和 ContentLength
怎么来呢?
点击跟随后,
找到 body
不为 nil
的地方, 可以看到有 支持 3 种 实现了 io.Reader
的 类型 可以改变 ContentLength
*bytes.Buffer
*bytes.Reader
*strings.Reader
注意, default
分支里面什么代码都没有,但有说 设置了什么值为 -1
, 返回前面, 可以看到 req.outgoingLength()
的默认值就是 -1
。
同样的, 在每个 case
分支中, 也设置各类 reader
类型的 GetBody
方法。而这正是
对去其他非支持的类型, 则返回了一个 NoBody
这里可以看到, 由于返回的 NoBody
在 http.Client
在 重定向请求检查 不会通过。
Golang 实现重定向的必要条件
request
body 是所支持的三种类型之一response
中含有 Location
的 Header
因此
*os.File
实现了 io.Reader
接口, 所以能正常执行第一次请求。也能获取含有 Location
的 header。
但是 *os.File
不是 net/http.Client
默认支持的 重定向 的 body
类型。
main.go
// 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
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)
}
}