Categorygithub.com/elazarl/goproxy
modulepackage
1.7.0
Repository: https://github.com/elazarl/goproxy.git
Documentation: pkg.go.dev

# README

GoProxy

Status GoDoc Go Report BSD-3 License Pull Requests Awesome Go

GoProxy is a library to create a customized HTTP/HTTPS proxy server using Go (aka Golang), with several configurable settings available. The target of this project is to offer an optimized proxy server, usable with reasonable amount of traffic, yet customizable and programmable.

The proxy itself is simply a net/http handler, so you can add multiple middlewares (panic recover, logging, compression, etc.) over it. It can be easily integrated with any other HTTP network library.

In order to use goproxy, one should set their browser (or any other client) to use goproxy as an HTTP proxy. Here is how you do that in Chrome and in Firefox. If you decide to start with the base example, the URL you should use as proxy is localhost:8080, which is the default one in our example. You also have to trust the proxy CA certificate, to avoid any certificate issue in the clients.

✈️ Telegram Group

Features

  • Perform certain actions only on specific hosts, with a single equality comparison or with regex evaluation
  • Manipulate requests and responses before sending them to the browser
  • Use a custom http.Transport to perform requests to the target server
  • You can specify a MITM certificates cache, to reuse them later for other requests to the same host, thus saving CPU. Not enabled by default, but you should use it in production!
  • Redirect normal HTTP traffic to a custom handler, when the target is a relative path (e.g. /ping)
  • You can choose the logger to use, by implementing the Logger interface
  • You can disable the HTTP request headers canonicalization, by setting PreventCanonicalization to true

Proxy modes

  1. Regular HTTP proxy
  2. HTTPS through CONNECT
  3. HTTPS MITM ("Man in the Middle") proxy server, in which the server generate TLS certificates to parse request/response data and perform actions on them
  4. "Hijacked" proxy connection, where the configured handler can access the raw net.Conn data

Sponsors

Does your company use GoProxy? Ask your manager or marketing team if your company would be interested in supporting our project. Supporting this project will allow the maintainers to dedicate more time for maintenance and new features for everyone. This will also benefit you, because maintainers will fix problems that will occur and keep GoProxy up to date for your projects. Moreover, your company logo will be shown on GitHub, in this README section.

Apply Here

GoProxy Sponsor GoProxy Sponsor GoProxy Sponsor GoProxy Sponsor

Maintainers

If you need to integrate GoProxy into your project, or you need some custom features to maintain in your fork, you can contact Erik (the current maintainer) by email, and you can discuss together how he can help you as a paid independent consultant.

Contributions

If you have any trouble, suggestion, or if you find a bug, feel free to reach out by opening a GitHub issue. This is an open source project managed by volunteers, and we're happy to discuss anything that can improve it.

Make sure to explain everything, including the reason behind the issue and what you want to change, to make the problem easier to understand. You can also directly open a Pull Request, if it's a small code change, but you need to explain in the description everything. If you open a pull request named refactoring with 5,000 lines changed, we won't merge it... :D

The code for this project is released under the BSD 3-Clause license, making it useful for commercial uses as well.

Submit your case study

So, you have introduced & integrated GoProxy into one of your personal projects or a project inside the company you work for.

We're happy to learn about new creative solutions made with this library, so feel free to contact the maintainer listed above via e-mail, to explaining why you found this project useful for your needs.

If you have signed a Non Disclosure Agreement with the company, you can propose them to write a blog post on their official website about this topic, so this information will be public by their choice, and you can share the link of the blog post with us :)

The purpose of case studies is to share with the community why all the contributors to this project are improving the world with their help and what people are building using it.

Linter

The codebase uses an automatic lint check over your Pull Request code. Before opening it, you should check if your changes respect it, by running the linter in your local machine, so you won't have any surprise.

To install the linter:

go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

This will create an executable in your $GOPATH/bin folder ($GOPATH is an environment variable, usually its value is equivalent to ~/go, check its value in your machine if you aren't sure about it). Make sure to include the bin folder in the path of your shell, to be able to directly use the golangci-lint run command.

A taste of GoProxy

To get a taste of goproxy, here you are a basic HTTP/HTTPS proxy that just forward data to the 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))
}

Request handler

This line will add X-GoProxy: yxorPoG-X header to all requests sent through the proxy, before sending them to the 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
    })

When the OnRequest() input is empty, the function specified in DoFunc will process all incoming requests to the proxy. In this case, it will add a header to the request and return it to the caller. The proxy will send the modified request to the destination. You can also use Do instead of DoFunc, if you implement the specified interface in your type.

⚠️ Note we returned a nil value as the response. If the returned response is not nil, goproxy will discard the request and send the specified response to the client.

Conditional Request handler

Refuse connections to www.reddit.com between 8 and 17 in the server local timezone:

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(r, goproxy.ContentTypeText, http.StatusForbidden, "Don't waste your time!")
            return req, resp
        }
        return req,nil
})

