简介

经常会有些需求需要动态拦截、修改请求,包括 http 或者 https 的所有请求, 简单例如以前很火的头脑王者,拦截请求,拿到题目匹配答案并最终在正确答案的前面打钩号。

这种东西怎么做呢,很简单 -> 代理,也就是今天的主角 github.com/elazarl/goproxy

先看个最基础的例子

package main

import (
    "github.com/elazarl/goproxy"
    "log"
    "net/http"
)

func main() {
    proxy := goproxy.NewProxyHttpServer()
    log.Fatal(http.ListenAndServe(":8080", proxy))
}

几行代码就可以启动一个代理了,现在开始加各种逻辑

添加上级代理

本地网络可能是通过代理上网的,或者爬虫需要于是只能做代理的代理,假设已有上级代理地址 pProxy="socks://127.0.0.1:10082",那么只需要简单设置即可:

proxy.Tr.Proxy = func(req *http.Request) (*url.URL, error) {
    return url.Parse(pProxy)
}

添加证书缓存

请求可能是 http 的,当然更可能是 https 的,https 的请求就牵扯一个证书的证书的问题,证书可以自己生成,但这个生成的过程是很耗费 cpu 的,所以咱们可以缓存一下:

var memStore = gcache.New(300).LRU().Build()

func (MemCertStore) Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error) {

    if r, e := memStore.Get(hostname); e == nil {
        return r.(*tls.Certificate), nil
    } else {
        cert, err := gen()
        if err != nil {
            return nil, err
        }
        memStore.Set(hostname, cert)
        return cert, nil
    }
}

这地方用了个支持 LRU 的缓存 github.com/bluele/gcache,如果没多少量或者不需要长时间运行直接扔内存里也可以,如果没有这段代码在请求量稍高的时候很容易跑满 CPU。

拦截请求

OnRequestOnResponse
proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {

    if needHijack(req.URL) {

        resp, err := http.ReadResponse(bufio.NewReader(bytes.NewReader([]byte(`HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

`+hijackResp))), req)
        ctx.Resp = resp
    }
    return req, ctx.Resp
})

All Done !!!

附带一个获取本机空闲端口的代码:

func GetFreePort(retry int) (int, error) {
    addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
    if err != nil {
        if retry >= 3 {
            return 0, err
        } else {
            return GetFreePort(retry + 1)
        }
    }

    l, err := net.ListenTCP("tcp", addr)
    if err != nil {
        if retry >= 3 {
            return 0, err
        } else {
            return GetFreePort(retry + 1)
        }
    }
    defer l.Close()
    return l.Addr().(*net.TCPAddr).Port, nil
}