valyala / fasthttp

Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http
MIT License
21.94k stars 1.76k forks source link

How to set proxy and tcpdialer at the same time #1822

Closed 0xfocu5 closed 3 months ago

0xfocu5 commented 3 months ago
client = &fasthttp.Client{
        ReadTimeout:         time.Second * 5,
        WriteTimeout:        time.Second * 5,
        MaxIdleConnDuration: time.Second * 5,
        //Dial: (&fasthttp.TCPDialer{
        //  Concurrency:      0,
        //  DNSCacheDuration: time.Hour,
        //}).Dial,
        Dial: fasthttpproxy.FasthttpSocksDialer("socks5://127.0.0.1:7890")
    }

I want these work together

Dial: (&fasthttp.TCPDialer{
            Concurrency:      0,
            DNSCacheDuration: time.Hour,
        }).Dial,
Dial: fasthttpproxy.FasthttpSocksDialer("socks5://127.0.0.1:7890")
erikdubbelboer commented 3 months ago

fasthttpproxy.FasthttpSocksDialer is very simple and passes proxy.Direct to golang.org/x/net/proxy.FromURL which is what you want to change. I would copy the implementation of fasthttpproxy.FasthttpSocksDialer and try to pass a fasthttp.TCPDialer as second argument so it uses that instead if net.Dial.

0xfocu5 commented 3 months ago

fasthttpproxy.FasthttpSocksDialer is very simple and passes proxy.Direct to golang.org/x/net/proxy.FromURL which is what you want to change. I would copy the implementation of fasthttpproxy.FasthttpSocksDialer and try to pass a fasthttp.TCPDialer as second argument so it uses that instead if net.Dial.

Looking forward to your good news

newacorn commented 3 months ago

Implement based on fasthttpproxy

support socks5 and http.

package fasthttpproxy

import (
    "bufio"
    "encoding/base64"
    "errors"
    "fmt"
    "github.com/valyala/fasthttp"
    "golang.org/x/net/proxy"
    "net"
    "net/url"
    "strings"
    "time"
)

type DialerFunc func(network, addr string) (net.Conn, error)

func (d DialerFunc) Dial(network, addr string) (net.Conn, error) {
    return d(network, addr)
}

type HttpProxyDialer interface {
    ConnectTimeout() time.Duration
}

func (d *Dialer) ConnectTimeout() time.Duration {
    return d.connectTimeout
}

type Dialer struct {
    fasthttp.TCPDialer
    timeout        time.Duration
    connectTimeout time.Duration
}

func init() {
    proxy.RegisterDialerType("http", HttpProxyDial)
}
func (d *Dialer) Dial(network, addr string) (conn net.Conn, err error) {
    if network == "tcp4" {
        if d.timeout > 0 {
            return d.TCPDialer.DialTimeout(addr, d.timeout)
        }
        return d.TCPDialer.Dial(addr)
    }
    if network == "tcp" {
        if d.timeout > 0 {
            return d.TCPDialer.DialDualStackTimeout(addr, d.timeout)
        }
        return d.TCPDialer.DialDualStack(addr)
    }
    err = errors.New("don't know how to dial network:" + network)
    return
}

func (d *Dialer) GetDialFunc(proxyAddr string) (dialFunc fasthttp.DialFunc, err error) {
    u, err := url.Parse(proxyAddr)
    if err != nil {
        return
    }
    dialer, err := proxy.FromURL(u, d)
    if err != nil {
        return
    }
    dialFunc = func(addr string) (net.Conn, error) {
        var network string
        if strings.HasPrefix(addr, "[") {
            network = "tcp"
        } else {
            network = "tcp4"
        }
        return dialer.Dial(network, addr)
    }
    return
}
func HttpProxyDial(u *url.URL, dialer proxy.Dialer) (proxy.Dialer, error) {
    var proxyAddr string
    if u.Scheme != "" {
        proxyAddr = strings.TrimPrefix(u.String(), u.Scheme+"://")
    }
    var auth string
    if strings.Contains(proxyAddr, "@") {
        index := strings.LastIndex(proxyAddr, "@")
        auth = base64.StdEncoding.EncodeToString([]byte(proxyAddr[:index]))
        proxyAddr = proxyAddr[index+1:]
    }
    return DialerFunc(func(network, addr string) (conn net.Conn, err error) {
        conn, err = dialer.Dial(network, proxyAddr)
        if err != nil {
            return
        }
        var connectTimeout time.Duration
        hp, ok := dialer.(HttpProxyDialer)
        if ok {
            connectTimeout = hp.ConnectTimeout()
        }

        if connectTimeout > 0 {
            if err = conn.SetDeadline(time.Now().Add(connectTimeout)); err != nil {
                _ = conn.Close()
                return nil, err
            }
            defer func() {
                _ = conn.SetDeadline(time.Time{})
            }()
        }
        req := "CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n"
        if auth != "" {
            req += "Proxy-Authorization: Basic " + auth + "\r\n"
        }
        req += "\r\n"
        _, err = conn.Write([]byte(req))
        if err != nil {
            _ = conn.Close()
            return
        }
        res := fasthttp.AcquireResponse()
        defer fasthttp.ReleaseResponse(res)
        res.SkipBody = true
        if err = res.Read(bufio.NewReader(conn)); err != nil {
            _ = conn.Close()
            return
        }
        if res.Header.StatusCode() != 200 {
            _ = conn.Close()
            err = fmt.Errorf("could not connect to proxyAddr: %s status code: %d", proxyAddr, res.Header.StatusCode())
            return
        }
        return
    }), nil
}
func TestDialProxy(t *testing.T) {
    p := Dialer{
        timeout: time.Second * 30,
    }
    dialer, err := p.GetDialFunc("http://127.0.0.1:8005")
    if err != nil {
        t.Fatal(err)
    }
    client := &fasthttp.Client{
        ReadTimeout:         time.Second * 5,
        WriteTimeout:        time.Second * 5,
        MaxIdleConnDuration: time.Second * 5,
        Dial:                dialer,
    }
    statusCode, resp, err := client.Get(nil, "https://www.google.com")
    if err != nil {
        t.Fatal(err)
    }
    if statusCode != 200 {
        t.Fatal(err)
    }
    _ = resp
}
0xfocu5 commented 3 months ago

