goproxy

package module
v1.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 30, 2024 License: BSD-3-Clause Imports: 21 Imported by: 1,577

README

GoProxy

GoDoc Status

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.

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

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

Maintainers

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.

Versioning

With GitHub, there are 2 version types: release versions (such as v1.x) and Go branch default versions (such as v0.0.0-202412xxx). The latter is automatically assigned by Go modules command if the master branch contains new commits that aren't present in the last GitHub release.

We plan to periodically publish stable releases (v1.x), after a while that no one reports issues with the fixes merged in the master branch. You can safely use both version types, since we carefully review all the pull requests before merging them.

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

Documentation

Overview

Taken from $GOROOT/src/pkg/net/http/chunked needed to write https responses to client.

Package goproxy provides a customizable HTTP proxy, supporting hijacking HTTPS connection.

The intent of the proxy, is to be usable with reasonable amount of traffic yet, customizable and programmable.

The proxy itself is simply an `net/http` handler.

Typical usage is

proxy := goproxy.NewProxyHttpServer()
proxy.OnRequest(..conditions..).Do(..requesthandler..)
proxy.OnRequest(..conditions..).DoFunc(..requesthandlerFunction..)
proxy.OnResponse(..conditions..).Do(..responesHandler..)
proxy.OnResponse(..conditions..).DoFunc(..responesHandlerFunction..)
http.ListenAndServe(":8080", proxy)

Adding a header to each request

proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
	r.Header.Set("X-GoProxy","1")
	return r, nil
})

> Note that the function is called before the proxy sends the request to the server

For printing the content type of all incoming responses

proxy.OnResponse().DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
	println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
	return r
})

note that we used the ProxyCtx context variable here. It contains the request and the response (Req and Resp, Resp is nil if unavailable) of this specific client interaction with the proxy.

To print the content type of all responses from a certain url, we'll add a ReqCondition to the OnResponse function:

proxy.OnResponse(goproxy.UrlIs("golang.org/pkg")).DoFunc(func(r *http.Response, ctx *goproxy.ProxyCtx)*http.Response{
	println(ctx.Req.Host,"->",r.Header.Get("Content-Type"))
	return r
})

We can write the condition ourselves, conditions can be set on request and on response

var random = ReqConditionFunc(func(r *http.Request) bool {
	return rand.Intn(1) == 0
})
var hasGoProxyHeader = RespConditionFunc(func(resp *http.Response,req *http.Request)bool {
	return resp.Header.Get("X-GoProxy") != ""
})

Caution! If you give a RespCondition to the OnRequest function, you'll get a run time panic! It doesn't make sense to read the response, if you still haven't got it!

Finally, we have convenience function to throw a quick response

proxy.OnResponse(hasGoProxyHeader).DoFunc(func(r*http.Response,ctx *goproxy.ProxyCtx)*http.Response {
	r.Body.Close()
	return goproxy.NewResponse(
		ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, "Can't see response with X-GoProxy header!"
	)
})

we close the body of the original response, and return a new 403 response with a short message.

Example use cases:

1. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-avgsize

To measure the average size of an Html served in your site. One can ask all the QA team to access the website by a proxy, and the proxy will measure the average size of all text/html responses from your host.

2. [not yet implemented]

All requests to your web servers should be directed through the proxy, when the proxy will detect html pieces sent as a response to AJAX request, it'll send a warning email.

3. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-httpdump/

Generate a real traffic to your website by real users using through proxy. Record the traffic, and try it again for more real load testing.

4. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-no-reddit-at-worktime

Will allow browsing to reddit.com between 8:00am and 17:00pm

5. https://github.com/elazarl/goproxy/tree/master/examples/goproxy-jquery-version

Will warn if multiple versions of jquery are used in the same domain.

6. https://github.com/elazarl/goproxy/blob/master/examples/goproxy-upside-down-ternet/

Modifies image files in an HTTP response via goproxy's image extension found in ext/.

Index

Constants

View Source
const (
	ConnectAccept = iota
	ConnectReject
	ConnectMitm
	ConnectHijack
	ConnectHTTPMitm
	ConnectProxyAuthHijack
)
View Source
const (
	ContentTypeText = "text/plain"
	ContentTypeHtml = "text/html"
)

Variables

