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

最近因为换了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方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • go语言实现一个简单的http客户端抓取远程url的方法

    本文实例讲述了go语言实现一个简单的http客户端抓取远程url的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (  "fmt"  "log"  "net/http"  "net/url"  "io/ioutil" ) func main() { resp, err := http.Get("http://www.google.co.

  • go语言实现一个最简单的http文件服务器实例

    本文实例讲述了go语言实现一个最简单的http文件服务器的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "net/http" ) func main() {     http.Handle("/", http.FileServer(http.Dir("./")))     http.ListenAndServe(":8123", nil) } 希望本文

  • 1行Go代码实现反向代理的示例

    暂且放下你的编程语言来瞻仰下我所见过的最棒的标准库. 为项目选择编程语言和挑选你最爱的球队不一样.应该从实用主义出发,根据特定的工作选择合适的工具. 在这篇文章中我会告诉你从何时开始并且为什么我认为 Go 语言如此闪耀,具体来说是它的标准库对于基本的网络编程来说显得非常稳固.更具体一点,我们将要编写一个反向代理程序. Go 为此提供了很多,但真正支撑起它的在于这些低级的网络管道任务,没有更好的语言了. 反向代理是什么? 有个很棒的说法是流量转发 .我获取到客户端来的请求,将它发往另一个服务器,从

  • go语言实现简单http服务的方法

    本文实例讲述了go语言实现简单http服务的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package main import (     "flag"     "log"     "net/http"     "text/template" ) var addr = flag.String("addr", ":1718", "http service

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

    最近因为换了Mac,以前的Linux基本上不再使用了,但是我的SS代理还得用.SS代理大家都了解,一个很NB的socks代理工具,但是就是因为他是Socks的,想用HTTP代理的时候很不方便. 以前在Linux下的时候,会安装一个Privoxy把socks代理转换为HTTP代理,开机启动,也比较方便.但是Mac下使用Brew安装的Privoxy就很难用,再加上以前一个有个想法,一个软件搞定socks和HTTP代理,这样就不用安装一个单独的软件做转换了. 想着就开始做吧,以前基本上没有搞过太多的网

  • 实现一个简单的vue无限加载指令方法

    vue 中的自定义指令是对底层 dom 进行操作,下面以实现滚动到底部加载数据,实现无限加载来介绍如何自定义一个简单的指令. 无限加载的原理是通过对滚动事件的监听,每一次滚动都要获取到已滚动的距离,如果滚动的距离加上浏览器窗口高度,会大于等于内容高度,就触发函数加载数据. 先介绍不使用 vue 的情况如何实现无限加载. 不使用框架 首先是html: <!DOCTYPE html><html lang="en"> <head><meta char

  • 用函数模板,写一个简单高效的 JSON 查询器的方法介绍

    JSON可谓是JavaScript的亮点,它能用优雅简练的代码实现Object和Array的初始化.同样是基于文本的数据定义,它比符号分隔更有语义,比XML更简洁.因此越来越多的JS开发中,使用它作为数据的传输和储存. JS数组内置了不少有用的方法,方便我们对数据的查询和筛选.例如我们有一堆数据: 复制代码 代码如下: var heros = [        // 名============攻=====防=======力量====敏捷=====智力====        {name:'冰室女巫

  • 一个简单的JS鼠标悬停特效具体方法

    首先,需加载jquery库(略过). 其次,将下面 Javascript代码加到页面的头部:  复制代码 代码如下: <script type='text/javascript'>    jQuery(document).ready(function($){    $('a').hover(function()    {    $(this).stop().animate({'left': '5px'}, 'fast');    }, function() {    $(this).stop(

  • 基于一个简单定长内存池的实现方法详解

    主要分为 3 个部分,memoryPool 是管理内存池类,block 表示内存块,chunk 表示每个存储小块.它们之间的关系为,memoryPool 中有一个指针指向某一起始 block,block 之前通过 next 指针构成链表结构的连接,每个 block 包含指定数量的 chunk.每次分配内存的时候,分配 chunk 中的数据地址. 主要数据结构设计: Block: 复制代码 代码如下: struct block {    block * next;//指向下一个block指针   

  • 浅谈Golang中创建一个简单的服务器的方法

    我们知道,golang中的net/http包对网络的支持非常好,这样会让我们比较容易的建立起一个相对简单的服务器,我们来看一段代码 func sayHi(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w,"Hi") } func main() { http.HandleFunc("/sayHi", sayHi) log.Fatal(http.ListenAndServe("localhost:80

  • 在 Golang 中实现一个简单的Http中间件

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的形式添加一些功能到管道中,一次请求和响应在管道中的执行过程如下 首先, 我定义了三个测试的中间件 Middleware1,2,3 如下 func Middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w

  • golang实现一个简单的websocket聊天室功能

    基本原理: 1.引入了 golang.org/x/net/websocket 包. 2.监听端口. 3.客户端连接时,发送结构体: {"type":"login","uid":"我是用户名","msg":"登陆成功"}' .服务端根据login信息,维护一个map,用来存放不同用户的连接体. 4.有用户发言时,将msg内容轮询发给给一个用户. 5.客户端使用js websocket功能,

  • 在 Golang 中实现一个简单的Http中间件过程详解

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的形式添加一些功能到管道中,一次请求和响应在管道中的执行过程如下 首先, 我定义了三个测试的中间件 Middleware1,2,3 如下 func Middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w

  • Golang 手写一个简单的并发任务 manager

    目录 前言 errgroup 需求拆解 实战代码 Job JobManager 错误处理 及时退出 完整代码 小结 前言 今天也是偏实战的内容,作为一个并发复习课,很简单,我们来看看怎样实现一个并发任务 manager. 在微服务的场景下,我们有很多任务的执行是没有明确的先后顺序的,比如一个接口同时要做到任务 A 和 任务 B,两个任务分别拿到一些数据,最后组装裁剪后通过接口下发. 此时,A 和 B 两个任务没有依赖关系,如果我们串行来执行,会拖慢整个任务的执行节奏,用并发的方式来优化是一个方向

随机推荐