Implement based on fasthttpproxy

support socks5 and http.

package fasthttpproxy

import (
  "bufio"
  "encoding/base64"
  "errors"
  "fmt"
  "github.com/valyala/fasthttp"
  "golang.org/x/net/proxy"
  "net"
  "net/url"
  "strings"
  "time"
)

type DialerFunc func(network, addr string) (net.Conn, error)

func (d DialerFunc) Dial(network, addr string) (net.Conn, error) {
  return d(network, addr)
}

type HttpProxyDialer interface {
  ConnectTimeout() time.Duration
}

func (d *Dialer) ConnectTimeout() time.Duration {
  return d.connectTimeout
}

type Dialer struct {
  fasthttp.TCPDialer
  timeout        time.Duration
  connectTimeout time.Duration
}

func init() {
  proxy.RegisterDialerType("http", HttpProxyDial)
}
func (d *Dialer) Dial(network, addr string) (conn net.Conn, err error) {
  if network == "tcp4" {
      if d.timeout > 0 {
          return d.TCPDialer.DialTimeout(addr, d.timeout)
      }
      return d.TCPDialer.Dial(addr)
  }
  if network == "tcp" {
      if d.timeout > 0 {
          return d.TCPDialer.DialDualStackTimeout(addr, d.timeout)
      }
      return d.TCPDialer.DialDualStack(addr)
  }
  err = errors.New("don't know how to dial network:" + network)
  return
}

func (d *Dialer) GetDialFunc(proxyAddr string) (dialFunc fasthttp.DialFunc, err error) {
  u, err := url.Parse(proxyAddr)
  if err != nil {
      return
  }
  dialer, err := proxy.FromURL(u, d)
  if err != nil {
      return
  }
  dialFunc = func(addr string) (net.Conn, error) {
      var network string
      if strings.HasPrefix(addr, "[") {
          network = "tcp"
      } else {
          network = "tcp4"
      }
      return dialer.Dial(network, addr)
  }
  return
}
func HttpProxyDial(u *url.URL, dialer proxy.Dialer) (proxy.Dialer, error) {
  var proxyAddr string
  if u.Scheme != "" {
      proxyAddr = strings.TrimPrefix(u.String(), u.Scheme+"://")
  }
  var auth string
  if strings.Contains(proxyAddr, "@") {
      index := strings.LastIndex(proxyAddr, "@")
      auth = base64.StdEncoding.EncodeToString([]byte(proxyAddr[:index]))
      proxyAddr = proxyAddr[index+1:]
  }
  return DialerFunc(func(network, addr string) (conn net.Conn, err error) {
      conn, err = dialer.Dial(network, proxyAddr)
      if err != nil {
          return
      }
      var connectTimeout time.Duration
      hp, ok := dialer.(HttpProxyDialer)
      if ok {
          connectTimeout = hp.ConnectTimeout()
      }

      if connectTimeout > 0 {
          if err = conn.SetDeadline(time.Now().Add(connectTimeout)); err != nil {
              _ = conn.Close()
              return nil, err
          }
          defer func() {
              _ = conn.SetDeadline(time.Time{})
          }()
      }
      req := "CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n"
      if auth != "" {
          req += "Proxy-Authorization: Basic " + auth + "\r\n"
      }
      req += "\r\n"
      _, err = conn.Write([]byte(req))
      if err != nil {
          _ = conn.Close()
          return
      }
      res := fasthttp.AcquireResponse()
      defer fasthttp.ReleaseResponse(res)
      res.SkipBody = true
      if err = res.Read(bufio.NewReader(conn)); err != nil {
          _ = conn.Close()
          return
      }
      if res.Header.StatusCode() != 200 {
          _ = conn.Close()
          err = fmt.Errorf("could not connect to proxyAddr: %s status code: %d", proxyAddr, res.Header.StatusCode())
          return
      }
      return
  }), nil
}
func TestDialProxy(t *testing.T) {
  p := Dialer{
      timeout: time.Second * 30,
  }
  dialer, err := p.GetDialFunc("http://127.0.0.1:8005")
  if err != nil {
      t.Fatal(err)
  }
  client := &fasthttp.Client{
      ReadTimeout:         time.Second * 5,
      WriteTimeout:        time.Second * 5,
      MaxIdleConnDuration: time.Second * 5,
      Dial:                dialer,
  }
  statusCode, resp, err := client.Get(nil, "https://www.google.com")
  if err != nil {
      t.Fatal(err)
  }
  if statusCode != 200 {
      t.Fatal(err)
  }
  _ = resp
}

thanks a lot,it worked.