View Source
var (
	OkConnect       = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
	MitmConnect     = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
	HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
	RejectConnect   = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
)
View Source
var CA_CERT = []byte(`-----BEGIN CERTIFICATE-----
MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD
VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM
B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0
aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0
MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE
CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV
BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI
hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9
3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP
sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9
V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh
hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr
lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq
j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo
WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD
fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj
YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh
WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj
UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4
uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F
AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0
C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3
Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin
o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye
i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr
bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY
VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft
8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86
NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV
BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA==
-----END CERTIFICATE-----`)
View Source
var CA_KEY = []byte(`-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----`)
View Source
var ErrInvalidH2Frame = errors.New("invalid H2 frame")
View Source
var GoproxyCa tls.Certificate

Functions

func NewResponse

func NewResponse(r *http.Request, contentType string, status int, body string) *http.Response

Will generate a valid http response to the given request the response will have the given contentType, and http status. Typical usage, refuse to process requests to local addresses:

proxy.OnRequest(IsLocalHost()).DoFunc(func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request,*http.Response) {
	return nil,NewResponse(r,goproxy.ContentTypeHtml,http.StatusUnauthorized,
		`<!doctype html><html><head><title>Can't use proxy for local addresses</title></head><body/></html>`)
})

func RemoveProxyHeaders

func RemoveProxyHeaders(ctx *ProxyCtx, r *http.Request)

RemoveProxyHeaders removes all proxy headers which should not propagate to the next hop.

func TLSConfigFromCA

func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error)

func TextResponse

func TextResponse(r *http.Request, text string) *http.Response

Alias for NewResponse(r,ContentTypeText,http.StatusAccepted,text).

Types

type CertStorage

type CertStorage interface {
	Fetch(hostname string, gen func() (*tls.Certificate, error)) (*tls.Certificate, error)
}

type ConnectAction

type ConnectAction struct {
	Action    ConnectActionLiteral
	Hijack    func(req *http.Request, client net.Conn, ctx *ProxyCtx)
	TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error)
}

ConnectAction enables the caller to override the standard connect flow. When Action is ConnectHijack, it is up to the implementer to send the HTTP 200, or any other valid http response back to the client from within the Hijack func.

type ConnectActionLiteral

type ConnectActionLiteral int

type FuncHttpsHandler

type FuncHttpsHandler func(host string, ctx *ProxyCtx) (*ConnectAction, string)

A wrapper that would convert a function to a HttpsHandler interface type.

var AlwaysMitm FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
	return MitmConnect, host
}

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)
var AlwaysReject FuncHttpsHandler = func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
	return RejectConnect, host
}

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$"))).
	HandleConnect(goproxy.AlwaysReject)

func (FuncHttpsHandler) HandleConnect

func (f FuncHttpsHandler) HandleConnect(host string, ctx *ProxyCtx) (*ConnectAction, string)

FuncHttpsHandler should implement the RespHandler interface.

type FuncReqHandler

type FuncReqHandler func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)

A wrapper that would convert a function to a ReqHandler interface type.

func (FuncReqHandler) Handle

func (f FuncReqHandler) Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)

FuncReqHandler.Handle(req,ctx) <=> FuncReqHandler(req,ctx).

type FuncRespHandler

type FuncRespHandler func(resp *http.Response, ctx *ProxyCtx) *http.Response

A wrapper that would convert a function to a RespHandler interface type.

func (FuncRespHandler) Handle

func (f FuncRespHandler) Handle(resp *http.Response, ctx *ProxyCtx) *http.Response

FuncRespHandler.Handle(req,ctx) <=> FuncRespHandler(req,ctx).

type H2Transport

type H2Transport struct {
	ClientReader io.Reader
	ClientWriter io.Writer
	TLSConfig    *tls.Config
	Host         string
}

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.

func (*H2Transport) RoundTrip

func (r *H2Transport) RoundTrip(_ *http.Request) (*http.Response, error)

RoundTrip executes an HTTP/2 session (including all contained streams). The request and response are ignored but any error encountered during the proxying from the session is returned as a result of the invocation.

type HttpsHandler

type HttpsHandler interface {
	HandleConnect(req string, ctx *ProxyCtx) (*ConnectAction, string)
}

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. That is, the proxy will create a TLS connection with the client, another TLS connection with the destination the client wished to connect to, and would send back and forth all messages from the server to the client and vice versa. The request and responses sent in this Man In the Middle channel are filtered through the usual flow (request and response filtered through the ReqHandlers and RespHandlers).

type Logger

type Logger interface {
	Printf(format string, v ...any)
}

type ProxyConds

type ProxyConds struct {
	// contains filtered or unexported fields
}

ProxyConds is used to aggregate RespConditions for a ProxyHttpServer. Upon calling ProxyConds.Do, it will register a RespHandler that would handle the HTTP response from remote server if all conditions on the HTTP response are met.

