Overview

A common use case for Repose is rate limiting. It limits how many requests per some unit of time (e.g., 10 requests per minute) are allowed to be made. In this recipe, we’ll be using the X-PP-User header to indicate who we are for rate limiting purposes. For additional info on populating that header and on rate limiting by groups, see the Rate Limiting Filter documentation.

Configuration

System Model

The filter can be enabled by adding it to the list of filters in the System Model.

system-model.cfg.xml (partial)
<?xml version="1.0" encoding="UTF-8"?>
<!-- To configure Repose see: http://wiki.openrepose.org/display/REPOSE/Configuration -->
<system-model xmlns="http://docs.openrepose.org/repose/system-model/v2.0">
  <repose-cluster id="repose">
    ...
    <filters>
      <filter name="rate-limiting"/>
    </filters>
    ...
</system-model>

Rate Limiting

After the filter has been added to the System Model, the example configuration can copied/moved from the examples directory to the configuration files directory. The example configuration for rate limiting is sufficient for testing and will limit requests to the origin service to 10 times per minute.

rate-limiting.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<rate-limiting xmlns="http://docs.openrepose.org/repose/rate-limiting/v1.0">
    <!--
        Defines an endpoint with a matching regex to bind GET requests for
        returning live rate limiting information.
    -->
    <request-endpoint uri-regex="/limits" include-absolute-limits="true"/>

    <!-- Protects the Origin Service from being flooded. -->
    <global-limit-group>
        <limit id="global" uri="*" uri-regex=".*" value="1000" unit="MINUTE"/>
    </global-limit-group>

    <!-- Limits for all other requests -->
    <limit-group id="limited" groups="limited" default="true">
        <limit id="get" uri="/service/*" uri-regex="/service/([\d^/]*)/.*" http-methods="GET" unit="SECOND" value="1"/>
        <limit id="put" uri="/service/*" uri-regex="/service/([\d^/]*)/.*" http-methods="PUT" unit="MINUTE" value="5"/>
        <limit id="post" uri="/service/*" uri-regex="/service/([\d^/]*)/.*" http-methods="POST" unit="HOUR" value="15"/>
        <limit id="delete" uri="/service/*" uri-regex="/service/([\d^/]*)/.*" http-methods="DELETE" unit="DAY" value="2"/>
        <limit id="all" uri="*" uri-regex="/.*" http-methods="POST PUT GET DELETE" unit="MINUTE" value="10"/>
    </limit-group>

    <!-- Limits for WhiteListed IPs -->
    <limit-group id="unlimited" groups="unlimited" default="false"/>

</rate-limiting>

Testing

Script

You can use this script to quickly make 11 requests to Repose to confirm that rate limiting is working.

test_rate_limiting.sh
#!/bin/bash
OUT_FILE="repose-curl.out"
rm -f $OUT_FILE
touch $OUT_FILE
for i in {1..11} ; do
  echo -en "\n\n~~~~~ Attempt #$i ~~~~~\n\n" >> $OUT_FILE
  curl -H "x-pp-user: abc123" -H "Content-Type: Test" -H "Content-Length: 0" localhost:8080/get -v >> $OUT_FILE 2>&1
done

Running

Assuming you named the script test_rate_limiting.sh, you can run it with the following:

console session
# make the script executable
chmod +x test_rate_limiting.sh

# run the script
./test_rate_limiting.sh

# view the results
less repose-curl.out

Results

You should see the first 10 attempts succeed with a 200 and the 11th attempt fail with a 413. A sample of the 11th attempt output is below.

console session
~~~~~ Attempt #11 ~~~~~
* Hostname was NOT found in DNS cache
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
^M  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /get HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
> x-pp-user: abc123
> Content-Type: Test
> Content-Length: 0
>
< HTTP/1.1 413 Request Entity Too Large
< Date: Wed, 02 Dec 2015 04:59:28 GMT
< Retry-After: Wed, 02 Dec 2015 05:00:28 GMT
< Content-Type: application/json
< Via: 1.1 Repose (Repose/7.2.2.0)
< x-trans-id: eyJyZXF1ZXN0SWQiOiJjMWQxYmU0Ny1iOTYwLTQxZTAtYTY0My03NWQzNDNhNTJlNjciLCJvcmlnaW4iOm51bGx9
< Content-Length: 223
* Server Jetty(9.2.z-SNAPSHOT) is not blacklisted
< Server: Jetty(9.2.z-SNAPSHOT)
<
{ [data not shown]
^M100   223  100   223    0     0  11081      0 --:--:-- --:--:-- --:--:-- 11150
* Connection #0 to host localhost left intact
{
    "overLimit" : {
        "code" : 413,
        "message" : "OverLimit Retry...",
        "details" : "Error Details...",
        "retryAfter" : "2015-12-02T05:00:28Z"
    }
}

This is real handy if you have a single Repose node, however if you scale your Repose cluster horizontally, then you will need to configure for Distributed Rate Limiting for it to behave as you would expect it to.

Distributed Rate Limiting

If no Distributed Datastores are available, then rate limiting will use the local datastore and each node will allow the configured rate through. This is not typically the desired behavior and is easily remedied. By default, rate limiting will be distributed using the standard Distributed Datastore (hash-ring) if it is available. However, the Distributed Datastore must be enabled in the System Model. Furthermore, any of the datastore types can be used to store rate limiting information. The Distributed Datastore documentation has more information on how to properly enable and configure them.