欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-11-16 09:21:41
最近因为换了mac,以前的linux基本上不再使用了,但是我的ss代理还得用。ss代理大家都了解,一个很nb的socks代理工具,但是就是因为他是socks的,想用http...

最近因为换了mac,以前的linux基本上不再使用了,但是我的ss代理还得用。ss代理大家都了解,一个很nb的socks代理工具,但是就是因为他是socks的,想用http代理的时候很不方便。

以前在linux下的时候,会安装一个privoxy把socks代理转换为http代理,开机启动,也比较方便。但是mac下使用brew安装的privoxy就很难用,再加上以前一个有个想法,一个软件搞定socks和http代理,这样就不用安装一个单独的软件做转换了。

想着就开始做吧,以前基本上没有搞过太多的网络编程,最近也正好在研究go,正好练练手。

我们这里主要讲使用http/1.1协议中的connect方法建立起来的隧道连接,实现的http proxy。这种代理的好处就是不用知道客户端请求的数据,只需要原封不动的转发就可以了,对于处理https的请求就非常方便了,不用解析他的内容,就可以实现代理。

启动代理监听

要想做一个http proxy,我们需要启动一个服务器,监听一个端口,用于接收客户端的请求。golang给我们提供了强大的net包供我们使用,我们启动一个代理服务器监听非常方便。

  l, err := net.listen("tcp", ":8080")
  if err != nil {
    log.panic(err)
  }

以上代理我们就实现了一个在8080端口上监听的服务器,我们这里没有写ip地址,默认在所有ip地址上进行监听。如果你只想本机适用,可以使用127.0.0.1:8080,这样机器就访问不了你的代理服务器了。

监听接收代理请求

启动了代理服务器,就可以开始接受不了代理请求了,有了请求,我们才能做进一步的处理。

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

    go handleclientrequest(client)
  }

listener接口的accept方法,会接受客户端发来的连接数据,这是一个阻塞型的方法,如果客户端没有连接数据发来,他就是阻塞等待。接收来的连接数据,会马上交给handleclientrequest方法进行处理,这里使用一个go关键字开一个goroutine的目的是不阻塞客户端的接收,代理服务器可以马上接收下一个连接请求。

解析请求,获取要访问的ip和端口

有了客户端的代理请求了,我们还得从请求里提取客户端要访问的远程主机的ip和端口,这样我们的代理服务器才可以建立和远程主机的连接,代理转发。

http协议的头信息里就包含有我们需要的主机名(ip)和端口信息,并且是明文的,协议很规范,类似于:

connect www.google.com:443 http/1.1
host: www.google.com:443
proxy-connection: keep-alive
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

可以看到我们需要的在第一行,第一个行的信息以空格分开,第一部分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 established\r\n")
  } 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 established\r\n")
  } else {
    server.write(b[:n])
  }
  //进行转发
  go io.copy(server, client)
  io.copy(client, server)
}

把源代码编译,然后放到你国外的vps上,在自己机器上配置好http代理,就可以到处访问,*自在了。

以上这篇一个简单的golang实现的http proxy方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。