分享
  1. 首页
  2. 文章

用golang写一个http代理,可以抓包和科学上网

sheepbao · · 2191 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

用golang写一个http代理,可以抓包和科学上网

缘起

因为在工作中需要对上网进行限制,只让我们的app上网,意思就是放行app请求的所有域名或ip,而其他域名都禁止,所以我需要对app的http请求进行抓包。上网搜了一下,win下的fiddler不错,可惜我用的是Linux系统,fiddler不跨平台,找了下linux下的抓包软件,当然tcpdump和wireshare是足够强大的,完全可以实现我要的小小要求,但用起来有一定的复杂性,门槛稍高。在网上找到其他类似的软件还挺多,charles、NProxy等,最后发现mitmproxy最符合我的胃口,如图:
mitproy
但是mitmproxy的安装依赖太多,python就是这样,一不小心就报错了,so,想着用golang实现类似的功能,自己也很喜欢go语言,如果有空能安静写写自己喜欢的代码,是多么的幸福。想好了就实践,Let’s do it,当然凡事都有个从简到繁的过程,下面的特性慢慢增加。

特性

特性,也可以说功能,以下是实现或者将要实现的特性

  • [X] http代理
  • [X] http请求响应的抓取
  • [ ] 修改http请求
  • [ ] 重复请求
  • [ ] 同时监听多端口
  • [ ] 支持socks5、websocket、https协议
  • [ ] 界面支持终端和网页两种形式

安装

git clone https://github.com/sheepbao/gomitmproxy.git
cd gomitmproxy 
go build 

使用

  • http代理
gomimtproxy 

不带任何参数,表示http代理,默认端口8080,更改端口用 -port

  • http抓包
gomimtproxy -m 

加 -m 参数,表示抓取http请求和响应
fetch http

  • http代理科学上网

    首先你得有个墙外的服务器,如阿里香港的服务器,为图中的Server,假设其ip地址为:22.222.222.222

在Server执行:
gomitmproxy -port 8888
在你自己电脑执行:
gomitmproxy -port 8080 -raddr 22.222.222.222:8888

然后浏览器设置代理,ip为localhost,端口为8080,即可实现科学上网

proxy

源码简析

对于网络编程,Anything is a socket!
实现http代理并不难,简单地说就是用代理服务器代替客户端去请求web服务,然后在把请求的结果返回给客户端。
先来个示意图:

proxy-2

1. 客户端发起一个到gomitmproxy的连接,并且提交了HTTP CONNECT请求。 
2. gomitmproxy返回200表示连接已经建立。 
 if req.Method == "CONNECT" {
 b := []byte("HTTP/1.1 200 Connection Established\r\n" +
 "Proxy-Agent: golang_proxy/" + Version + "\r\n\r\n")
 _, err := conn.Write(b)
 if err != nil {
 logger.Println("Write Connect err:", err)
 return
 }
 } else {
 req.Header.Del("Proxy-Connection")
 req.Header.Set("Connection", "Keep-Alive")
 if err = req.Write(conn_proxy); err != nil {
 logger.Println("send to server err", err)
 return
 }
 }
3. gomitmproxy连接服务器的host,并将客户端的请求发送给服务器,如果没加 -m 参数,那直接将客户端和服务器的io口通过gomitmproxy连接,gomitmproxy很像水管的连接器,把断开的水管连接起来。
//两个io口的连接
func Transport(conn1, conn2 net.Conn) (err error) {
 rChan := make(chan error, 1)
 wChan := make(chan error, 1)
 go MyCopy(conn1, conn2, wChan)
 go MyCopy(conn2, conn1, rChan)
 select {
 case err = <-wChan:
 case err = <-rChan:
 }
 return
}
func MyCopy(src io.Reader, dst io.Writer, ch chan<- error) {
 _, err := io.Copy(dst, src)
 ch <- err
}
如果加了 -m 参数,表示要截取http请求和响应。
//打印http请求和响应
func httpDump(req *http.Request, resp *http.Response) {
 defer resp.Body.Close()
 var respStatusStr string
 respStatus := resp.StatusCode
 respStatusHeader := int(math.Floor(float64(respStatus / 100)))
 switch respStatusHeader {
 case 2:
 respStatusStr = Green("<--" + strconv.Itoa(respStatus))
 case 3:
 respStatusStr = Yellow("<--" + strconv.Itoa(respStatus))
 case 4:
 respStatusStr = Magenta("<--" + strconv.Itoa(respStatus))
 case 5:
 respStatusStr = Red("<--" + strconv.Itoa(respStatus))
 }
 fmt.Println(Green("Request:"))
 fmt.Printf("%s %s %s\n", Blue(req.Method), req.RequestURI, respStatusStr)
 for headerName, headerContext := range req.Header {
 fmt.Printf("%s: %s\n", Blue(headerName), headerContext)
 }
 fmt.Println(Green("Response:"))
 for headerName, headerContext := range resp.Header {
 fmt.Printf("%s: %s\n", Blue(headerName), headerContext)
 }
 respBody, err := ioutil.ReadAll(resp.Body)
 if err != nil {
 logger.Println("read resp body err:", err)
 } else {
 acceptEncode := resp.Header["Content-Encoding"]
 var respBodyBin bytes.Buffer
 w := bufio.NewWriter(&respBodyBin)
 w.Write(respBody)
 w.Flush()
 for _, compress := range acceptEncode {
 switch compress {
 case "gzip":
 r, err := gzip.NewReader(&respBodyBin)
 if err != nil {
 logger.Println("gzip reader err:", err)
 } else {
 defer r.Close()
 respBody, _ = ioutil.ReadAll(r)
 }
 break
 case "deflate":
 r := flate.NewReader(&respBodyBin)
 defer r.Close()
 respBody, _ = ioutil.ReadAll(r)
 break
 }
 }
 fmt.Printf("%s\n", string(respBody))
 }
 fmt.Printf("%s%s%s\n", Black("####################"), Cyan("END"), Black("####################"))
}
4. gomitmproxy将请求服务器的响应发送给客户端。
 resp, err := http.ReadResponse(bufio.NewReader(conn_proxy), req)
 if err != nil {
 logger.Println("read response err:", err)
 return
 }
 respDump, err := httputil.DumpResponse(resp, true)
 if err != nil {
 logger.Println("respDump err:", err)
 }
 _, err = conn.Write(respDump)
 if err != nil {
 logger.Println("conn write err:", err)
 }

总结

源码放在github上了,欢迎各位指导和贡献!

The more you know, the more you know you don't know!

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:sheepbao

查看原文:用golang写一个http代理,可以抓包和科学上网

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2191 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