一个简单的Golang实现的HTTP Proxy方法

2020-01-28 14:20:10王旭

可以看到我们需要的在第一行,第一个行的信息以空格分开,第一部分CONNECT是请求方法,这里是CONNECT,除此之外还有GET,POST等,都是HTTP协议的标准方法。

第二部分是URL,https的请求只有host和port,http的请求是一个完成的url,等下会看个样例,就明白了。

第三部是HTTP的协议和版本,这个我们不用太关注。

以上是一个https的请求,我们看下http的:


GET http://www.flysnow.org/ HTTP/1.1
Host: www.flysnow.org
Proxy-Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36

可以看到htt的,没有端口号(默认是80);比https多了schame–http://。

有了分析,下面我们就可以从HTTP头信息中获取请求的url和method信息了。



  var b [1024]byte
  n, err := client.Read(b[:])
  if err != nil {
    log.Println(err)
    return
  }
  var method, host, address string
  fmt.Sscanf(string(b[:bytes.IndexByte(b[:], 'n')]), "%s%s", &method, &host)
  hostPortURL, err := url.Parse(host)
  if err != nil {
    log.Println(err)
    return
  
  }

然后需要进一步对url进行解析,获取我们需要的远程服务器信息



  if hostPortURL.Opaque == "443" { //https访问
    address = hostPortURL.Scheme + ":443"
  } else { //http访问
    if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
      address = hostPortURL.Host + ":80"
    } else {
      address = hostPortURL.Host
    }
  }

这样就完整了获取了要请求服务器的信息,他们可能是以下几种格式


ip:port
hostname:port
domainname:port

就是有可能是ip(v4orv6),有可能是主机名(内网),有可能是域名(dns解析)

代理服务器和远程服务器建立连接

有了远程服务器的信息了,就可以进行拨号建立连接了,有了连接,才可以通信。


  //获得了请求的host和port,就开始拨号吧
  server, err := net.Dial("tcp", address)
  if err != nil {
    log.Println(err)
    return
  }

数据转发

拨号成功后,就可以进行数据代理传输了


if method == "CONNECT" {
    fmt.Fprint(client, "HTTP/1.1 200 Connection establishedrn")
  } else {
    server.Write(b[:n])
  }
  //进行转发
  go io.Copy(server, client)
  io.Copy(client, server)

其中对CONNECT方法有单独的回应,客户端说要建立连接,代理服务器要回应建立好了,然后才可以像HTTP一样请求访问。

运行外国外VPS上

到这里,我们的代理服务器全部开发完成了,下面是完整的源代码:


package main

import (
  "bytes"
  "fmt"
  "io"
  "log"
  "net"
  "net/url"
  "strings"
)

func main() {
  log.SetFlags(log.LstdFlags|log.Lshortfile)
  l, err := net.Listen("tcp", ":8081")
  if err != nil {
    log.Panic(err)
  }

  for {
    client, err := l.Accept()
    if err != nil {
      log.Panic(err)
    }

    go handleClientRequest(client)
  }
}

func handleClientRequest(client net.Conn) {
  if client == nil {
    return
  }
  defer client.Close()

  var b [1024]byte
  n, err := client.Read(b[:])
  if err != nil {
    log.Println(err)
    return
  }
  var method, host, address string
  fmt.Sscanf(string(b[:bytes.IndexByte(b[:], 'n')]), "%s%s", &method, &host)
  hostPortURL, err := url.Parse(host)
  if err != nil {
    log.Println(err)
    return
  }

  if hostPortURL.Opaque == "443" { //https访问
    address = hostPortURL.Scheme + ":443"
  } else { //http访问
    if strings.Index(hostPortURL.Host, ":") == -1 { //host不带端口, 默认80
      address = hostPortURL.Host + ":80"
    } else {
      address = hostPortURL.Host
    }
  }

  //获得了请求的host和port,就开始拨号吧
  server, err := net.Dial("tcp", address)
  if err != nil {
    log.Println(err)
    return
  }
  if method == "CONNECT" {
    fmt.Fprint(client, "HTTP/1.1 200 Connection establishedrn")
  } else {
    server.Write(b[:n])
  }
  //进行转发
  go io.Copy(server, client)
  io.Copy(client, server)
}