用golang寫一個http代理,可以抓包和科學上網

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

用golang寫一個http代理,可以抓包和科學上網

緣起

因為在工作中需要對上網進行限制,只讓我們的app上網,意思就是允許存取app請求的所有網域名稱或ip,而其他網域名稱都禁止,所以我需要對app的http請求進行抓包。上網搜了一下,win下的fiddler不錯,可惜我用的是Linux系統,fiddler不跨平台,找了下linux下的抓包軟體,當然tcpdump和wireshare是足夠強大的,完全可以實現我要的小小要求,但用起來有一定的複雜性,門檻稍高。在網上找到其他類似的軟體還挺多,charles、NProxy等,最後發現mitmproxy最符合我的胃口,

但是mitmproxy的安裝依賴太多,python就是這樣,一不小心就報錯了,so,想著用golang實作類別似的功能,自己也很喜歡go語言,如果有空能安靜寫寫自己喜歡的代碼,是多麼的幸福。想好了就實踐,Let’s do it,當然凡事都有個從簡到繁的過程,下面的特性慢慢增加。

特性

特性,也可以說功能,以下是實現或者將要實現的特性

  • [X] http代理
  • [X] http請求響應的抓取
  • [ ] 修改http請求
  • [ ] 重複請求
  • [ ] 同時監聽多連接埠
  • [ ] 支援socks5、websocket、https協議
  • [ ] 介面支援終端和網頁兩種形式

安裝

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

使用

  • http代理
gomimtproxy 

不帶任何參數,表示http代理,預設連接埠8080,更改連接埠用 -port

  • http抓包
gomimtproxy -m 

加 -m 參數,表示抓取http請求和響應

  • http代理科學上網

    首先你得有個牆外的伺服器,如阿里香港的伺服器,為圖中的Server,假設其ip地址為:22.222.222.222

在Server執行:gomitmproxy -port 8888
在你自己電腦執行:gomitmproxy -port 8080 -raddr 22.222.222.222:8888

然後瀏覽器設定代理,ip為localhost,連接埠為8080,即可實現科學上網

源碼簡析

對於網路編程,Anything is a socket!實現http代理並不難,簡單地說就是用Proxy 伺服器代替用戶端去請求web服務,然後在把請求的結果返回給用戶端。先來個:

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!
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.