A simple router and middleware for region endpoint redirection written in Go.
The motivation for this work is that many DNS-based approaches to region routing rely on GeoIP lookups - operating under the assumption that the requesting IP will provide a sufficiently robust data point in order to ascertain physical location. This approach falls short in a number of areas, and further fails to take into account a far more robust mechanism available to most Edge devices and mobile applications - GPS telemetry. Rather than placing emphasis on the backend to work out the requesting party's location, we instead take the approach of having the requesting party encode this information directly in the request headers.
To this extent, go-region-router
expects to find a ISO 3166-1 alpha-2 2-character country code
included on any in-bound request headers that wish to be redirected - accomplished through the addition of a
custom X-Country-Code
header which specifies the country code denoting where the request originated:
X-Country-Code: de
The extraction of the country code from GPS coordinates on the requesting client side is not handled by this router, but is possible by any number of reverse geocoding services, including, but not limited to, our reverse-geocoding-service microservice.
The router should also be trivially extendable for HTML5 Geolocation support, but as the initial focus of this router has been for backend routing in response to a moving mobile frontend, this remains to be implemented.
go get github.com/adaptant-labs/go-region-router
A Docker Compose file is provided in order to demonstrate the basic operation:
$ docker-compose up -d
Once this is up and running, it can be tested as:
$ curl -L -H 'X-Country-Code: at' -X GET http://localhost:7000
hello from the AT endpoint
$ curl -L -H 'X-Country-Code: de' -X GET http://localhost:7000
hello from the DE endpoint
If the default
tag has been set for a server, as in the example, a country
mismatch will redirect to the default server:
$ curl -L -H 'X-Country-Code: it' -X GET http://localhost:7000
hello from the DE endpoint
if not provided in the service definition, a mismatch will result in a 503 response:
$ curl -L -H 'X-Country-Code: it' -X GET http://localhost:7000
Service is unavailable in your region (it)
Mappings between regions and servers can either be done statically, or dynamically through Consul. In order for the
discovery to work, each server must be tagged with a region identifier (denoted by region-<code>
), and optionally with
a connection protocol (when the protocol is anything other than HTTPS).
An example service configuration is provided in docs/consul.d
:
{
"services": [
{
"id": "apigw0",
"name": "api",
"tags": [
"v1",
"region-de"
],
"meta": {
"protocol": "http"
},
"port": 80
},
{
"id": "apigw1",
"name": "api",
"tags": [
"v1",
"region-at"
],
"port": 443
}
]
}
This can be registered with the Consul Agent directly via:
$ consul services register docs/consul.d/consul-service.json
Registered service: api
Registered service: api
Once Consul is up and running and the service definitions are amended with the appropriate data, the router can be started directly:
$ go-region-router
2019/07/05 19:20:09 Setting up region routing for [DE] -> http://127.0.0.1:80
2019/07/05 19:20:09 Setting up region routing for [AT] -> https://127.0.0.1:443
2019/07/05 19:20:09 Listening on :7000 ...
The following options can be set at run-time:
$ go-region-router --help
NAME:
go-region-router - A simple routing service for region endpoints
USAGE:
go-region-router [global options] command [command options] [arguments...]
VERSION:
0.0.1
AUTHOR:
Adaptant Labs <[email protected]>
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--consul-agent value Consul agent to connect to
--consul-service value Name of Consul Service to look up (default: "api")
--consul-tag value Name of Consul tag to filter on (default: "v1")
--host value Host address to bind to
--port value Port to bind to (default: 7000)
--help, -h show help
--version, -v print the version
COPYRIGHT:
(c) 2019 Adaptant Solutions AG
Region Routing middleware is provided in the github.com/adaptant-labs/go-region-router/middleware
package. While the
Consul-backed router provides a more elaborate usage example, direct usage of the middleware for establishing static
routes is rather straightforward:
import (
"github.com/adaptant-labs/go-region-router/middleware"
"github.com/gorilla/mux"
"net/http"
)
func main() {
m := mux.NewRouter()
r := region.NewRegionRouter()
r.SetDefaultServer("https://default.api.xxx.com")
r.SetRegionServer("de", "https://de.api.xxx.com")
r.SetRegionServer("at", "https://at.api.xxx.com")
// Apply the region routing middleware with default settings
http.ListenAndServer(":8080", r.RegionHandler()(m))
}
Note that as above, the middleware expects to find the country code in the custom X-Country-Code
header on in-bound
requests. Requests that do not have this header defined (or for where no matching route is available) will pass through
to the default
tagged service. If no default handler is specified, a 503 response will be returned informing the
user of the unavailability of a server for their specific region. This response may be trapped for e.g. spinning up a
new instance in a new region and having the client periodically retry the connection.
Routers may also enable a GeoIP-based lookup from the client IP address in order to identify the country code and
automatically insert the X-Country-Code
header on in-bound requests. This, however, requires the availability of the
reverse-geocoding-service - the host:port of which must be set in the REVERSE_GEOCODING_SERVICE
environment
variable. Furthermore, if the router is placed behind other loadbalancers and routers, it will also look at the
X-Forwarded-For
header in order to determine the originating client IP.
If used, this should wrap the RegionHandler above:
http.ListenAndServe(":8080", region.CountryCodeHandler(r.RegionHandler()(m)))
Online API documentation is provided through godoc, this can be accessed directly on the package entry in the godoc package repository.
Docker images are provided under adaptant/go-region-router, and should be deployed together with
a Consul agent and reverse geocoding service (optionally) - as provided in the sample docker-compose.yml
. Information
on obtaining and deploying a Consul image can be found on the Consul Docker page, while information
about the reverse geocoding service images can be found under adaptant/reverse-geocoding-service.
Please file feature requests and bugs at the issue tracker.
This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 731678.
go-region-router
is licensed under the terms of the Apache 2.0 license, the full version of which can
be found in the LICENSE file included in the distribution.