
Une bibliothèque de proxy HTTP/HTTPS personnalisable pour Go prenant en charge le transfert standard, le tunneling CONNECT, l'interception TLS MITM et la modification programmatique des requêtes/réponses.
GoProxy est une bibliothèque permettant de créer un serveur proxy HTTP/HTTPS personnalisé en utilisant
Go (alias Golang), avec plusieurs paramètres configurables disponibles.
L'objectif de ce projet est d'offrir un serveur proxy optimisé, utilisable avec
un volume de trafic raisonnable, tout en étant personnalisable et programmable.
Le proxy lui-même est simplement un gestionnaire net/http, vous pouvez donc ajouter plusieurs
middlewares (récupération de panique, journalisation, compression, etc.) par-dessus. Il peut être
facilement intégré à toute autre bibliothèque réseau HTTP.
Pour utiliser goproxy, il faut configurer son navigateur (ou tout autre client)
pour utiliser goproxy comme proxy HTTP.
Voici comment faire dans Chrome
et dans Firefox.
Si vous décidez de commencer avec l'exemple base, l'URL à utiliser comme
proxy est localhost:8080, qui est celle par défaut dans notre exemple.
Vous devez également approuver
le certificat CA du proxy, afin d'éviter tout problème de certificat chez les clients.
hôtes spécifiques, avec une simple comparaison d'égalité ou une évaluation par expression régulièrerequêtes et réponses avant de les envoyer au navigateurhttp.Transport personnalisé pour effectuer les requêtes vers le serveur ciblecache de certificats MITM, pour les réutiliser ensuite pour d'autres requêtes vers le même hôte, économisant ainsi du CPU. Non activé par défaut, mais vous devriez l'utiliser en production !gestionnaire personnalisé, lorsque la cible est un chemin relatif (ex. /ping)Loggerdésactiver la canonicalisation des en-têtes de requête HTTP, en définissant PreventCanonicalization sur trueVotre entreprise utilise-t-elle GoProxy ? Aidez-nous à maintenir le projet en bonne santé ! Soutenir GoProxy nous permet de consacrer plus de temps aux corrections de bugs et aux nouvelles fonctionnalités. En contrepartie, si vous choisissez le plan Gold Supporter ou Enterprise, nous afficherons fièrement le logo de votre entreprise ici.
Si vous avez besoin d'intégrer GoProxy dans votre projet, ou si vous avez besoin de fonctionnalités personnalisées à maintenir dans votre fork, vous pouvez contacter Erik (le mainteneur actuel) par e-mail, et vous pourrez discuter ensemble de la manière dont il peut vous aider en tant que consultant indépendant rémunéré.
Si vous rencontrez un problème, avez une suggestion, ou si vous trouvez un bug, n'hésitez pas à nous
contacter en ouvrant une issue GitHub.
Il s'agit d'un projet open source géré par des bénévoles, et nous serons ravis
de discuter de tout ce qui peut l'améliorer.
Assurez-vous de tout expliquer, y compris la raison derrière l'issue
et ce que vous souhaitez changer, afin de faciliter la compréhension du problème.
Vous pouvez également ouvrir directement une Pull Request, s'il s'agit d'une petite modification de code, mais
vous devez tout expliquer dans la description.
Si vous ouvrez une pull request nommée refactoring avec 5 000 lignes modifiées,
nous ne la fusionnerons pas... :D
Le code de ce projet est publié sous licence BSD 3-Clause,
ce qui le rend également utile pour des utilisations commerciales.
Vous avez donc introduit et intégré GoProxy dans l'un de vos projets personnels ou dans un projet au sein de l'entreprise pour laquelle vous travaillez.
Nous serions ravis de découvrir de nouvelles solutions créatives réalisées avec cette bibliothèque,
alors n'hésitez pas à contacter le mainteneur listé ci-dessus par e-mail, pour expliquer
pourquoi vous avez trouvé ce projet utile pour vos besoins.
Si vous avez signé un accord de non-divulgation avec l'entreprise, vous
pouvez lui proposer d'écrire un article de blog sur son site web officiel à propos de
ce sujet, afin que cette information soit rendue publique à leur discrétion, et vous pourrez
partager le lien de l'article de blog avec nous :)
L'objectif des études de cas est de partager avec la communauté pourquoi tous les
contributeurs de ce projet améliorent le monde grâce à leur aide et
ce que les gens construisent en utilisant ce projet.
Le codebase utilise une vérification automatique de lint sur le code de vos Pull Requests. Avant de l'ouvrir, vous devriez vérifier que vos modifications le respectent, en exécutant le linter sur votre machine locale, afin d'éviter toute surprise.
Pour installer le linter :
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
Cela créera un exécutable dans votre dossier $GOPATH/bin
($GOPATH est une variable d'environnement, généralement
sa valeur équivaut à ~/go, vérifiez sa valeur sur votre machine si vous
n'en êtes pas sûr).
Assurez-vous d'inclure le dossier bin dans le chemin de votre shell, afin de pouvoir
utiliser directement la commande golangci-lint run.
Pour avoir un aperçu de goproxy, voici un proxy HTTP/HTTPS basique
qui se contente de transférer les données vers la destination :
package main
import (
"log"
"net/http"
"github.com/elazarl/goproxy"
)
func main() {
proxy := goproxy.NewProxyHttpServer()
proxy.Verbose = true
log.Fatal(http.ListenAndServe(":8080", proxy))
}
Cette ligne ajoutera l'en-tête X-GoProxy: yxorPoG-X à toutes les requêtes envoyées via le proxy,
avant de les transmettre à la destination :
proxy.OnRequest().DoFunc(
func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
r.Header.Set("X-GoProxy","yxorPoG-X")
return r,nil
})
Lorsque l'entrée de OnRequest() est vide, la fonction spécifiée dans DoFunc
traitera toutes les requêtes entrantes vers le proxy. Dans ce cas, elle ajoutera
un en-tête à la requête et la retournera à l'appelant.
Le proxy enverra la requête modifiée à la destination.
Vous pouvez également utiliser Do au lieu de DoFunc, si vous implémentez l'interface
spécifiée dans votre type.
⚠️ Notez que nous avons retourné une valeur nil comme réponse. Si la réponse retournée n'est pas nil, goproxy ignorera la requête et enverra la réponse spécifiée au client.
Refuser les connexions à www.reddit.com entre 8 et 17 heures dans le fuseau horaire local du serveur :
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 retourne une ReqCondition, qui est une fonction recevant un *http.Request
et retournant un booléen vérifiant si la requête satisfait la condition (et qui sera traitée).
DstHostIs("www.reddit.com") retournera une ReqCondition qui renvoie true
lorsque la requête est destinée à "www.reddit.com".
La vérification d'égalité de l'hôte est insensible à la casse, pour refléter le comportement des
résolveurs DNS, donc même si l'utilisateur saisit "www.rEdDit.com", la comparaison
satisfaira la condition.
Lorsque l'heure est entre 8h00 et 17h59, nous retournons directement
une réponse dans DoFunc(), de sorte que la destination distante ne recevra pas la
requête et que le client recevra la réponse « Don't waste your time! ».
import "github.com/elazarl/goproxy"
Il y a quelques exemples d'utilisation du proxy dans le dossier examples, qui
couvrent les cas les plus courants. Jetez-y un œil et bonne chance !
Il existe 3 types différents de gestionnaires pour manipuler le comportement du proxy, comme suit :
// 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
Selon ce que vous souhaitez manipuler, voici les façons d'ajouter des gestionnaires à chacune des listes précédentes :
// 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())
Exemple :
// 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())
Si une erreur survient lors du traitement d'une requête via le proxy, par défaut
le proxy retourne une erreur HTTP 500 (Internal Server Error) avec le message d'erreur comme contenu du corps.
Si vous souhaitez remplacer ce comportement, vous pouvez définir votre propre
RespHandler qui modifie la réponse d'erreur.
Parmi les paramètres du contexte, ctx.Error contient l'erreur survenue,
le cas échéant, ou la valeur nil, si aucune erreur n'est survenue.
Vous pouvez la gérer comme vous le souhaitez, y compris en retournant un JSON personnalisé comme corps. Exemple de gestionnaire d'erreurs :
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
})
Si une erreur survient lors de l'envoi de données au serveur distant cible (ou au
client du proxy), le proxy.ConnectionErrHandler est appelé pour gérer
l'erreur, s'il est présent, sinon un gestionnaire par défaut sera utilisé.
L'erreur est passée en paramètre de fonction et non dans le contexte du proxy,
vous n'avez donc pas à vérifier le champ ctx.Error dans ce gestionnaire.
Dans ce gestionnaire, vous avez accès à la connexion brute avec le client
du proxy (en tant qu'io.Writer), vous pouvez donc envoyer toutes les données HTTP sur celle-ci,
si nécessaire, contenant les données d'erreur.
Il n'y a aucune garantie que la connexion n'ait pas déjà été fermée, donc
le Write() pourrait retourner une erreur.
La connexion sera automatiquement fermée par la bibliothèque du proxy après l'appel du
gestionnaire d'erreurs, vous n'avez donc pas à vous en soucier.
Ce projet a été créé il y a 10 ans, et a atteint un stade de
maturité. Il peut être utilisé en toute sécurité en production, et de nombreux projets
le font déjà.
Si un changement cassant survient à l'avenir, une nouvelle version du
module Go sera publiée (ex. v2).