
Eine anpassbare HTTP/HTTPS-Proxy-Bibliothek für Go mit Unterstützung für reguläres Forwarding, CONNECT-Tunneling, MITM-TLS-Interception und programmatische Anfrage-/Antwort-Modifikation.
GoProxy ist eine Bibliothek zur Erstellung eines angepassten HTTP/HTTPS-Proxy-Servers mit Go (auch Golang genannt) und mehreren konfigurierbaren Einstellungen.
Das Ziel dieses Projekts ist es, einen optimierten Proxy-Server anzubieten, der bei einer angemessenen Menge an Datenverkehr nutzbar ist und dennoch anpassbar und programmierbar bleibt.
Der Proxy selbst ist einfach ein net/http-Handler, sodass du mehrere Middlewares (Panic-Recovery, Logging, Komprimierung usw.) darüber hinzufügen kannst. Er lässt sich problemlos in jede andere HTTP-Netzwerkbibliothek integrieren.
Um goproxy zu verwenden, sollte man den Browser (oder einen anderen Client) so einstellen, dass goproxy als HTTP-Proxy verwendet wird.
So machst du das in Chrome und in Firefox.
Wenn du mit dem base-Beispiel beginnen möchtest, ist die URL, die du als Proxy verwenden solltest, localhost:8080, die Standard-URL in unserem Beispiel.
Du musst außerdem das Zertifikat der Proxy-CA vertrauen, um Zertifikatsprobleme bei den Clients zu vermeiden.
bestimmte Hosts ausführen, mit einem einfachen Gleichheitsvergleich oder mit Regex-AuswertungRequests und Responses manipulieren, bevor sie an den Browser gesendet werdenbenutzerdefinierten http.Transport verwenden, um Requests an den Zielserver zu sendenMITM-Zertifikats-Cache angeben, um sie später für andere Requests an denselben Host wiederzuverwenden und so CPU zu sparen. Standardmäßig nicht aktiviert, aber du solltest ihn in der Produktion verwenden!benutzerdefinierten Handler umleiten, wenn das Ziel ein relativer Pfad ist (z. B. /ping)Logger-Interface implementierstKanonisierung der HTTP-Request-Header deaktivieren, indem du PreventCanonicalization auf true setztNutzt dein Unternehmen GoProxy? Hilf uns, das Projekt gewartet und gesund zu halten! Die Unterstützung von GoProxy erlaubt es uns, mehr Zeit für Fehlerbehebungen und neue Funktionen aufzuwenden. Im Gegenzug zeigen wir dein Firmenlogo hier stolz an, wenn du einen Gold-Supporter- oder Enterprise-Plan wählst.
Wenn du GoProxy in dein Projekt integrieren musst oder benutzerdefinierte Funktionen benötigst, die du in deinem Fork pflegen möchtest, kannst du Erik (den derzeitigen Betreuer) per E-Mail kontaktieren und gemeinsam besprechen, wie er dir als bezahlter unabhängiger Berater helfen kann.
Wenn du Probleme hast, Vorschläge hast oder einen Fehler findest, zögere nicht, ein GitHub-Issue zu eröffnen.
Dies ist ein von Freiwilligen verwaltetes Open-Source-Projekt, und wir freuen uns, über alles zu diskutieren, was es verbessern kann.
Erkläre alles genau, einschließlich des Grundes für das Issue und was du ändern möchtest, um das Problem verständlicher zu machen.
Du kannst auch direkt einen Pull Request eröffnen, wenn es sich um eine kleine Codeänderung handelt, aber du musst in der Beschreibung alles erklären.
Wenn du einen Pull Request mit dem Namen refactoring und 5.000 geänderten Zeilen eröffnest, werden wir ihn nicht mergen ... :D
Der Code für dieses Projekt wird unter der BSD-3-Clause-Lizenz veröffentlicht, was ihn auch für kommerzielle Zwecke nutzbar macht.
Du hast GoProxy also in eines deiner persönlichen Projekte oder in ein Projekt des Unternehmens, für das du arbeitest, eingeführt und integriert.
Wir freuen uns über neue kreative Lösungen, die mit dieser Bibliothek erstellt wurden. Zögere also nicht, den oben genannten Betreuer per E-Mail zu kontaktieren und zu erklären, warum du dieses Projekt für deine Bedürfnisse nützlich fandest.
Wenn du eine Geheimhaltungsvereinbarung mit dem Unternehmen unterzeichnet hast, kannst du vorschlagen, dass sie einen Blogbeitrag zu diesem Thema auf ihrer offiziellen Website schreiben. So werden diese Informationen öffentlich, und du kannst uns den Link des Blogbeitrags mitteilen :)
Der Zweck von Fallstudien ist es, mit der Community zu teilen, warum alle Beitragenden zu diesem Projekt mit ihrer Hilfe die Welt verbessern und was Menschen damit bauen.
Die Codebasis führt bei deinem Pull-Request-Code eine automatische Lint-Prüfung durch. Bevor du ihn eröffnest, solltest du prüfen, ob deine Änderungen diese einhalten, indem du den Linter auf deinem lokalen Rechner ausführst, damit es keine Überraschungen gibt.
So installierst du den Linter:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Dadurch wird eine ausführbare Datei in deinem $GOPATH/bin-Ordner erstellt
($GOPATH ist eine Umgebungsvariable, normalerweise
entspricht ihr Wert ~/go; überprüfe den Wert auf deinem Rechner, wenn du
dir nicht sicher bist).
Stelle sicher, dass der bin-Ordner im Pfad deiner Shell enthalten ist, um den Befehl golangci-lint run direkt verwenden zu können.
Um einen Vorgeschmack auf goproxy zu bekommen, hier ist ein einfacher HTTP/HTTPS-Proxy, der Daten einfach an das Ziel weiterleitet:
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
Diese Zeile fügt allen Requests, die über den Proxy gesendet werden, den Header X-GoProxy: yxorPoG-X hinzu, bevor sie an das Ziel gesendet werden:
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
Wenn die Eingabe von OnRequest() leer ist, verarbeitet die in DoFunc angegebene Funktion alle eingehenden Requests an den Proxy. In diesem Fall fügt sie dem Request einen Header hinzu und gibt ihn an den Aufrufer zurück.
Der Proxy sendet den modifizierten Request an das Ziel.
Du kannst auch Do anstelle von DoFunc verwenden, wenn du das angegebene Interface in deinem Typ implementierst.
⚠️ Beachte, dass wir als Response einen nil-Wert zurückgegeben haben. Wenn die zurückgegebene Response nicht nil ist, verwirft goproxy den Request und sendet die angegebene Response an den Client.
Verbindungen zu www.reddit.com zwischen 8 und 17 Uhr in der lokalen Zeitzone des Servers ablehnen:
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 gibt eine ReqCondition zurück, eine Funktion, die einen *http.Request empfängt und einen booleschen Wert zurückgibt, der prüft, ob der Request die Bedingung erfüllt (und der dann verarbeitet wird).
DstHostIs("www.reddit.com") gibt eine ReqCondition zurück, die true liefert, wenn der Request an „www.reddit.com" gerichtet ist.
Der Host-Gleichheitscheck ist case-insensitiv, um das Verhalten von DNS-Auflösern widerzuspiegeln. Selbst wenn der Benutzer also „www.rEdDit.com" eingibt, erfüllt der Vergleich die Bedingung.
Wenn die Stunde zwischen 8:00 und 17:59 Uhr liegt, geben wir in DoFunc() direkt eine Response zurück, sodass das entfernte Ziel den Request nicht erhält und der Client die Antwort "Don't waste your time!" bekommt.
import "github.com/elazarl/goproxy"
Im Ordner examples gibt es einige Beispiele für die Proxy-Nutzung, die die häufigsten Fälle abdecken. Wirf einen Blick darauf und viel Glück!
Es gibt 3 verschiedene Arten von Handlern, um das Verhalten des Proxys zu manipulieren, wie folgt:
// 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
Je nachdem, was du manipulieren möchtest, gibt es folgende Möglichkeiten, Handler zu jeder der vorherigen Listen hinzuzufügen:
// 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())
Beispiel:
// 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())
Wenn beim Verarbeiten eines Requests durch den Proxy ein Fehler auftritt, gibt der Proxy standardmäßig den HTTP-Fehler 500 (Internal Server Error) mit der Fehlermeldung als Body-Inhalt zurück.
Wenn du dieses Verhalten überschreiben möchtest, kannst du deinen eigenen RespHandler definieren, der die Fehlerantwort ändert.
Unter den Kontextparametern enthält ctx.Error den aufgetretenen error oder den Wert nil, wenn kein Fehler aufgetreten ist.
Du kannst ihn nach Belieben behandeln, einschließlich der Rückgabe eines benutzerdefinierten JSON als Body. Beispiel eines Fehlerhandlers:
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
})
Wenn beim Senden von Daten an den entfernten Zielserver (oder an den Proxy-Client) ein Fehler auftritt, wird der proxy.ConnectionErrHandler aufgerufen, um den Fehler zu behandeln, falls vorhanden; andernfalls wird ein Standard-Handler verwendet.
Der Fehler wird als Funktionsparameter übergeben und nicht im Proxy-Kontext, du musst also in diesem Handler das Feld ctx.Error nicht prüfen.
In diesem Handler hast du Zugriff auf die rohe Verbindung mit dem Proxy-Client (als io.Writer), sodass du bei Bedarf beliebige HTTP-Daten darüber senden kannst, die die Fehlerdaten enthalten.
Es gibt keine Garantie, dass die Verbindung nicht bereits geschlossen wurde, daher könnte Write() einen Fehler zurückgeben.
Die Verbindung wird nach dem Aufruf des Fehlerhandlers vom Proxy-automatisch geschlossen, du musst dir also keine Sorgen darum machen.
Dieses Projekt wurde vor 10 Jahren erstellt und hat ein Stadium der Reife erreicht. Es kann sicher in der Produktion verwendet werden, und viele Projekte tun das bereits.
Sollte es in Zukunft eine bahnbrechende Änderung geben, wird eine neue Version des Go-Moduls veröffentlicht (z. B. v2).