golang通过 SOCKS5 进行 HTTP 请求转发的代码示例(含账号密码验证)

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

func main() {
	tr := &http.Transport{Dial: dial("socks5://192.168.16.22:1080?timeout=5s")}
	httpClient := &http.Client{Transport: tr}
	resp, err := httpClient.Get("https://www.google.com")
	if err != nil {
		log.Fatal(err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			panic(err)
		}
	}(resp.Body)
	if resp.StatusCode != http.StatusOK {
		log.Fatal(resp.StatusCode)
	}
	buf, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(buf))
}

type (
	Config struct {
		Proto   int
		Host    string
		Auth    *Auth
		Timeout time.Duration
	}
	Auth struct {
		Username string
		Password string
	}
)

func parse(proxyURI string) (*Config, error) {
	uri, err := url.Parse(proxyURI)
	if err != nil {
		return nil, err
	}
	cfg := &Config{}
	switch uri.Scheme {
	case "socks4":
		cfg.Proto = SOCKS4
	case "socks4a":
		cfg.Proto = SOCKS4A
	case "socks5":
		cfg.Proto = SOCKS5
	default:
		return nil, fmt.Errorf("unknown SOCKS protocol %s", uri.Scheme)
	}
	cfg.Host = uri.Host
	user := uri.User.Username()
	password, _ := uri.User.Password()
	if user != "" || password != "" {
		if user == "" || password == "" || len(user) > 255 || len(password) > 255 {
			return nil, errors.New("invalid user name or password")
		}
		cfg.Auth = &Auth{
			Username: user,
			Password: password,
		}
	}
	query := uri.Query()
	timeout := query.Get("timeout")
	if timeout != "" {
		var err error
		cfg.Timeout, err = time.ParseDuration(timeout)
		if err != nil {
			return nil, err
		}
	}
	return cfg, nil
}

type requestBuilder struct {
	bytes.Buffer
}

func (b *requestBuilder) add(data ...byte) {
	_, _ = b.Write(data)
}

func (config *Config) sendReceive(conn net.Conn, req []byte) (resp []byte, err error) {
	if config.Timeout > 0 {
		if err := conn.SetWriteDeadline(time.Now().Add(config.Timeout)); err != nil {
			return nil, err
		}
	}
	_, err = conn.Write(req)
	if err != nil {
		return
	}
	resp, err = config.readAll(conn)
	return
}

func (config *Config) readAll(conn net.Conn) (resp []byte, err error) {
	resp = make([]byte, 1024)
	if config.Timeout > 0 {
		if err := conn.SetReadDeadline(time.Now().Add(config.Timeout)); err != nil {
			return nil, err
		}
	}
	n, err := conn.Read(resp)
	resp = resp[:n]
	return
}

func lookupIPv4(host string) (net.IP, error) {
	ips, err := net.LookupIP(host)
	if err != nil {
		return nil, err
	}
	for _, ip := range ips {
		ipv4 := ip.To4()
		if ipv4 == nil {
			continue
		}
		return ipv4, nil
	}
	return nil, fmt.Errorf("no IPv4 address found for host: %s", host)
}

func splitHostPort(addr string) (host string, port uint16, err error) {
	host, portStr, err := net.SplitHostPort(addr)
	if err != nil {
		return "", 0, err
	}
	portInt, err := strconv.ParseUint(portStr, 10, 16)
	if err != nil {
		return "", 0, err
	}
	port = uint16(portInt)
	return
}

// Constants to choose which version of SOCKS protocol to use.
const (
	SOCKS4 = iota
	SOCKS4A
	SOCKS5
)

// Dial returns the dial function to be used in http.Transport object.
// Argument proxyURI should be in the format: "socks5://user:password@127.0.0.1:1080?timeout=5s".
// The protocol could be socks5, socks4 and socks4a.
func dial(proxyURI string) func(string, string) (net.Conn, error) {
	cfg, err := parse(proxyURI)
	if err != nil {
		return dialError(err)
	}
	return cfg.dialFunc()
}

func (config *Config) dialFunc() func(string, string) (net.Conn, error) {
	switch config.Proto {
	case SOCKS5:
		return func(_, targetAddr string) (conn net.Conn, err error) {
			return config.dialSocks5(targetAddr)
		}
	case SOCKS4, SOCKS4A:
		return func(_, targetAddr string) (conn net.Conn, err error) {
			return config.dialSocks4(targetAddr)
		}
	}
	return dialError(fmt.Errorf("unknown SOCKS protocol %v", config.Proto))
}

func dialError(err error) func(string, string) (net.Conn, error) {
	return func(_, _ string) (net.Conn, error) {
		return nil, err
	}
}