func (*ProxyConds) Do

func (pcond *ProxyConds) Do(h RespHandler)

ProxyConds.Do will register the RespHandler on the proxy, h.Handle(resp,ctx) will be called on every request that matches the conditions aggregated in pcond.

func (*ProxyConds) DoFunc

func (pcond *ProxyConds) DoFunc(f func(resp *http.Response, ctx *ProxyCtx) *http.Response)

ProxyConds.DoFunc is equivalent to proxy.OnResponse().Do(FuncRespHandler(f)).

type ProxyCtx

type ProxyCtx struct {
	// Will contain the client request from the proxy
	Req *http.Request
	// Will contain the remote server's response (if available. nil if the request wasn't send yet)
	Resp         *http.Response
	RoundTripper RoundTripper
	// will contain the recent error that occurred while trying to send receive or parse traffic
	Error error
	// A handle for the user to keep data in the context, from the call of ReqHandler to the
	// call of RespHandler
	UserData any
	// Will connect a request to a response
	Session int64

	Proxy *ProxyHttpServer
	// contains filtered or unexported fields
}

ProxyCtx is the Proxy context, contains useful information about every request. It is passed to every user function. Also used as a logger.

func (*ProxyCtx) Charset

func (ctx *ProxyCtx) Charset() string

Will try to infer the character set of the request from the headers. Returns the empty string if we don't know which character set it used. Currently it will look for charset=<charset> in the Content-Type header of the request.

func (*ProxyCtx) Logf

func (ctx *ProxyCtx) Logf(msg string, argv ...any)

Logf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter This message will be printed only if the Verbose field of the ProxyHttpServer is set to true

proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
	nr := atomic.AddInt32(&counter,1)
	ctx.Printf("So far %d requests",nr)
	return r, nil
})

func (*ProxyCtx) RoundTrip

func (ctx *ProxyCtx) RoundTrip(req *http.Request) (*http.Response, error)

func (*ProxyCtx) Warnf

func (ctx *ProxyCtx) Warnf(msg string, argv ...any)

Warnf prints a message to the proxy's log. Should be used in a ProxyHttpServer's filter This message will always be printed.

proxy.OnRequest().DoFunc(func(r *http.Request,ctx *goproxy.ProxyCtx) (*http.Request, *http.Response){
	f,err := os.OpenFile(cachedContent)
	if err != nil {
		ctx.Warnf("error open file %v: %v",cachedContent,err)
		return r, nil
	}
	return r, nil
})

type ProxyHttpServer

type ProxyHttpServer struct {

	// KeepDestinationHeaders indicates the proxy should retain any headers present in the http.Response before proxying
	KeepDestinationHeaders bool
	// setting Verbose to true will log information on each request sent to the proxy
	Verbose         bool
	Logger          Logger
	NonproxyHandler http.Handler

	Tr *http.Transport
	// ConnectionErrHandler will be invoked to return a custom response
	// to clients (written using conn parameter), when goproxy fails to connect
	// to a target proxy.
	// The error is passed as function parameter and not inside the proxy
	// context, to avoid race conditions.
	ConnectionErrHandler func(conn io.Writer, ctx *ProxyCtx, err error)
	// ConnectDial will be used to create TCP connections for CONNECT requests
	// if nil Tr.Dial will be used
	ConnectDial        func(network string, addr string) (net.Conn, error)
	ConnectDialWithReq func(req *http.Request, network string, addr string) (net.Conn, error)
	CertStore          CertStorage
	KeepHeader         bool
	AllowHTTP2         bool
	// KeepAcceptEncoding, if true, prevents the proxy from dropping
	// Accept-Encoding headers from the client.
	//
	// Note that the outbound http.Transport may still choose to add
	// Accept-Encoding: gzip if the client did not explicitly send an
	// Accept-Encoding header. To disable this behavior, set
	// Tr.DisableCompression to true.
	KeepAcceptEncoding bool
	// contains filtered or unexported fields
}

The basic proxy type. Implements http.Handler.

func NewProxyHttpServer

func NewProxyHttpServer() *ProxyHttpServer

NewProxyHttpServer creates and returns a proxy server, logging to stderr by default.

func (*ProxyHttpServer) NewConnectDialToProxy

func (proxy *ProxyHttpServer) NewConnectDialToProxy(httpsProxy string) func(network, addr string) (net.Conn, error)

func (*ProxyHttpServer) NewConnectDialToProxyWithHandler

func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(
	httpsProxy string,
	connectReqHandler func(req *http.Request),
) func(network, addr string) (net.Conn, error)

