Building proxies

What I learned so far (and some musings) about building proxies in Go.

It’s pretty easy to start building a proxy in Go. The most simple example to create a (reverse) proxy looks something like:

proxy := httputil.NewSingleHostReverseProxy(targetURL)

But one thing that’s not obvious to me yet, is the best way to work with upstreams (i.e. targets to proxy to) that are not known beforehand.

For example, Caddy has support for dynamic upstreams. But it looks like you do need to known them beforehand?

So I’m not sure yet what the “best practice approach” is to proxy to a different target for different requests (e.g. performance wise). But I guess it depends on the exact use-case(s).

I did learn that you can use httputil.ReverseProxy and Director to do something more custom per request:

func NewProxy() *httputil.ReverseProxy {
	return &httputil.ReverseProxy{
		Transport: &http.Transport{
			IdleConnTimeout:     2 * time.Minute,
			MaxIdleConnsPerHost: 32,
			MaxIdleConns:        100,
		},
		Director: func(req *http.Request) {
			var rawTarget string
			if t, ok := FromTargetContext(req.Context()); ok {
				rawTarget = t
			}
			if rawTarget != "" {
				if target, err := url.Parse(rawTarget); err != nil {
					// Do nothing?
				} else {
					req.Host = target.Host
					req.URL.Scheme = target.Scheme
					req.URL.Host = target.Host
					req.URL.Path = target.Path
					req.URL.RawPath = target.EscapedPath()
				}
			}
		},
	}
}

For example, by using the request context (FromTargetContext). But this doesn’t feel great (haven’t explored how performance looks like when using this yet though).

Maybe it’s better to implement a “non-standard” (i.e. not using ServeHTTP) handler, and just pass extra information to it?

Something like:

customProxy.ProxyHTTP(w http.ResponseWriter, r *http.Request, targetURL string)

Note

Resources