Skip to content

adaptant-labs/go-region-router

Repository files navigation

go-region-router

godoc Build Status

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.

Installation

go get github.com/adaptant-labs/go-region-router

Quick Start

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)

Usage

Consul Configuration

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

Starting the Router

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 <labs@adaptant.io>

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

Middlewares

Region Routing Middleware

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.

GeoIP Reverse Lookup Middleware

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 Documentation

Online API documentation is provided through godoc, this can be accessed directly on the package entry in the godoc package repository.

Deployment

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.

Features and bugs

Please file feature requests and bugs at the issue tracker.

Acknowledgements

This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 731678.

License

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.