func (*ProxyHttpServer) OnRequest

func (proxy *ProxyHttpServer) OnRequest(conds ...ReqCondition) *ReqProxyConds

ProxyHttpServer.OnRequest Will return a temporary ReqProxyConds struct, aggregating the given condtions. You will use the ReqProxyConds struct to register a ReqHandler, that would filter the request, only if all the given ReqCondition matched. Typical usage:

proxy.OnRequest(UrlIs("example.com/foo"),UrlMatches(regexp.MustParse(`.*\.exampl.\com\./.*`)).Do(...)

func (*ProxyHttpServer) OnResponse

func (proxy *ProxyHttpServer) OnResponse(conds ...RespCondition) *ProxyConds

OnResponse is used when adding a response-filter to the HTTP proxy, usual pattern is

proxy.OnResponse(cond1,cond2).Do(handler) // handler.Handle(resp,ctx) will be used
			// if cond1.HandleResp(resp) && cond2.HandleResp(resp)

func (*ProxyHttpServer) ServeHTTP

func (proxy *ProxyHttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

Standard net/http function. Shouldn't be used directly, http.Serve will use it.

type ReqCondition

type ReqCondition interface {
	RespCondition
	HandleReq(req *http.Request, ctx *ProxyCtx) bool
}

ReqCondition.HandleReq will decide whether or not to use the ReqHandler on an HTTP request before sending it to the remote server.

func SrcIpIs

func SrcIpIs(ips ...string) ReqCondition

SrcIpIs returns a ReqCondition testing whether the source IP of the request is one of the given strings.

type ReqConditionFunc

type ReqConditionFunc func(req *http.Request, ctx *ProxyCtx) bool

ReqConditionFunc.HandleReq(req,ctx) <=> ReqConditionFunc(req,ctx).

var IsLocalHost ReqConditionFunc = func(req *http.Request, ctx *ProxyCtx) bool {
	h := req.URL.Hostname()
	if h == "localhost" {
		return true
	}
	if ip := net.ParseIP(h); ip != nil {
		return ip.IsLoopback()
	}

	if ip := net.ParseIP(req.URL.Host); ip != nil {
		return ip.IsLoopback()
	}

	return false
}

IsLocalHost checks whether the destination host is localhost.

func DstHostIs

func DstHostIs(host string) ReqConditionFunc

DstHostIs returns a ReqCondition testing wether the host in the request url is the given string.

func Not

Not returns a ReqCondition negating the given ReqCondition.

func ReqHostIs

func ReqHostIs(hosts ...string) ReqConditionFunc

ReqHostIs returns a ReqCondition, testing whether the host to which the request is directed to equal to one of the given strings.

func ReqHostMatches

func ReqHostMatches(regexps ...*regexp.Regexp) ReqConditionFunc

ReqHostMatches returns a ReqCondition, testing whether the host to which the request was directed to matches any of the given regular expressions.

func UrlHasPrefix

func UrlHasPrefix(prefix string) ReqConditionFunc

UrlHasPrefix returns a ReqCondition checking wether the destination URL the proxy client has requested has the given prefix, with or without the host. For example UrlHasPrefix("host/x") will match requests of the form 'GET host/x', and will match requests to url 'http://host/x'

func UrlIs

func UrlIs(urls ...string) ReqConditionFunc

UrlIs returns a ReqCondition, testing whether or not the request URL is one of the given strings with or without the host prefix. UrlIs("google.com/","foo") will match requests 'GET /' to 'google.com', requests `'GET google.com/' to any host, and requests of the form 'GET foo'.

func UrlMatches

func UrlMatches(re *regexp.Regexp) ReqConditionFunc

UrlMatches returns a ReqCondition testing whether the destination URL of the request matches the given regexp, with or without prefix.

func (ReqConditionFunc) HandleReq

func (c ReqConditionFunc) HandleReq(req *http.Request, ctx *ProxyCtx) bool

func (ReqConditionFunc) HandleResp

func (c ReqConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool

ReqConditionFunc cannot test responses. It only satisfies RespCondition interface so that to be usable as RespCondition.

type ReqHandler

type ReqHandler interface {
	Handle(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response)
}

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. If it returns nil,resp the proxy will skip sending any requests, and will simply return the response `resp` to the client.

type ReqProxyConds

type ReqProxyConds struct {
	// contains filtered or unexported fields
}

ReqProxyConds aggregate ReqConditions for a ProxyHttpServer. Upon calling Do, it will register a ReqHandler that would handle the request if all conditions on the HTTP request are met.

func (*ReqProxyConds) Do

func (pcond *ReqProxyConds) Do(h ReqHandler)

ReqProxyConds.Do will register the ReqHandler on the proxy, the ReqHandler will handle the HTTP request if all the conditions aggregated in the ReqProxyConds are met. Typical usage:

proxy.OnRequest().Do(handler) // will call handler.Handle(req,ctx) on every request to the proxy
proxy.OnRequest(cond1,cond2).Do(handler)
// given request to the proxy, will test if cond1.HandleReq(req,ctx) && cond2.HandleReq(req,ctx) are true
// if they are, will call handler.Handle(req,ctx)

func (*ReqProxyConds) DoFunc

func (pcond *ReqProxyConds) DoFunc(f func(req *http.Request, ctx *ProxyCtx) (*http.Request, *http.Response))

DoFunc is equivalent to proxy.OnRequest().Do(FuncReqHandler(f)).

func (*ReqProxyConds) HandleConnect

func (pcond *ReqProxyConds) HandleConnect(h HttpsHandler)

HandleConnect is used when proxy receives an HTTP CONNECT request, it'll then use the HttpsHandler to determine what should it do with this request. The handler returns a ConnectAction struct, the Action field in the ConnectAction struct returned will determine what to do with this request. ConnectAccept will simply accept the request forwarding all bytes from the client to the remote host, ConnectReject will close the connection with the client, and ConnectMitm, will assume the underlying connection is an HTTPS connection, and will use Man in the Middle attack to eavesdrop the connection. All regular handler will be active on this eavesdropped connection. The ConnectAction struct contains possible tlsConfig that will be used for eavesdropping. If nil, the proxy will use the default tls configuration.

proxy.OnRequest().HandleConnect(goproxy.AlwaysReject) // rejects all CONNECT requests

func (*ReqProxyConds) HandleConnectFunc

func (pcond *ReqProxyConds) HandleConnectFunc(f func(host string, ctx *ProxyCtx) (*ConnectAction, string))

HandleConnectFunc is equivalent to HandleConnect, for example, accepting CONNECT request if they contain a password in header

io.WriteString(h,password)
passHash := h.Sum(nil)
proxy.OnRequest().HandleConnectFunc(func(host string, ctx *ProxyCtx) (*ConnectAction, string) {
	c := sha1.New()
	io.WriteString(c,ctx.Req.Header.Get("X-GoProxy-Auth"))
	if c.Sum(nil) == passHash {
		return OkConnect, host
	}
	return RejectConnect, host
})

func (*ReqProxyConds) HijackConnect

func (pcond *ReqProxyConds) HijackConnect(f func(req *http.Request, client net.Conn, ctx *ProxyCtx))

type RespCondition

type RespCondition interface {
	HandleResp(resp *http.Response, ctx *ProxyCtx) bool
}

RespCondition.HandleReq will decide whether or not to use the RespHandler on an HTTP response before sending it to the proxy client. Note that resp might be nil, in case there was an error sending the request.

func ContentTypeIs

func ContentTypeIs(typ string, types ...string) RespCondition

ContentTypeIs returns a RespCondition testing whether the HTTP response has Content-Type header equal to one of the given strings.

func StatusCodeIs

func StatusCodeIs(codes ...int) RespCondition

StatusCodeIs returns a RespCondition, testing whether or not the HTTP status code is one of the given ints.

type RespConditionFunc

type RespConditionFunc func(resp *http.Response, ctx *ProxyCtx) bool

RespConditionFunc.HandleResp(resp,ctx) <=> RespConditionFunc(resp,ctx).

func (RespConditionFunc) HandleResp

func (c RespConditionFunc) HandleResp(resp *http.Response, ctx *ProxyCtx) bool

type RespHandler

type RespHandler interface {
	Handle(resp *http.Response, ctx *ProxyCtx) *http.Response
}

after the proxy have sent the request to the destination server, it will "filter" the response through the RespHandlers it has. The proxy server will send to the client the response returned by the RespHandler. In case of error, resp will be nil, and ctx.RoundTrip.Error will contain the error.

func HandleBytes

func HandleBytes(f func(b []byte, ctx *ProxyCtx) []byte) RespHandler

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.

type RoundTripper

type RoundTripper interface {
	RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)
}

type RoundTripperFunc

type RoundTripperFunc func(req *http.Request, ctx *ProxyCtx) (*http.Response, error)

func (RoundTripperFunc) RoundTrip

func (f RoundTripperFunc) RoundTrip(req *http.Request, ctx *ProxyCtx) (*http.Response, error)

Directories

Path Synopsis
ext module
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL