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

4 回复

另一个话题,标准规定请求头是不区分大小写的。所以我认为问题出在服务器端,因为它似乎在进行区分大小写的比较。

更多关于Golang中请求头如何规范化为标准格式的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


官方文档所述,你应该:

要使用非规范键,请直接分配给映射。

最后一种方法不起作用吗?

感谢你的帮助,Massimo! 关于这两点,你说得都对!第一,只要我使用映射来分配所有的头部键,这种方法就能正常工作。 关于服务器端实现错误的问题,你也说对了。我试着告诉过他们,但他们似乎并不在意。

感谢你的帮助! 干杯

在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的惯用法,第二种方法提供更底层的控制。

回到顶部