master

laravel/framework

Last updated at: 29/12/2023 09:24

RateLimitedWithRedis.php

TLDR

The file RateLimitedWithRedis.php defines a class RateLimitedWithRedis that extends another class RateLimited and implements middleware functionality for rate limiting jobs. It uses Redis as a backend to store and manage rate limiting data.

Methods

handleJob

This method is responsible for handling a rate limited job. It takes in the job object, a callback function for the next middleware, and an array of rate limit configurations. It iterates over the rate limit configurations and checks if the current job has exceeded the rate limit. If the limit is exceeded, it either releases the job for retry if shouldRelease is true, or returns false to discard the job. If the limit is not exceeded, it proceeds to the next middleware.

tooManyAttempts

This method determines if the given key (job identifier) has been "accessed" too many times within the specified max attempts and decay seconds. It creates a new DurationLimiter object using the Redis factory and the given key, max attempts, and decay seconds. It attempts to acquire the rate limit, and returns the negation of the result. It also updates the decaysAt array with the decay timestamp for the given key.

getTimeUntilNextRetry

This method calculates the number of seconds that should elapse before the job is retried. It subtracts the current time from the decaysAt timestamp for the given key, and adds 3 seconds as a buffer time.

__construct

This is the constructor method for the RateLimitedWithRedis class. It takes a limiterName parameter and calls the parent constructor from RateLimited. It creates an instance of the Redis factory using the container and assigns it to the $redis property.

__wakeup

This method is called after the object is unserialized. It is responsible for recreating the Redis factory instance from the container and assigning it to the $redis property.

Classes

RateLimitedWithRedis

This class extends the RateLimited class and implements middleware functionality for rate limiting jobs using Redis as the backend. It has a $redis property to hold the Redis factory instance and a $decaysAt property to store the decay timestamp for each job. It provides methods to handle rate limited jobs, check if a job has exceeded the rate limit, calculate the time until next retry, and handle object serialization/deserialization.

<?php

namespace Illuminate\Queue\Middleware;

use Illuminate\Container\Container;
use Illuminate\Contracts\Redis\Factory as Redis;
use Illuminate\Redis\Limiters\DurationLimiter;
use Illuminate\Support\InteractsWithTime;

class RateLimitedWithRedis extends RateLimited
{
    use InteractsWithTime;

    /**
     * The Redis factory implementation.
     *
     * @var \Illuminate\Contracts\Redis\Factory
     */
    protected $redis;

    /**
     * The timestamp of the end of the current duration by key.
     *
     * @var array
     */
    public $decaysAt = [];

    /**
     * Create a new middleware instance.
     *
     * @param  string  $limiterName
     * @return void
     */
    public function __construct($limiterName)
    {
        parent::__construct($limiterName);

        $this->redis = Container::getInstance()->make(Redis::class);
    }

    /**
     * Handle a rate limited job.
     *
     * @param  mixed  $job
     * @param  callable  $next
     * @param  array  $limits
     * @return mixed
     */
    protected function handleJob($job, $next, array $limits)
    {
        foreach ($limits as $limit) {
            if ($this->tooManyAttempts($limit->key, $limit->maxAttempts, $limit->decaySeconds)) {
                return $this->shouldRelease
                    ? $job->release($this->getTimeUntilNextRetry($limit->key))
                    : false;
            }
        }

        return $next($job);
    }

    /**
     * Determine if the given key has been "accessed" too many times.
     *
     * @param  string  $key
     * @param  int  $maxAttempts
     * @param  int  $decaySeconds
     * @return bool
     */
    protected function tooManyAttempts($key, $maxAttempts, $decaySeconds)
    {
        $limiter = new DurationLimiter(
            $this->redis, $key, $maxAttempts, $decaySeconds
        );

        return tap(! $limiter->acquire(), function () use ($key, $limiter) {
            $this->decaysAt[$key] = $limiter->decaysAt;
        });
    }

    /**
     * Get the number of seconds that should elapse before the job is retried.
     *
     * @param  string  $key
     * @return int
     */
    protected function getTimeUntilNextRetry($key)
    {
        return ($this->decaysAt[$key] - $this->currentTime()) + 3;
    }

    /**
     * Prepare the object after unserialization.
     *
     * @return void
     */
    public function __wakeup()
    {
        parent::__wakeup();

        $this->redis = Container::getInstance()->make(Redis::class);
    }
}