
Настраиваемая библиотека HTTP/HTTPS-прокси для Go, поддерживающая обычное проксирование, CONNECT-туннелирование, MITM-перехват TLS и программную модификацию запросов/ответов.
GoProxy — это библиотека для создания настраиваемого HTTP/HTTPS прокси-сервера на Go (также известном как Golang) с несколькими доступными параметрами конфигурации.
Цель этого проекта — предложить оптимизированный прокси-сервер, способный работать с разумным объёмом трафика, но при этом настраиваемый и программируемый.
Сам прокси — это просто обработчик net/http, поэтому поверх него можно добавить несколько промежуточных обработчиков (panic recover, логирование, сжатие и т. д.). Его можно легко интегрировать с любой другой сетевой HTTP-библиотекой.
Чтобы использовать goproxy, нужно настроить браузер (или любой другой клиент) на использование goproxy в качестве HTTP-прокси.
Вот как это сделать в Chrome
и в Firefox.
Если вы решите начать с примера base, используйте в качестве адреса прокси localhost:8080 — это значение по умолчанию в нашем примере.
Вам также нужно довериться сертификату прокси-ЦС (CA), чтобы у клиентов не возникало проблем с сертификатами.
конкретных хостов — с помощью простого сравнения на равенство или регулярного выражениязапросы и ответы перед отправкой в браузерсобственный http.Transport для выполнения запросов к целевому серверукэш сертификатов MITM, чтобы переиспользовать их позже для других запросов к тому же хосту и тем самым экономить CPU. По умолчанию не включён, но в production его стоит использовать!специальный обработчик, когда целевой URL является относительным путём (например, /ping)Loggerотключить канонизацию заголовков HTTP-запросов, установив PreventCanonicalization в trueВаша компания использует GoProxy? Помогите нам поддерживать проект в рабочем состоянии! Поддержка GoProxy позволяет нам уделять больше времени исправлению ошибок и новым функциям. Взамен, если вы выберете план Gold Supporter или Enterprise, мы с гордостью разместим логотип вашей компании здесь.
Если вам нужно интегрировать GoProxy в свой проект или вы хотите поддерживать какие-то собственные доработки в своём форке, вы можете связаться с Эриком (текущим мейнтейнером) по электронной почте и обсудить, как он может помочь вам в качестве платного независимого консультанта.
Если у вас возникли проблемы, есть предложения или вы нашли ошибку, смело обращайтесь, создав issue на GitHub.
Это open source-проект, которым управляют волонтёры, и мы рады обсудить всё, что может его улучшить.
Обязательно объясните всё, включая причину обращения и то, что вы хотите изменить, чтобы проблему было легче понять.
Если изменение кода небольшое, вы можете сразу открыть Pull Request, но в описании нужно объяснить всё.
Если вы откроете pull request с названием refactoring и 5,000 изменённых строк, мы не сможем его слить... :D
Код этого проекта распространяется под лицензией BSD 3-Clause, что делает его пригодным и для коммерческого использования.
Итак, вы внедрили и интегрировали GoProxy в один из своих личных проектов или в проект внутри компании, где работаете.
Мы будем рады узнать о новых креативных решениях, созданных с помощью этой библиотеки, поэтому не стесняйтесь связаться с указанным выше мейнтейнером по электронной почте и объяснить, почему этот проект оказался полезным для ваших задач.
Если вы подписали с компанией соглашение о неразглашении (NDA), вы можете предложить им написать пост в блоге на их официальном сайте на эту тему; тогда эта информация станет публичной по их решению, и вы сможете поделиться ссылкой на этот пост с нами :)
Цель таких примеров — поделиться с сообществом, как все участники этого проекта с помощью своего вклада улучшают мир, и что люди создают, используя его.
В кодовой базе используется автоматическая проверка линтером кода вашего Pull Request. Перед открытием запроса стоит запустить линтер локально и убедиться, что ваши изменения ему соответствуют, чтобы не было сюрпризов.
Чтобы установить линтер:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Это создаст исполняемый файл в папке $GOPATH/bin ($GOPATH — переменная окружения, обычно её значение эквивалентно ~/go; проверьте её значение на своей машине, если не уверены).
Убедитесь, что папка bin добавлена в путь вашей оболочки, чтобы можно было напрямую использовать команду 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 отбросит запрос и отправит клиенту указанный ответ.
Отклонять соединения с www.reddit.com в период с 8 до 17 по местному времени сервера:
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 и возвращает логическое значение, проверяя, удовлетворяет ли запрос условию (и будет обработан).
DstHostIs("www.reddit.com") вернёт ReqCondition, которая возвращает true, когда запрос направлен на "www.reddit.com".
Проверка равенства хоста не зависит от регистра — это отражает поведение DNS-резолверов, так что даже если пользователь введёт "www.rEdDit.com", сравнение удовлетворит условие.
Когда час находится в диапазоне с 8:00 до 17: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 (Internal Server Error) с текстом ошибки в качестве тела ответа.
Если вы хотите изменить это поведение, вы можете определить свой собственный RespHandler, который изменит ответ с ошибкой.
Среди параметров контекста ctx.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 лет назад и достиг стадии зрелости. Его можно безопасно использовать в production, и многие проекты уже так делают.
Если в будущем произойдут критические изменения (breaking change), будет выпущена новая версия Go-модуля (например, v2).