Skip to content

Go Quickstart

Server-side use: tile proxy, batch geocoding, downloads.

Tile proxy (net/http)

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"))
)

func signHeaders(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 := signHeaders("GET", path)

	req, _ := http.NewRequest("GET", "https://tiles.mapsfordevs.com"+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 error", http.StatusBadGateway)
		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() {
	http.HandleFunc("/tiles/", tileHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Geocoding client

go
package mfd

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type GeocodeItem struct {
	Lat        float64           `json:"lat"`
	Lng        float64           `json:"lng"`
	Label      string            `json:"label"`
	Components map[string]string `json:"components"`
	Confidence float64           `json:"confidence"`
	Type       string            `json:"type"`
}

type geocodeResp struct {
	OK    bool          `json:"ok"`
	Items []GeocodeItem `json:"items"`
	Error string        `json:"error"`
}

func Geocode(apiKey, query, country string, limit int) ([]GeocodeItem, error) {
	q := url.Values{}
	q.Set("q", query)
	q.Set("limit", fmt.Sprintf("%d", limit))
	if country != "" {
		q.Set("country", country)
	}
	req, _ := http.NewRequest("GET",
		"https://api.mapsfordevs.com/geocode/forward?"+q.Encode(), nil)
	req.Header.Set("Authorization", "Bearer "+apiKey)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var body geocodeResp
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		return nil, err
	}
	if !body.OK {
		return nil, fmt.Errorf("mfd: %s", body.Error)
	}
	return body.Items, nil
}

Retry with backoff

go
func fetchWithRetry(req *http.Request, attempts int) (*http.Response, error) {
	for i := 0; i < attempts; i++ {
		resp, err := http.DefaultClient.Do(req)
		if err == nil && resp.StatusCode < 500 && resp.StatusCode != 429 {
			return resp, nil
		}
		var delay time.Duration
		if resp != nil {
			if ra := resp.Header.Get("Retry-After"); ra != "" {
				if s, err := strconv.Atoi(ra); err == nil {
					delay = time.Duration(s) * time.Second
				}
			}
			resp.Body.Close()
		}
		if delay == 0 {
			delay = time.Duration(1<<i) * time.Second
		}
		time.Sleep(delay)
	}
	return nil, fmt.Errorf("failed after %d attempts", attempts)
}