Writing a tiny reverse proxy in golang

I faced an unusal case at work where I needed to write a reverse proxy that would inject a specific header read from a file to handle the request. While I do not write professionally in Go, I thought it would be good enough for the task at hand. I did not know where to start and googling did show some other efforts, so I asked at Golangs’s Slack. When in doubt always ask. Hopefully someone will pick it up and answer. I was pointed to NewSingleHostReverseProxy. So the simplest reverse proxy in Go can look like this:

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

func main() {
	target, _ := url.Parse("https://api.chucknorris.io")
	proxy := httputil.NewSingleHostReverseProxy(target)
	http.Handle("/", proxy)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

This is the simplest reverse proxy and if you call it with curl -H 'Host: api.chucknorris.io' http://localhost:8080/jokes/random you are going to get back a joke.

But say we want to add a header, what do we do? We can define a Director function in our program that will allow us to do so:

proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Director = func(req *http.Request) {
	req.Header.Set("X-Foo", "Bar")
}

Now trying the same curl command we get the error 2024/11/17 19:41:53 http: proxy error: unsupported protocol scheme "". That is because by defining the Director function, we have lost the previous one and we have to rebuild the scheme (and a couple of other headers after). There is though a better way to do this, by keeping track of the previous default Director:

	proxy := httputil.NewSingleHostReverseProxy(target)
	d := proxy.Director
	proxy.Director = func(req *http.Request) {
		d(req)
		req.Header.Set("X-Foo", "Bar")
	}

And now you are set. I am certain there are better and more performant ways to do this, but for the PoC I was working on, that was good enough.

[*] I used api.chucknorris.io for the blog post because it is one of the simplest open APIs out there to try stuff.
[**] The complete reverse proxy, taking care of error handling and environment variables is 65 lines.

Leave a comment