Golang中如何使用HTTP CONNECT代理明文请求

Golang中如何使用HTTP CONNECT代理明文请求 我配置了一个仅接受HTTP CONNECT请求来代理到上游服务器的HTTP代理,其中一些上游服务器使用HTTPS,另一些使用HTTP。然而,https://golang.org/src/net/http/transport.go 中大约第1638行的行为导致HTTP CONNECT隧道仅在请求URL使用"https"方案时才被使用。我目前的代码在http.Transport结构体的Proxy字段上设置了通过我的代理服务器发送请求,对于使用"https"方案的请求,一切工作正常,但对于使用"http"方案的请求则不行。

由于HTTP CONNECT的目的是创建一个到上游服务器的TCP连接,然后通过该连接代理字节流,这对于HTTP和HTTPS上游服务器似乎同样有用,但我还没有找到使用http.Transport实现此行为的方法。我唯一的选择是复制http.Transport中的大部分实现吗?还是有办法可以发出信号,表明我希望始终使用HTTP CONNECT来代理这些请求?支持像"http+connect"和"https+connect"这样的方案似乎是一种在不破坏当前接口的情况下发出此信号的相当自然的方式。


更多关于Golang中如何使用HTTP CONNECT代理明文请求的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中如何使用HTTP CONNECT代理明文请求的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中,http.Transport确实默认只在HTTPS请求时使用CONNECT方法建立隧道。对于HTTP请求通过CONNECT代理,需要自定义DialContext来实现。以下是一种解决方案:

package main

import (
    "context"
    "fmt"
    "net"
    "net/http"
    "net/url"
    "time"
)

type connectDialer struct {
    proxyURL *url.URL
}

func (c *connectDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
    // 建立到代理服务器的连接
    conn, err := net.DialTimeout("tcp", c.proxyURL.Host, 10*time.Second)
    if err != nil {
        return nil, err
    }

    // 发送CONNECT请求建立隧道
    connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, addr)
    if _, err := conn.Write([]byte(connectReq)); err != nil {
        conn.Close()
        return nil, err
    }

    // 读取代理响应
    buf := make([]byte, 4096)
    n, err := conn.Read(buf)
    if err != nil {
        conn.Close()
        return nil, err
    }

    // 检查CONNECT响应是否成功(HTTP 200)
    response := string(buf[:n])
    if !contains(response, "200") {
        conn.Close()
        return nil, fmt.Errorf("proxy CONNECT failed: %s", response)
    }

    return conn, nil
}

func contains(s, substr string) bool {
    return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && s[:len(substr)] == substr)
}

func main() {
    proxyURL, _ := url.Parse("http://proxy.example.com:8080")
    
    transport := &http.Transport{
        Proxy: http.ProxyURL(proxyURL),
        DialContext: (&connectDialer{proxyURL: proxyURL}).DialContext,
    }
    
    client := &http.Client{
        Transport: transport,
        Timeout:   30 * time.Second,
    }

    // 测试HTTP请求
    resp, err := client.Get("http://httpbin.org/get")
    if err != nil {
        fmt.Printf("HTTP request failed: %v\n", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("HTTP request succeeded")

    // 测试HTTPS请求
    resp, err = client.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Printf("HTTPS request failed: %v\n", err)
        return
    }
    defer resp.Body.Close()
    fmt.Println("HTTPS request succeeded")
}

对于更复杂的场景,可以扩展connectDialer以支持代理认证:

type connectDialerWithAuth struct {
    proxyURL  *url.URL
    proxyUser *url.Userinfo
}

func (c *connectDialerWithAuth) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
    conn, err := net.DialTimeout("tcp", c.proxyURL.Host, 10*time.Second)
    if err != nil {
        return nil, err
    }

    // 构建带认证头的CONNECT请求
    connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n", addr, addr)
    
    if c.proxyUser != nil {
        username := c.proxyUser.Username()
        password, _ := c.proxyUser.Password()
        auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
        connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth)
    }
    
    connectReq += "\r\n"

    if _, err := conn.Write([]byte(connectReq)); err != nil {
        conn.Close()
        return nil, err
    }

    // 其余代码与上面相同...
    // 读取响应并验证
}

这种方法通过自定义DialContext绕过了http.Transport对HTTP请求不使用CONNECT的限制,确保所有请求都通过CONNECT隧道代理。

回到顶部