Rate Limiting (wiki) is a very useful technique to keep your server resources on a good use, and avoid using your full bandwidth when you are providing free or paid API services that users can be granted access to it. Rate limiting is often used to prevent robots from exhausting your API endpoints, and also as a marketing technique to sell different API access plans with different access counts per given interval (say per minute).

While rate-limiting your app, you’ll want to use less resources possible and handle things incredibly fast to maintain a scaled application, and therefore you should use a no-SQL database as a storing mechanism to keep the identifiers and access counts, or a memory caching technique such as Redis. In this tutorial, I’ll be using Redis because it’s incredibly fast and also easier to implement that any other tool.

Suggested Reading: How To Install and Configure Redis on Ubuntu 16.04

I’ll be using Predis as a PHP Redis client, that’s the only library used in this tutorial so far. You can install it by  either running this command:

composer require predis/predis

or, open composer.json file and save into it the following JSON code:

{
    "require": {
        "predis/predis": "^1.1"
    }
}

and run

composer install

after saving composer.json file.

Next thing, we’ll create an api.php file and require the composer autoload file:

<?php

use Predis\Client as PredisClient;

include __DIR__ . '/vendor/autoload.php';

Now, let’s stop coding for a second and explain the process. We will want to identify our users uniquely in order to serve them the fresh API data, and to apply rate-limiting. Normally you can identify an anonymous user by their IP address, in addition to their browser data namely user-agent attached to the request headers, but maybe you have another unique way of identifying them, like if they are authenticated with a login or an access token, which will be used and it’s even better than using the IP.

Now that you identify your users uniquely, you’ll just want to count their API requests at a given time interval (say each 5 seconds), and limit it to only 2 (or X) requests per that interval. So we’ll use Redis to save and increment these counts, until they reach the limit then we’ll just shout at our users and send them error responses.

Now the cool thing about Redis is the TTL command (time-to-live), so we don’t have to apply additional algorithm to restart our counts each and every 5 seconds. With Redis, set the TTL to 5 at first time:

EXPIRE key 5

and later on, set it to the TTL itself, Redis provides how many seconds left for a key to expire:

TTL key

.

So, we’ll save the counts correctly, check against the user access count to verify whether to serve them a good response or not, and proceed with incrementing the counts for later use.

Here’s the full tutorial code (api.php file):

<?php

use Predis\Client as PredisClient;

include __DIR__ . '/vendor/autoload.php';

function redis() {
    global $Redis;

    if ( !isset($Redis) || !$Redis instanceof PredisClient ) {
        $Redis = new PredisClient;
    }

    return $Redis;
}

function whatismyip() {
    if (isset($_SERVER['HTTP_CLIENT_IP']))
        $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
    else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_X_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
    else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
        $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
    else if(isset($_SERVER['HTTP_FORWARDED']))
        $ipaddress = $_SERVER['HTTP_FORWARDED'];
    else if(isset($_SERVER['REMOTE_ADDR']))
        $ipaddress = $_SERVER['REMOTE_ADDR'];
    else
        $ipaddress = null;
    return $ipaddress;
}

function send_json($resp, $status=200) {
    header('Content-type: application/json; charset=utf-8');

    if ( $status ) { // status code
        http_response_code($status);
    }

    print json_encode($resp);
    die;
}

$limit = [
    'interval'      => 5, // seconds
    'num_requests'  => 2, // number of requests allowed per interval
    'user_ip'       => whatismyip(), // getting the user IP.
];

$uid = "requests_count_{$limit['user_ip']}";
$logged = (int) redis()->get($uid);

if ( !$logged ) {
    // first API request (or the count has expired)
    $logged = 1;
    // log the requests count to 1
    redis()->set($uid, $logged);
    // first time setting the key, expire the key at X seconds 
    redis()->expire($uid, $limit['interval']);
} else if ( $logged + 1 > $limit['num_requests'] ) { // num. requests exceeded
    // send them a notice to slow down
    return send_json([
        'success' => false,
        'message' => 'Too many requests, please slow down or upgrade your API access plan.'
    ], 429);
} else { // we're still below the quota
    // get the time-to-live integer
    $ttl = redis()->ttl($uid);
    // set the key
    redis()->set($uid, $logged+1);
    // expire the key at X seconds (ttl)
    redis()->expire($uid, $ttl);
}

// Send the good response to the good users
send_json(['success' => true, 'data' => 'xyz']);

Don’t forget to send HTTP response code along the API response headers because they’re very useful.

Download the source code here.

Digital Ocean

Cheap Cloud SSD Hosting

Get a VPS now starting at $5/m, fast and perfect for WordPress and PHP applications

Sign Up with $10 Credit