Writing a Simple Reverse Proxy in Go

Writing a reverse proxy in Go is an absolute delight thanks to the httputil package’s NewSingleHostReverseProxy function.

package main

import (
    "crypto/tls"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
    "time"
)

var (
    targetUrl *url.URL
)

func main() {
    var err error
    targetUrl, err = url.Parse("https://url-to-direct/")
    if err != nil {
        panic(err)
    }

    handler := http.NewServeMux()
    handler.HandleFunc("/", proxyHandler)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      handler,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  15 * time.Second,
    }
    server.SetKeepAlivesEnabled(true)
    log.Printf("Listening on %s\n", server.Addr)
    log.Fatal(server.ListenAndServe())
}

func proxyHandler(writer http.ResponseWriter, request *http.Request) {
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    httputil.NewSingleHostReverseProxy(targetUrl).ServeHTTP(writer, request)
}

Note that if there’s a reverse proxy (i.e. Nginx, Traefik, Ambassador, etc.) on the receiving end, you will most likely also need to override the request host to the target host:

func proxyHandler(writer http.ResponseWriter, request *http.Request) {
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    request.Host = targetUrl.Host
    httputil.NewSingleHostReverseProxy(targetUrl).ServeHTTP(writer, request)
}

If you want to expose the proxied service through a specific path on your proxy rather than exposing it on /, then you could do something like this:

handler.HandleFunc("/external-service/", externalServiceHandler)

// ...

func externalServiceHandler(writer http.ResponseWriter, request *http.Request) {
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    request.Host = targetUrl.Host
    request.URL.Path = strings.TrimPrefix(request.URL.Path, "/external-service")
    httputil.NewSingleHostReverseProxy(targetUrl).ServeHTTP(writer, request)
}

Keep in mind that while this shouldn’t cause any problems if you’re using a proxy to reach an API, it’s very likely that you’ll run into weird issues if you try to proxy to a front-end application due to absolute and relative paths.