Leave your programming language hang ups at the door and come admire the best standard library I’ve ever come across. This is all the code you actually require… Choosing a Programming Language for a project shouldn’t be like declaring who your favourite team is. it should be a matter of pragmatism and choosing the right tool for the right job. In this post I want to show you when and why Go shines as a language. Specifically I want to show you how rock solid their standard lib is for basic internet programming. Even more specifically… were gonna write a Reverse Proxy! “Go has a lot going for it but where it really struts it stuff is in these lower level network plumbing tasks, there’s no better language.” What is a reverse proxy? I get a request send from a client, send that request to another server, receive a response from the server and forward it back to the client. The reverse part of this simply means the proxy itself determines where to send traffic and when A big fancy way of saying a traffic forwarder. (Just beautiful 😍…) Why is it useful? Because the concept is so simple it can be applied to assist in many different cases: Load balancing, A/B Testing, Caching, Authentication etc… By the end of this short post you will have learned how to: Serve HTTP requests Parse the body of a request Serve traffic to another server using a Reverse Proxy Our Reverse Proxy Project Lets dive into the actual project. What we are going to do is have a web server that: 1. Takes requests 2. Reads the body of a request, specifically the proxy_condition field 3. If the proxy domain is equal to A send traffic to URL 1 4. If the proxy domain is equal to B send traffic to URL 2 5. If the proxy domain is neither then send traffic to the Default URL. Prerequisites for programming with. Go for creating simple servers with. http-server Setting up our environment First thing we want to do is input all the required configuration variables into our environment so that we can both use them in our application while keeping them out of source code. I find the best approach is to create a .env file that contains the desired environment variables. Below is what I have for this specific project: PORT= A_CONDITION_URL= B_CONDITION_URL= DEFAULT_CONDITION_URL= export 1330 export "http://localhost:1331" export "http://localhost:1332" export "http://localhost:1333" This is a habit I picked up from the 12 Factor App After you save your file you can run: .env . source env to configure load the config into your environment any time. Laying the foundation of our project Next lets create a file called that does the following: main.go 1. When started logs the , , , and environment variables to the console PORT A_CONDITION_URL B_CONDITION_URL DEFAULT_CONDITION_URL 2. Listen for requests on the path: / package main ( ) func getEnv(key, fallback string) string { value, := os.LookupEnv(key); ok { value } fallback } func getListenAddress() string { := getEnv( , ) + port } func logSetup() { := os.Getenv( ) b_condtion_url := os.Getenv( ) default_condtion_url := os.Getenv( ) log.Printf( , getListenAddress()) log.Printf( , a_condtion_url) log.Printf( , b_condtion_url) log.Printf( , default_condtion_url) } func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { } func main() { logSetup() http.HandleFunc( , handleRequestAndRedirect) err := http.ListenAndServe(getListenAddress(), nil); err != nil { panic(err) } } import "bytes" "encoding/json" "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" "os" "strings" // Get env var or default if ok return return // Get the port to listen on port "PORT" "1338" return ":" // Log the env variables required for a reverse proxy a_condtion_url "A_CONDITION_URL" "B_CONDITION_URL" "DEFAULT_CONDITION_URL" "Server will run on: %s\n" "Redirecting to A url: %s\n" "Redirecting to B url: %s\n" "Redirecting to Default url: %s\n" // Given a request send it to the appropriate url // We will get to this... // Log setup values // start server "/" if (💀Let’s get the skeletons out of the closet so we can move onto the fun stuff.) Now you should be able to run Parse the request body Now that we have the skeleton of our project together we want to start creating the logic that will handle parsing the request body. Start by updating to parse the value from the request body. handleRequestAndRedirect proxy_condition type requestPayloadStruct struct { ProxyCondition string } func requestBodyDecoder(request *http.Request) *json.Decoder { body, := ioutil.ReadAll(request.Body) err != nil { log.Printf( , err) panic(err) } request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) json.NewDecoder(ioutil.NopCloser(bytes.NewBuffer(body))) } func parseRequestBody(request *http.Request) requestPayloadStruct { := requestBodyDecoder(request) requestPayload requestPayloadStruct err := decoder.Decode(&requestPayload) err != nil { panic(err) } requestPayload } func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { := parseRequestBody(req) } `json:"proxy_condition"` // Get a json decoder for a given requests body // Read body to buffer err if "Error reading body: %v" // Because go lang is a pain in the ass if you read the body then any susequent calls // are unable to read the body again.... return // Parse the requests body decoder var if return // Given a request send it to the appropriate url requestPayload // ... more to come (Basic parsing of a JSON blob to a struct in Go.) Use to determine where we send traffic proxy_condition Now that we have the value of the from the request we will use it to decide where we direct our reverse proxy to. proxy_condition Remember from earlier that we have three cases: If is equal to then we send traffic to proxy_condition A A_CONDITION_URL If is equal to then we send traffic to proxy_condition B B_CONDITION_URL Else send traffic to DEFAULT_CONDITION_URL func logRequestPayload(requestionPayload requestPayloadStruct, proxyUrl string) { log.Printf( , requestionPayload.ProxyCondition, proxyUrl) } func getProxyUrl(proxyConditionRaw string) string { := strings.ToUpper(proxyConditionRaw) a_condtion_url := os.Getenv( ) b_condtion_url := os.Getenv( ) default_condtion_url := os.Getenv( ) proxyCondition == { a_condtion_url } proxyCondition == { b_condtion_url } default_condtion_url } func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { := parseRequestBody(req) url := getProxyUrl(requestPayload.ProxyCondition) logRequestPayload(requestPayload, url) } // Log the typeform payload and redirect url "proxy_condition: %s, proxy_url: %s\n" // Get the url for a given proxy condition proxyCondition "A_CONDITION_URL" "B_CONDITION_URL" "DEFAULT_CONDITION_URL" if "A" return if "B" return return // Given a request send it to the appropriate url requestPayload // more still to come... Reverse Proxy to that URL Finally we are onto the actual reverse proxy! In so many languages a reverse proxy would require a lot of thought and a fair amount of code or at least having to import a sophisticated library. However Golang’s standard library makes creating a reverse proxy so simple it’s almost unbelievable. Below is essentially the only line of code you need: httputil. . NewSingleHostReverseProxy( ) url ServeHTTP( , ) res req Note that in the following code we add a little extra so it can fully support SSL redirection (though not necessary): func serveReverseProxy(target string, res http.ResponseWriter, req *http.Request) { url, := url.Parse(target) proxy := httputil.NewSingleHostReverseProxy(url) req.URL.Host = url.Host req.URL.Scheme = url.Scheme req.Header.Set( , req.Header.Get( )) req.Host = url.Host proxy.ServeHTTP(res, req) } func handleRequestAndRedirect(res http.ResponseWriter, req *http.Request) { := parseRequestBody(req) url := getProxyUrl(requestPayload.ProxyCondition) logRequestPayload(requestPayload, url) serveReverseProxy(url, res, req) } // Serve a reverse proxy for a given url // parse the url _ // create the reverse proxy // Update the headers to allow for SSL redirection "X-Forwarded-Host" "Host" // Note that ServeHttp is non blocking and uses a go routine under the hood // Given a request send it to the appropriate url requestPayload The one time in the project it felt like Go was truly getting out of my way.Start it all up Ok now that we have this all wired up setup our application on port and our 3 simple servers on ports (all in separate terminals): 1330 1331–1333 source .env && go install && $GOPATH/bin/reverse-proxy-demo http-server -p 1331 http-server -p 1332 http-server -p 1333 With all these up and ruuning we can start to send through a requests with a json body in another terminal like so: curl --request GET \ --url http: --header \ --data //localhost:1330/ \ 'content-type: application/json' '{ "proxy_condition": "a" }' If your looking for a great HTTP request client I cannot recommend Insomnia enough. and Voila we can start to see our reverse proxy directing traffic to one of our 3 servers based on what we set in the field! proxy_condition (Its alive!!!) Wrap Up Go has a lot going for it but where it really struts it stuff is in these lower level network “plumbing” tasks, there’s no better language. What we’ve written here is simple, performant, reliable and very much ready for use in production. For simple services I can see myself reaching for Go again in the future. 🧞 This is open source! you can find it here on Github ❤️ I only write about programming and remote work. If you follow me on Twitter I won’t waste your time.