GoProxy 是一个使用 Go(又称 Golang)编写的库,用于创建定制的 HTTP/HTTPS代理服务器,并提供多种可配置设置。
该项目的目标是提供一个优化过的代理服务器,能够在合理流量下使用,同时又可定制且可编程。
代理本身就是一个 net/http 处理器,因此您可以在它之上添加多个中间件(panic 恢复、日志记录、压缩等)。它可以轻松地与任何其他 HTTP 网络库集成。
要使用 goproxy,需要将浏览器(或任何其他客户端)设置为使用 goproxy 作为 HTTP 代理。
以下是在 Chrome 和 Firefox 中的设置方法。
如果您决定从 base 示例开始,用作代理的 URL 是 localhost:8080,这是我们示例中的默认值。
您还需要信任代理 CA 证书,以避免客户端出现任何证书问题。
特定主机执行某些操作,可通过单一相等比较或正则匹配实现请求和响应发送到浏览器之前对其进行操作自定义 http.Transport向目标服务器发起请求MITM 证书缓存,以便稍后对同一主机的其他请求复用,从而节省 CPU。默认未启用,但您应该在生产环境中使用它!相对路径(例如 /ping)时,将常规 HTTP 流量重定向到自定义处理器Logger 接口来选择使用的日志记录器PreventCanonicalization 设置为 true,可以禁用 HTTP 请求头的规范化您的公司使用 GoProxy 吗?请帮助我们保持项目的维护和健康发展! 支持 GoProxy 可以让我们投入更多时间进行 bug 修复和新功能开发。 作为回报,如果您选择 Gold Supporter 或 Enterprise 计划,我们将在此处自豪地展示贵公司的 Logo。
如果您需要将 GoProxy 集成到您的项目中,或者您需要在分叉中维护一些自定义功能,您可以通过电子邮件联系 Erik(现任维护者),共同讨论他如何以付费独立顾问的身份为您提供帮助。
如果您遇到任何问题、有建议,或者发现了 bug,欢迎通过提交 GitHub issue 与我们联系。
这是一个由志愿者管理的开源项目,我们很乐意讨论任何可以改进它的内容。
请务必解释清楚一切,包括问题背后的原因以及您想要更改的内容,以便更容易理解问题。
如果是小的代码更改,您也可以直接提交 Pull Request,但需要在描述中说明一切。
如果您提交一个名为 refactoring 且改动 5,000 行的 Pull Request,我们不会合并它……:D
本项目的代码以 BSD 3-Clause 许可证发布,因此也适用于商业用途。
那么,您已经将 GoProxy 引入并集成到您的个人项目或您所在公司的项目中了。
我们很高兴了解使用这个库构建的新创意解决方案,因此欢迎通过电子邮件联系上面列出的维护者,说明您为什么认为这个项目对您的需求很有用。
如果您与公司签署了保密协议,您可以建议他们在其官方网站上撰写一篇关于此主题的博客文章,这样该信息将由他们自行决定公开,您也可以与我们分享博客文章的链接:)
案例研究的目的是与社区分享:为什么这个项目的所有贡献者都在通过他们的帮助改善世界,以及人们正在使用它构建什么。
代码库会对您的 Pull Request 代码进行自动 lint 检查。 在提交之前,您应该在本地机器上运行 linter,检查您的更改是否符合规范,这样就不会有任何意外。
要安装 linter:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
这将在您的 $GOPATH/bin 文件夹中创建一个可执行文件($GOPATH 是一个环境变量,通常其值相当于 ~/go,如果您不确定,请检查您机器上的值)。
请确保将 bin 文件夹包含在 shell 的路径中,以便能够直接使用 golangci-lint run 命令。
为了体验 goproxy,这里有一个基本的 HTTP/HTTPS 代理,它只是将数据转发到目标地址:
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
这一行将为所有通过代理发送的请求添加 X-GoProxy: yxorPoG-X 请求头,然后再发送到目标地址:
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
当 OnRequest() 的输入为空时,DoFunc 中指定的函数将处理所有到达代理的请求。在这种情况下,它会为请求添加一个请求头并将其返回给调用方。
代理会将修改后的请求发送到目标地址。
如果您在您的类型中实现了指定的接口,也可以使用 Do 而不是 DoFunc。
⚠️ 请注意,我们返回了一个 nil 值作为响应。 如果返回的响应不为 nil,goproxy 将丢弃该请求, 并将指定的响应发送给客户端。
在服务器本地时区的 8 点到 17 点之间拒绝连接到 www.reddit.com:
proxy.OnRequest(goproxy.DstHostIs("www.reddit.com")).DoFunc(
func(req *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
if h,_,_ := time.Now().Clock(); h >= 8 && h <= 17 {
resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!")
return req, resp
}
return req, nil
})
DstHostIs 返回一个 ReqCondition,它是一个接收 *http.Request 并返回布尔值的函数,用于检查请求是否满足条件(如果满足则会被处理)。
当请求指向 "www.reddit.com" 时,DstHostIs("www.reddit.com") 将返回一个返回 true 的 ReqCondition。
主机相等性检查是不区分大小写的,以反映 DNS 解析器的行为,因此即使用户输入 "www.rEdDit.com",该比较也会满足条件。
当时间在上午 8:00 到下午 5:59 之间时,我们直接在 DoFunc() 中返回一个响应,这样远程目标将不会收到该请求,客户端将收到 "Don't waste your time!" 响应。
import "github.com/elazarl/goproxy"
examples 文件夹中有一些代理使用示例,涵盖了最常见的用例。看一看,祝你好运!
有 3 种不同类型的处理器来操作代理的行为,如下所示:
// handler called after receiving HTTP CONNECT from the client, and
// before proxy establishes connection with the destination host
httpsHandlers []HttpsHandler
// handler called before proxy sends HTTP request to destination host
reqHandlers []ReqHandler
// handler called after proxy receives HTTP Response from destination host,
// and before proxy forwards the Response to the client
respHandlers []RespHandler
根据您想要操作的内容,向上述每个列表添加处理器的方式如下:
// Add handlers to httpsHandlers
proxy.OnRequest(some ReqConditions).HandleConnect(YourHandlerFunc())
// Add handlers to reqHandlers
proxy.OnRequest(some ReqConditions).Do(YourReqHandlerFunc())
// Add handlers to respHandlers
proxy.OnResponse(some RespConditions).Do(YourRespHandlerFunc())
示例:
// This rejects the HTTPS request to *.reddit.com during HTTP CONNECT phase.
// Reddit URL check is case-insensitive because of (?i), so the block will work also if the user types something like rEdDit.com.
proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("(?i)reddit.*:443$"))).HandleConnect(goproxy.AlwaysReject)
// Be careful about this example! It shows you a common error that you
// need to avoid.
// This will NOT reject the HTTPS request with URL ending with .gif because,
// if the scheme is HTTPS, the proxy will receive only URL.Hostname
// and URL.Port during the HTTP CONNECT phase.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).HandleConnect(goproxy.AlwaysReject)
// To fix the previous example, here there is the correct way to manipulate
// an HTTP request using URL.Path (target path) as a condition.
proxy.OnRequest(goproxy.UrlMatches(regexp.MustCompile(`.*gif$`))).Do(YourReqHandlerFunc())
如果在通过代理处理请求时发生错误,默认情况下代理会返回 HTTP 错误 500(内部服务器错误),并将错误消息作为响应体内容。
如果您想覆盖此行为,可以定义自己的 RespHandler 来更改错误响应。
在上下文参数中,ctx.Error 包含发生的 error(如果有),如果没有发生错误,则为 nil 值。
您可以按自己的意愿处理它,包括返回自定义 JSON 作为响应体。 错误处理器示例:
proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
var dnsError *net.DNSError
if errors.As(ctx.Error, &dnsError) {
// Do not leak our DNS server's address
dnsError.Server = "<server-redacted>"
return goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusBadGateway, dnsError.Error())
}
return resp
})
如果在向目标远程服务器(或代理客户端)发送数据时发生错误,将调用 proxy.ConnectionErrHandler 来处理该错误(如果存在),否则将使用默认处理器。
错误作为函数参数传递,而不是放在代理上下文中,因此您不必在此处理器中检查 ctx.Error 字段。
在此处理器中,您可以访问与代理客户端的原始连接(作为 io.Writer),因此如果需要,您可以通过它发送任何包含错误数据的 HTTP 数据。
无法保证连接尚未被关闭,因此 Write() 可能返回错误。
在错误处理器调用之后,代理库将自动关闭该连接,因此您不必担心这一点。
这个项目创建于10年前,已经达到了成熟阶段。它可以安全地用于生产环境,许多项目已经在这样做了。
如果未来有任何破坏性变更,将发布 Go 模块的新版本(例如 v2)。