Skip to content

Go Server Proxy

See ../integrations/go.md for full code. This file: deployment patterns + gotchas.

net/http server

go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

var (
	key    = os.Getenv("MFD_SRV_KEY")
	secret = []byte(os.Getenv("MFD_SRV_SECRET"))
	upstream = "https://tiles.mapsfordevs.com"
)

func sign(method, path string) (string, string) {
	ts := strconv.FormatInt(time.Now().Unix(), 10)
	mac := hmac.New(sha256.New, secret)
	fmt.Fprintf(mac, "%s\n%s\n%s", method, path, ts)
	return ts, hex.EncodeToString(mac.Sum(nil))
}

func tileHandler(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	ts, sig := sign("GET", path)

	req, _ := http.NewRequestWithContext(r.Context(), "GET", upstream+path, nil)
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("X-MFD-Timestamp", ts)
	req.Header.Set("X-MFD-Signature", sig)

	resp, err := http.DefaultClient.Do(req)
	if err != nil { http.Error(w, "upstream", 502); return }
	defer resp.Body.Close()

	w.Header().Set("Content-Type", "application/x-protobuf")
	w.Header().Set("Cache-Control", "public, max-age=86400")
	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/tiles/", tileHandler)
	log.Fatal(http.ListenAndServe(":8080", mux))
}

chi router

go
import "github.com/go-chi/chi/v5"

r := chi.NewRouter()
r.Get("/tiles/{z}/{x}/{y}.pbf", tileHandler)

In-memory LRU tile cache

go
import "github.com/hashicorp/golang-lru/v2/expirable"

cache := expirable.NewLRU[string, []byte](10000, nil, 24*time.Hour)

func cachedTile(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if buf, ok := cache.Get(path); ok {
		w.Header().Set("X-Cache", "HIT")
		w.Header().Set("Content-Type", "application/x-protobuf")
		w.Write(buf)
		return
	}
	// fetch upstream + cache.Add(path, buf)
}

Reverse proxy with httputil

For minimal hand-rolled code, use httputil.ReverseProxy:

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

target, _ := url.Parse(upstream)
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Director = func(req *http.Request) {
	req.URL.Scheme = target.Scheme
	req.URL.Host   = target.Host
	ts, sig := sign("GET", req.URL.Path)
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("X-MFD-Timestamp", ts)
	req.Header.Set("X-MFD-Signature", sig)
	req.Host = target.Host
}

Production checklist

  • http.Server with ReadHeaderTimeout set (avoid Slowloris).
  • httputil.NewSingleHostReverseProxy reuses connections.
  • pprof exposed on a separate internal port for diagnosis.
  • prometheus/client_golang for tile-hit metrics.

Don't

  • Don't read env vars on every request — cache at startup.
  • Don't ioutil.ReadAll upstream into memory if you can io.Copy direct to response.
  • Don't pass raw upstream errors to client — log + return generic 502.