DstHostIs returns a ReqCondition, which is a function receiving a *http.Request and returning a boolean that checks if the request satisfies the condition (and that will be processed). DstHostIs("www.reddit.com") will return a ReqCondition that returns true when the request is directed to "www.reddit.com". The host equality check is case-insensitive, to reflect the behaviour of DNS resolvers, so even if the user types "www.rEdDit.com", the comparison will satisfy the condition. When the hour is between 8:00am and 5:59pm, we directly return a response in DoFunc(), so the remote destination will not receive the request and the client will receive the "Don't waste your time!" response.

Let's start

import "github.com/elazarl/goproxy"

There are some proxy usage examples in the examples folder, which cover the most common cases. Take a look at them and good luck!

Request & Response manipulation

There are 3 different types of handlers to manipulate the behavior of the proxy, as follows:

// 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 

Depending on what you want to manipulate, the ways to add handlers to each of the previous lists are:

// 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())

Example:

// 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())

Error handling

Generic error

If an error occurs while handling a request through the proxy, by default the proxy returns HTTP error 500 (Internal Server Error) with the error message as the body content.

If you want to override this behaviour, you can define your own RespHandler that changes the error response. Among the context parameters, ctx.Error contains the error occurred, if any, or the nil value, if no error happened.

You can handle it as you wish, including returning a custom JSON as the body. Example of an error handler:

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
})

Connection error

If an error occurs while sending data to the target remote server (or to the proxy client), the proxy.ConnectionErrHandler is called to handle the error, if present, else a default handler will be used. The error is passed as function parameter and not inside the proxy context, so you don't have to check the ctx.Error field in this handler.

In this handler you have access to the raw connection with the proxy client (as an io.Writer), so you could send any HTTP data over it, if needed, containing the error data. There is no guarantee that the connection hasn't already been closed, so the Write() could return an error.

The connection will be automatically closed by the proxy library after the error handler call, so you don't have to worry about it.

Project Status

This project has been created 10 years ago, and has reached a stage of maturity. It can be safely used in production, and many projects already do that.

If there will be any breaking change in the future, a new version of the Go module will be released (e.g. v2).

Trusted, as a direct dependency, by:

Stripe Dependabot Go Git Google Grafana Fly.io Kubernetes / Minikube New Relic

# Packages

No description provided by the author
No description provided by the author

# Functions

ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal to one of the given strings.
DstHostIs returns a ReqCondition testing wether the host in the request url is the given string.
HandleBytes will return a RespHandler that read the entire body of the request to a byte array in memory, would run the user supplied f function on the byte arra, and will replace the body of the original response with the resulting byte array.
NewProxyHttpServer creates and returns a proxy server, logging to stderr by default.
Will generate a valid http response to the given request the response will have the given contentType, and http status.
Not returns a ReqCondition negating the given ReqCondition.
RemoveProxyHeaders removes all proxy headers which should not propagate to the next hop.
ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal to one of the given strings.
ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches any of the given regular expressions.
SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings.
StatusCodeIs returns a RespCondition, testing whether or not the HTTP status code is one of the given ints.
Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text).
No description provided by the author
UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested has the given prefix, with or without the host.
UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings with or without the host prefix.
UrlMatches returns a ReqCondition testing whether the destination URL of the request matches the given regexp, with or without prefix.

# Constants

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Variables

AlwaysMitm is a HttpsHandler that always eavesdrop https connections, for example to eavesdrop all https connections to www.google.com, we can use proxy.OnRequest(goproxy.ReqHostIs("www.google.com")).HandleConnect(goproxy.AlwaysMitm).
AlwaysReject is a HttpsHandler that drops any CONNECT request, for example, this code will disallow connections to hosts on any other port than 443 proxy.OnRequest(goproxy.Not(goproxy.ReqHostMatches(regexp.MustCompile(":443$"))).
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
IsLocalHost checks whether the destination host is localhost.
No description provided by the author
No description provided by the author
No description provided by the author

# Structs

ConnectAction enables the caller to override the standard connect flow.
H2Transport is an implementation of RoundTripper that abstracts an entire HTTP/2 session, sending all client frames to the server and responses back to the client.
ProxyConds is used to aggregate RespConditions for a ProxyHttpServer.
ProxyCtx is the Proxy context, contains useful information about every request.
The basic proxy type.
ReqProxyConds aggregate ReqConditions for a ProxyHttpServer.

# Interfaces

No description provided by the author
When a client send a CONNECT request to a host, the request is filtered through all the HttpsHandlers the proxy has, and if one returns true, the connection is sniffed using Man in the Middle attack.
No description provided by the author
ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request before sending it to the remote server.
ReqHandler will "tamper" with the request coming to the proxy server If Handle returns req,nil the proxy will send the returned request to the destination server.
RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response before sending it to the proxy client.
after the proxy have sent the request to the destination server, it will "filter" the response through the RespHandlers it has.
No description provided by the author

# Type aliases

No description provided by the author
A wrapper that would convert a function to a HttpsHandler interface type.
A wrapper that would convert a function to a ReqHandler interface type.
A wrapper that would convert a function to a RespHandler interface type.
ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx).
RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx).
No description provided by the author