0
votes

I'm using PHP for my as my backend for my webservice, I need the webservice to be able to set a rate limit of maximum of 1000 requests within the last 24 hours, based off the users PHP session. Is that possible to do it without using a database and just rate limit using only PHP. I have currently already made a rate limiter for 1 request per second per session, but I am looking to set a rate limit of 1000 requests per session in the last 24 hours. PS I'm new to PHP, any help would be great.

Here the code I did for the 1 per second rate limit.

class gaqsession {

        public $lastrequest;

        public function __construct() {
            $this->lastrequest = time();
        }
        public function ratelimited() {
            if($this->lastrequest == time()) {
                $this->lastrequest = time();
                return true;
            } else {
                $this->lastrequest = time();
            return false;
            } 
        }
    }
1
“based off the users PHP session” - what would stop me from simply getting a new session then? - 04FS

1 Answers

0
votes

You can do this provided your sessions are set up to last at least 24 hours. The only real trick is saving rate counts in the session in a way that allows you to maintain them within a 24 hour window. You can do that with a simple array that uses time as keys. Here's a class that adds some features around an array like that in order to easily manage the data, along with some example code that runs it through it's paces.

/**
 * Class RateLimitCounter
 * Track number of events by time. Intended to be set on a session.
 */
class RateLimitCounter
{
    // The time -> request count data
    private $timeline = [];

    /**
     * Log an event in the timeline
     */
    public function increment()
    {
        $now = time();
        if (!array_key_exists($now, $this->timeline))
        {
            $this->timeline[$now] = 0;
        }

        $this->timeline[$now]++;
    }

    /**
     * Return the total number of events logged in the counter
     * @return int
     */
    public function getTotal()
    {
        return array_sum($this->timeline);
    }

    /**
     * Remove any timeline data older than 24 hours
     */
    private function trim()
    {
        // Get the current time
        $now = time();

        // Time is in seconds, so subtract 1 day worth of seconds
        $timeFloor = $now - 86400;

        // Filter out all timeline entries more than 24 hours old
        $this->timeline = array_filter($this->timeline, function ($key) use ($timeFloor) {
            return $key > $timeFloor;
        }, ARRAY_FILTER_USE_KEY);
    }

    public function __serialize(): array
    {
        return [
            'timeline' => $this->timeline
        ];
    }

    /**
     * Wake up! Set the timeline data and trim data older than 24 hours
     * @param array $data
     */
    public function __unserialize(array $data): void
    {
        $this->timeline = $data['timeline'];

        $this->trim();
    }
}

/**
 * Verify that the rate limit has not been exceeded. Bail out if it has been.
 * @param $counter
 * @return bool
 */
function rateLimit($counter)
{
    $limit = 1000;
    if ($counter->getTotal() > $limit)
    {
        // Do whatever you need to here, throw an exception, redirect to an error page, etc.
        exit('Rate limit exceeded' . PHP_EOL);
    }

    return true;
}

/*
 * Instantiate a counter - this is what you would do if you do not already have one on the session
 */
$counter = new RateLimitCounter();

/*
 * Simulate some prior activity
 * Let's get close to the limit then save to the "session"
 */
for ($i = 0; $i <= 995; $i++)
{
    $counter->increment();
}

// Mock session
$dummySession = ['counter' => $counter];

// Serialize the session
$serializedSession = serialize($dummySession);

// Unserialize the session
$session = unserialize($serializedSession);

$counter = $session['counter'];

// Do API calls until we hit our limit. There should be 5 remaining.
while (rateLimit($counter))
{
    apiCall();

    // Don't forget to increment the counter for each call
    $counter->increment();
}

// Dummy function to simulate your API call
function apiCall()
{
    echo 'Doing something interesting' . PHP_EOL;
}