
Uma biblioteca de proxy HTTP/HTTPS personalizável para Go, com suporte a encaminhamento regular, tunelamento CONNECT, interceptação TLS MITM e modificação programática de requisições/respostas.
GoProxy é uma biblioteca para criar um servidor proxy HTTP/HTTPS personalizado usando
Go (também conhecido como Golang), com várias configurações disponíveis.
O objetivo deste projeto é oferecer um servidor proxy otimizado, utilizável com
uma quantidade razoável de tráfego, mas também personalizável e programável.
O proxy em si é simplesmente um handler net/http, então você pode adicionar vários
middlewares (panic recover, logging, compressão, etc.) sobre ele. Ele pode ser
facilmente integrado com qualquer outra biblioteca de rede HTTP.
Para usar o goproxy, você deve configurar seu navegador (ou qualquer outro cliente)
para usar o goproxy como um proxy HTTP.
Veja como fazer isso no Chrome
e no Firefox.
Se você decidir começar com o exemplo base, a URL que você deve usar como
proxy é localhost:8080, que é a padrão no nosso exemplo.
Você também precisa confiar
no certificado CA do proxy, para evitar qualquer problema de certificado nos clientes.
hosts específicos, com uma única comparação de igualdade ou com avaliação de regexrequests e responses antes de enviá-los ao navegadorhttp.Transport personalizado para realizar requisições ao servidor de destinocache de certificados MITM, para reutilizá-los posteriormente em outras requisições ao mesmo host, economizando CPU. Não habilitado por padrão, mas você deve usá-lo em produção!handler personalizado, quando o destino for um caminho relativo (ex.: /ping)Loggerdesabilitar a canonicalização dos cabeçalhos das requisições HTTP, definindo PreventCanonicalization como trueSua empresa usa o GoProxy? Ajude-nos a manter o projeto ativo e saudável! Apoiar o GoProxy nos permite dedicar mais tempo a correções de bugs e novos recursos. Em troca, se você escolher um plano Gold Supporter ou Enterprise, exibiremos orgulhosamente o logotipo da sua empresa aqui.
Se você precisar integrar o GoProxy ao seu projeto, ou precisar de alguns recursos personalizados para manter no seu fork, você pode entrar em contato com Erik (o mantenedor atual) por e-mail, e vocês podem discutir juntos como ele pode ajudar você como consultor independente remunerado.
Se você tiver algum problema, sugestão, ou se encontrar um bug, sinta-se à vontade
para entrar em contato abrindo uma issue no GitHub.
Este é um projeto open source gerenciado por voluntários, e ficamos felizes
em discutir qualquer coisa que possa melhorá-lo.
Certifique-se de explicar tudo, incluindo o motivo por trás da issue
e o que você deseja alterar, para tornar o problema mais fácil de entender.
Você também pode abrir diretamente um Pull Request, se for uma pequena alteração de código, mas
você precisa explicar tudo na descrição.
Se você abrir um pull request chamado refactoring com 5.000 linhas alteradas,
não vamos mesclá-lo... :D
O código deste projeto é distribuído sob a licença BSD 3-Clause,
tornando-o útil também para usos comerciais.
Então, você introduziu e integrou o GoProxy em um dos seus projetos pessoais ou em um projeto dentro da empresa em que você trabalha.
Ficamos felizes em conhecer novas soluções criativas feitas com esta biblioteca,
então sinta-se à vontade para entrar em contato com o mantenedor listado acima por e-mail,
explicando por que você achou este projeto útil para suas necessidades.
Se você assinou um Acordo de Confidencialidade (Non Disclosure Agreement) com a empresa, você
pode propor a eles que escrevam um post de blog no site oficial deles sobre
este tópico, assim essa informação será pública por escolha deles, e você pode
compartilhar o link do post do blog conosco :)
O objetivo dos estudos de caso é compartilhar com a comunidade por que todos os
contribuidores deste projeto estão melhorando o mundo com a ajuda deles e
o que as pessoas estão construindo usando-o.
O codebase usa uma verificação automática de lint no código do seu Pull Request. Antes de abri-lo, você deve verificar se suas alterações o respeitam, executando o linter na sua máquina local, para que você não tenha nenhuma surpresa.
Para instalar o linter:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Isso criará um executável na sua pasta $GOPATH/bin
($GOPATH é uma variável de ambiente, geralmente
seu valor é equivalente a ~/go, verifique o valor na sua máquina se você
não tiver certeza sobre isso).
Certifique-se de incluir a pasta bin no path do seu shell, para poder
usar diretamente o comando golangci-lint run.
Para ter um gostinho do goproxy, aqui está um proxy HTTP/HTTPS básico
que apenas encaminha dados para o destino:
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
Esta linha adicionará o cabeçalho X-GoProxy: yxorPoG-X a todas as requisições enviadas através do proxy,
antes de enviá-las ao destino:
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
Quando a entrada de OnRequest() estiver vazia, a função especificada em DoFunc
processará todas as requisições recebidas pelo proxy. Nesse caso, ela adicionará
um cabeçalho à requisição e a retornará ao chamador.
O proxy enviará a requisição modificada ao destino.
Você também pode usar Do em vez de DoFunc, se você implementar a interface
especificada no seu tipo.
⚠️ Observe que retornamos um valor nil como resposta. Se a resposta retornada não for nil, o goproxy descartará a requisição e enviará a resposta especificada ao cliente.
Recuse conexões com www.reddit.com entre 8 e 17 no fuso horário local do servidor:
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 retorna uma ReqCondition, que é uma função que recebe um *http.Request
e retorna um booleano que verifica se a requisição satisfaz a condição (e que será processada).
DstHostIs("www.reddit.com") retornará uma ReqCondition que retorna true
quando a requisição for direcionada a "www.reddit.com".
A verificação de igualdade do host é case-insensitive (não diferencia maiúsculas de minúsculas),
para refletir o comportamento dos resolvedores de DNS, então mesmo que o usuário digite "www.rEdDit.com", a comparação
satisfará a condição.
Quando a hora estiver entre 8:00 e 17:59, retornamos diretamente
uma resposta em DoFunc(), então o destino remoto não receberá a
requisição e o cliente receberá a resposta "Don't waste your time!".
import "github.com/elazarl/goproxy"
Há alguns exemplos de uso do proxy na pasta examples, que
cobrem os casos mais comuns. Dê uma olhada neles e boa sorte!
Existem 3 tipos diferentes de handlers para manipular o comportamento do proxy, conforme abaixo:
// 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
Dependendo do que você deseja manipular, as formas de adicionar handlers a cada uma das listas anteriores são:
// 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())
Exemplo:
// 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())
Se ocorrer um erro ao manipular uma requisição através do proxy, por padrão
o proxy retorna o erro HTTP 500 (Internal Server Error) com a mensagem de erro como conteúdo do body.
Se você quiser substituir esse comportamento, você pode definir seu próprio
RespHandler que altera a resposta de erro.
Entre os parâmetros de contexto, ctx.Error contém o error ocorrido,
se houver, ou o valor nil, se nenhum erro aconteceu.
Você pode tratá-lo como desejar, inclusive retornando um JSON personalizado como body. Exemplo de um handler de erro:
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
})
Se ocorrer um erro ao enviar dados para o servidor remoto de destino (ou para
o cliente do proxy), o proxy.ConnectionErrHandler é chamado para tratar o
erro, se presente; caso contrário, um handler padrão será usado.
O erro é passado como parâmetro de função e não dentro do contexto do proxy,
então você não precisa verificar o campo ctx.Error neste handler.
Neste handler você tem acesso à conexão bruta com o cliente do proxy
(como um io.Writer), então você pode enviar qualquer dado HTTP por ela,
se necessário, contendo os dados do erro.
Não há garantia de que a conexão já não foi fechada, então
o Write() pode retornar um erro.
A conexão será fechada automaticamente pela biblioteca do proxy após a
chamada do handler de erro, então você não precisa se preocupar com isso.
Este projeto foi criado há 10 anos e alcançou um estágio de
maturidade. Ele pode ser usado com segurança em produção, e muitos projetos
já o fazem.
Se houver alguma mudança que quebre a compatibilidade no futuro, uma nova versão do
módulo Go será lançada (ex.: v2).