func (config *Config) dialSocks4(targetAddr string) (_ net.Conn, err error) {
	socksType := config.Proto
	proxy := config.Host

	// dial TCP
	conn, err := net.DialTimeout("tcp", proxy, config.Timeout)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			err := conn.Close()
			if err != nil {
				panic(err)
			}
		}
	}()

	// connection request
	host, port, err := splitHostPort(targetAddr)
	if err != nil {
		return nil, err
	}
	ip := net.IPv4(0, 0, 0, 1).To4()
	if socksType == SOCKS4 {
		ip, err = lookupIPv4(host)
		if err != nil {
			return nil, err
		}
	}
	req := []byte{
		4,                          // version number
		1,                          // command CONNECT
		byte(port >> 8),            // higher byte of destination port
		byte(port),                 // lower byte of destination port (big endian)
		ip[0], ip[1], ip[2], ip[3], // special invalid IP address to indicate the host name is provided
		0, // user id is empty, anonymous proxy only
	}
	if socksType == SOCKS4A {
		req = append(req, []byte(host+"\x00")...)
	}

	resp, err := config.sendReceive(conn, req)
	if err != nil {
		return nil, err
	} else if len(resp) != 8 {
		return nil, errors.New("server does not respond properly")
	}
	switch resp[1] {
	case 90:
		// request granted
	case 91:
		return nil, errors.New("socks connection request rejected or failed")
	case 92:
		return nil, errors.New("socks connection request rejected because SOCKS server cannot connect to identd on the client")
	case 93:
		return nil, errors.New("socks connection request rejected because the client program and identd report different user-ids")
	default:
		return nil, errors.New("socks connection request failed, unknown error")
	}
	// clear the deadline before returning
	if err := conn.SetDeadline(time.Time{}); err != nil {
		return nil, err
	}
	return conn, nil
}

func (config *Config) dialSocks5(targetAddr string) (_ net.Conn, err error) {
	proxy := config.Host

	// dial TCP
	conn, err := net.DialTimeout("tcp", proxy, config.Timeout)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			err := conn.Close()
			if err != nil {
				panic(err)
			}
		}
	}()

	var req requestBuilder

	version := byte(5) // socks version 5
	method := byte(0)  // method 0: no authentication (only anonymous access supported for now)
	if config.Auth != nil {
		method = 2 // method 2: username/password
	}

	// version identifier/method selection request
	req.add(
		version, // socks version
		1,       // number of methods
		method,
	)

	resp, err := config.sendReceive(conn, req.Bytes())
	if err != nil {
		return nil, err
	} else if len(resp) != 2 {
		return nil, errors.New("server does not respond properly")
	} else if resp[0] != 5 {
		return nil, errors.New("server does not support Socks 5")
	} else if resp[1] != method {
		return nil, errors.New("socks method negotiation failed")
	}
	if config.Auth != nil {
		version := byte(1) // user/password version 1
		req.Reset()
		req.add(
			version,                         // user/password version
			byte(len(config.Auth.Username)), // length of username
		)
		req.add([]byte(config.Auth.Username)...)
		req.add(byte(len(config.Auth.Password)))
		req.add([]byte(config.Auth.Password)...)
		resp, err := config.sendReceive(conn, req.Bytes())
		if err != nil {
			return nil, err
		} else if len(resp) != 2 {
			return nil, errors.New("server does not respond properly")
		} else if resp[0] != version {
			return nil, errors.New("server does not support user/password version 1")
		} else if resp[1] != 0 { // not success
			return nil, errors.New("user/password login failed")
		}
	}

	// detail request
	host, port, err := splitHostPort(targetAddr)
	if err != nil {
		return nil, err
	}
	req.Reset()
	req.add(
		5,               // version number
		1,               // connect command
		0,               // reserved, must be zero
		3,               // address type, 3 means domain name
		byte(len(host)), // address length
	)
	req.add([]byte(host)...)
	req.add(
		byte(port>>8), // higher byte of destination port
		byte(port),    // lower byte of destination port (big endian)
	)
	resp, err = config.sendReceive(conn, req.Bytes())
	if err != nil {
		return
	} else if len(resp) != 10 {
		return nil, errors.New("server does not respond properly")
	} else if resp[1] != 0 {
		return nil, errors.New("can't complete SOCKS5 connection")
	}

	return conn, nil
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用Golang发送HTTP Chunked请求的示例代码: ```go package main import ( "bytes" "fmt" "net/http" ) func main() { data := []byte("Hello, World!") // 创建一个字节缓冲区,用于存储Chunked数据 buffer := bytes.NewBuffer(nil) // 将数据块写入缓冲区 buffer.WriteString(fmt.Sprintf("%x\r\n", len(data))) // 写入数据块大小 buffer.Write(data) // 写入数据块内容 buffer.WriteString("\r\n") // 写入数据块结束标志 // 创建一个HTTP请求 req, err := http.NewRequest("POST", "http://example.com/api/endpoint", buffer) if err != nil { fmt.Println("创建请求失败:", err) return } // 设置请求头部,包括Transfer-Encoding和Content-Type req.Header.Set("Transfer-Encoding", "chunked") req.Header.Set("Content-Type", "text/plain") // 发送请求并获取响应 client := &http.Client{} resp, err := client.Do(req) if err != nil { fmt.Println("发送请求失败:", err) return } defer resp.Body.Close() // 处理响应 fmt.Println("响应状态码:", resp.StatusCode) // 其他处理逻辑... } ``` 在这个示例中,我们使用`bytes.Buffer`来创建一个缓冲区,然后将Chunked数据写入缓冲区。我们创建了一个`http.Request`对象并设置了相应的请求头部,然后使用`http.Client`发送请求。最后,我们处理了响应。 请注意,您需要根据实际情况修改目标URL、数据块内容和其他请求头部字段。将此示例代码保存为Go文件并运行它,您就可以发送Chunked请求了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值