Golang中请求头如何规范化为标准格式
Golang中请求头如何规范化为标准格式
我正在尝试向一个URL发送POST请求,并且需要确保请求头的字母大小写匹配。例如,我有一个名为HealthCheck的请求头,但当我发送POST请求时,服务器接收到的键名是Healthcheck,其中的字母C变成了小写。
我尝试了:
req.Header.Set("HealthCheck", "Test")
和
req.Header.Add("HealthCheck", "Test")
以及
req.Header["HealthCheck"] = []string{"Test"}
但我得到的错误是缺少HealthCheck请求头。服务器端接收到的请求头是Healthcheck,而他们需要请求头严格按照他们指定的大小写格式。
我知道Go会将请求头转换为其规范形式,但是否有办法避免这种情况?我要向其发送请求的服务器拥有数百万用户,他们不会在他们那一端做任何更改。我必须为此想出一个解决方案。
另外,我可以轻松地用Python发出请求,并且请求头能保持我想要的格式。
提前感谢。
更多关于Golang中请求头如何规范化为标准格式的实战教程也可以访问 https://www.itying.com/category-94-b0.html
另一个话题,标准规定请求头是不区分大小写的。所以我认为问题出在服务器端,因为它似乎在进行区分大小写的比较。
更多关于Golang中请求头如何规范化为标准格式的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中,http.Header确实会自动将请求头键名转换为规范格式(首字母和连字符后字母大写)。要绕过这个限制,你需要使用底层的HTTP传输层直接写入原始请求头。以下是解决方案:
package main
import (
"fmt"
"io"
"net/http"
"net/textproto"
)
func main() {
// 创建自定义Transport
tr := &http.Transport{}
client := &http.Client{Transport: tr}
req, err := http.NewRequest("POST", "https://example.com/api", nil)
if err != nil {
panic(err)
}
// 设置请求体(如果需要)
// req.Body = io.NopCloser(strings.NewReader("request body"))
// 直接操作req.Header的底层map来避免规范化
// 使用textproto.CanonicalMIMEHeaderKey的逆操作
rawHeaders := make(textproto.MIMEHeader)
rawHeaders["HealthCheck"] = []string{"Test"} // 保持原始大小写
// 将原始请求头复制到请求中
for k, v := range rawHeaders {
req.Header[k] = v
}
// 使用自定义的RoundTripper来保持请求头格式
client.Transport = &customTransport{
originalHeaders: rawHeaders,
base: tr,
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
// 自定义Transport来保持请求头格式
type customTransport struct {
originalHeaders textproto.MIMEHeader
base http.RoundTripper
}
func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// 清除可能被规范化的请求头
for k := range req.Header {
delete(req.Header, k)
}
// 重新设置原始请求头
for k, v := range t.originalHeaders {
req.Header[k] = v
}
if t.base == nil {
t.base = http.DefaultTransport
}
return t.base.RoundTrip(req)
}
或者,使用更直接的方法通过控制TCP连接:
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
func sendRawHTTPRequest() {
conn, err := net.Dial("tcp", "example.com:80")
if err != nil {
panic(err)
}
defer conn.Close()
// 手动构造HTTP请求
request := strings.Join([]string{
"POST /api HTTP/1.1",
"Host: example.com",
"HealthCheck: Test", // 保持原始大小写
"Content-Type: application/json",
"Content-Length: 15",
"",
`{"key":"value"}`,
}, "\r\n")
fmt.Fprintf(conn, request)
// 读取响应
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Print(line)
}
}
func main() {
sendRawHTTPRequest()
}
对于HTTPS请求,可以使用tls.Dial:
package main
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
)
type rawHTTPClient struct {
Transport http.RoundTripper
}
func (c *rawHTTPClient) Do(req *http.Request, rawHeaders map[string]string) (*http.Response, error) {
// 创建原始TCP/TLS连接
var conn net.Conn
var err error
if req.URL.Scheme == "https" {
conn, err = tls.Dial("tcp", req.URL.Host+":443", &tls.Config{})
} else {
conn, err = net.Dial("tcp", req.URL.Host+":80")
}
if err != nil {
return nil, err
}
defer conn.Close()
// 构造原始HTTP请求
var request strings.Builder
request.WriteString(fmt.Sprintf("%s %s HTTP/1.1\r\n", req.Method, req.URL.RequestURI()))
request.WriteString(fmt.Sprintf("Host: %s\r\n", req.URL.Host))
// 添加原始请求头(保持大小写)
for k, v := range rawHeaders {
request.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
}
// 添加必要的请求头
if req.Body != nil {
// 这里需要处理请求体
}
request.WriteString("\r\n")
// 发送请求
conn.Write([]byte(request.String()))
// 解析响应
return http.ReadResponse(bufio.NewReader(conn), req)
}
func main() {
client := &rawHTTPClient{}
req, _ := http.NewRequest("POST", "https://example.com/api", nil)
rawHeaders := map[string]string{
"HealthCheck": "Test",
"Custom-Header": "Value",
}
resp, err := client.Do(req, rawHeaders)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Printf("Status: %s\n", resp.Status)
}
这些方法可以绕过Go的请求头规范化,保持请求头的大小写格式。第一种方法使用自定义Transport更符合Go的惯用法,第二种方法提供更底层的控制。


