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.
- Perform certain actions only on
specific hosts
, with a single equality comparison or with regex evaluation - Manipulate
requests
andresponses
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 arelative path
(e.g./ping
) - You can choose the logger to use, by implementing the
Logger
interface
- Regular HTTP proxy
- HTTPS through CONNECT
- 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
- "Hijacked" proxy connection, where the configured handler can access the raw net.Conn data
- Elazar Leibovich: Creator of the project, Software Engineer
- Erik Pellizzon: Maintainer, Freelancer (open to collaborations!)
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.
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))
}
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.
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.
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!
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())
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).