Rate limiter

A typical scenario is having a web API that has a certain limit to the number of calls that can be done in a certain period of time. This type of API will just prevent the usage for a while if this threshold is crossed, making it unusable for the time being. When creating a client for the API, we need to be aware of this and make sure our application does not overuse it.

That's a very good scenario where we can use time.Ticker to define an interval between calls. In this example, we will create a client for Google Maps' geocoding service that has a limit of 100,000 requests per 24 hours. Let's start by defining the client:

type Client struct {
client *http.Client
tick *time.Ticker
}

The client is made by an HTTP client that will call maps, a ticker that will help prevent passing the rate limit, and needs an API key for authentication with the service. We can define a custom Transport struct for our use case that will inject the key in the request as follows:

type apiTransport struct {
http.RoundTripper
key string
}

func (a apiTransport) RoundTrip(r *http.Request) (*http.Response, error) {
q := r.URL.Query()
q.Set("key", a.key)
r.URL.RawQuery = q.Encode()
return a.RoundTripper.RoundTrip(r)
}

This is a very good example of how Go interfaces allow the extension of their own behavior. We are defining a type that implements the http.RoundTripper interface, and also an attribute that is an instance of the same interface. The implementation injects the API key to the request before executing the underlying transport. This type allows us to define a helper function that creates a new client, where we are using the new transport that we defined together with the default one:

func NewClient(tick time.Duration, key string) *Client {
return &Client{
client: &http.Client{
Transport: apiTransport{http.DefaultTransport, key},
},
tick: time.NewTicker(tick),
}
}

The maps geocoding API returns a series of addresses that are composed of various parts. This is available at https://developers.google.com/maps/documentation/geocoding/intro#GeocodingResponses.

The result is encoded in JSON, so we need a data structure that can receive it:

type Result struct {
AddressComponents []struct {
LongName string `json:"long_name"`
ShortName string `json:"short_name"`
Types []string `json:"types"`
} `json:"address_components"`
FormattedAddress string `json:"formatted_address"`
Geometry struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
// more fields
} `json:"geometry"`
PlaceID string `json:"place_id"`
// more fields
}

We can use the structure to execute a reverse geocoding operationgetting a location from the coordinates by using the respective endpoint. We wait for the ticket before executing the HTTP request, remembering to defer the closure of the body:

    const url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=%v,%v"
<-c.tick.C
resp, err := c.client.Get(fmt.Sprintf(url, lat, lng))
if err != nil {
return nil, err
}
defer resp.Body.Close()

Then, we can decode the result in a data structure that uses the Result type we already defined and checks for the status string:

    var v struct {
Results []Result `json:"results"`
Status string `json:"status"`
}
// get the result
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
return nil, err
}
switch v.Status {
case "OK":
return v.Results, nil
case "ZERO_RESULTS":
return nil, nil
default:
return nil, fmt.Errorf("status: %q", v.Status)
}
}

Finally, we can use the client to geocode a series of coordinates, expecting the requests to be at least 860ms from each other:

c := NewClient(24*time.Hour/100000, os.Getenv("MAPS_APIKEY"))
start := time.Now()
for _, l := range [][2]float64{
{40.4216448, -3.6904040},
{40.4163111, -3.7047328},
{40.4123388, -3.7096724},
{40.4145150, -3.7064412},
} {
locs, err := c.ReverseGeocode(l[0], l[1])
e := time.Since(start)
if err != nil {
log.Println(e, l, err)
continue
}
// just print the first location
if len(locs) != 0 {
locs = locs[:1]
}
log.Println(e, l, locs)
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181