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.ServerwithReadHeaderTimeoutset (avoid Slowloris).httputil.NewSingleHostReverseProxyreuses connections.pprofexposed on a separate internal port for diagnosis.prometheus/client_golangfor tile-hit metrics.
Don't
- Don't read env vars on every request — cache at startup.
- Don't
ioutil.ReadAllupstream into memory if you canio.Copydirect to response. - Don't pass raw upstream errors to client — log + return generic 502.