github.com/m-lab/locate@v0.17.6/clientgeo/appengine.go (about) 1 package clientgeo 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "strings" 8 9 "github.com/apex/log" 10 "github.com/m-lab/locate/metrics" 11 "github.com/m-lab/locate/static" 12 ) 13 14 var ( 15 // ErrBadLatLonFormat is returned with a lat,lon header is missing or corrupt. 16 ErrBadLatLonFormat = errors.New("lat,lon format was missing or corrupt") 17 18 // ErrNullLatLon is returned with a 0,0 lat/lon value is provided. 19 ErrNullLatLon = errors.New("lat,lon value was null: " + nullLatLon) 20 21 latlonMethod = "appengine-latlong" 22 regionMethod = "appengine-region" 23 countryMethod = "appengine-country" 24 noneMethod = "appengine-none" 25 nullLatLon = "0.000000,0.000000" 26 ) 27 28 // NewAppEngineLocator creates a new AppEngineLocator. 29 func NewAppEngineLocator() *AppEngineLocator { 30 return &AppEngineLocator{} 31 } 32 33 // AppEngineLocator finds a client location using AppEngine headers for lat/lon, 34 // region, or country. 35 type AppEngineLocator struct{} 36 37 // Locate finds a location for the given client request using AppEngine headers. 38 // If no location is found, an error is returned. 39 func (sl *AppEngineLocator) Locate(req *http.Request) (*Location, error) { 40 headers := req.Header 41 fields := log.Fields{ 42 "CityLatLong": headers.Get("X-AppEngine-CityLatLong"), 43 "Country": headers.Get("X-AppEngine-Country"), 44 "Region": headers.Get("X-AppEngine-Region"), 45 "Proto": headers.Get("X-Forwarded-Proto"), 46 "Path": req.URL.Path, 47 } 48 49 country := headers.Get("X-AppEngine-Country") 50 metrics.AppEngineTotal.WithLabelValues(country).Inc() 51 52 // First, try the given lat/lon. Avoid invalid values like 0,0. 53 latlon := headers.Get("X-AppEngine-CityLatLong") 54 loc, err := splitLatLon(latlon) 55 if err == nil { 56 log.WithFields(fields).Info(latlonMethod) 57 loc.Headers.Set(hLocateClientlatlon, latlon) 58 loc.Headers.Set(hLocateClientlatlonMethod, latlonMethod) 59 return loc, nil 60 } 61 // The next two fallback methods require the country, so check this next. 62 if country == "" || static.Countries[country] == "" { 63 // Without a valid country value, we can neither lookup the 64 // region nor country. 65 log.WithFields(fields).Info(noneMethod) 66 loc.Headers.Set(hLocateClientlatlonMethod, noneMethod) 67 return loc, errors.New(hLocateClientlatlonMethod + ": " + noneMethod) 68 } 69 // Second, country is valid, so try to lookup region. 70 region := strings.ToUpper(headers.Get("X-AppEngine-Region")) 71 if region != "" && static.Regions[country+"-"+region] != "" { 72 latlon = static.Regions[country+"-"+region] 73 log.WithFields(fields).Info(regionMethod) 74 loc, err := splitLatLon(latlon) 75 loc.Headers.Set(hLocateClientlatlon, latlon) 76 loc.Headers.Set(hLocateClientlatlonMethod, regionMethod) 77 return loc, err 78 } 79 // Third, region was not found, fallback to using the country. 80 latlon = static.Countries[country] 81 log.WithFields(fields).Info(countryMethod) 82 loc, err = splitLatLon(latlon) 83 loc.Headers.Set(hLocateClientlatlon, latlon) 84 loc.Headers.Set(hLocateClientlatlonMethod, countryMethod) 85 return loc, err 86 } 87 88 // Reload does nothing. 89 func (sl *AppEngineLocator) Reload(ctx context.Context) {} 90 91 // splitLatLon attempts to split the "<lat>,<lon>" string provided by AppEngine 92 // into two fields. The return values preserve the original lat,lon order. 93 func splitLatLon(latlon string) (*Location, error) { 94 loc := &Location{ 95 // The empty header type is nil, so we set it. 96 Headers: http.Header{}, 97 } 98 if latlon == nullLatLon { 99 return loc, ErrNullLatLon 100 } 101 fields := strings.Split(latlon, ",") 102 if len(fields) != 2 { 103 return loc, ErrBadLatLonFormat 104 } 105 loc.Latitude = fields[0] 106 loc.Longitude = fields[1] 107 return loc, nil 108 }