Skip to content

Latest commit

 

History

History
133 lines (114 loc) · 3.86 KB

File metadata and controls

133 lines (114 loc) · 3.86 KB

Laravel comes with Illuminate\Routing\Middleware\ThrottleRequests middleware. Those solution developed to be declared only once in application, applying it twice brings unexpected behavior in headers response.

Example replace Illuminate\Routing\Middleware\ThrottleRequests with Noxlogic\RateLimitBundle based on configuration file rate limit rules, repeating default Illuminate\Routing\Middleware\ThrottleRequests behaviour with blocking by client ip.

rate-limit.php

<?php

return [
    'api' => [
        'path' => 'api/',
        'methods' => ['*'],
        'limit' => 60,
        'period' => 60
    ]
];

RateLimit.php

<?php

namespace Project\Middlewares;

use Closure;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Redis\RedisManager;
use Noxlogic\RateLimitBundle\Service\RateLimitInfo;
use Noxlogic\RateLimitBundle\Service\RateLimitInfoManager;
use Noxlogic\RateLimitBundle\Service\RateLimitService;
use Noxlogic\RateLimitBundle\Service\Storage\Redis;
use Symfony\Component\HttpFoundation\Response;
use Noxlogic\RateLimitBundle\Util\PathLimitProcessor;

class RateLimit
{
    /**
     * @var RateLimitService
     */
    private $rateLimitService;

    /**
     * @var PathLimitProcessor
     */
    private $pathLimitProcessor;

    public function __construct(RedisManager $redisFactory)
    {
        $rateLimitService = new RateLimitService();
        $rateLimitService->setStorage(new Redis($redisFactory->client()));
        $this->rateLimitService = $rateLimitService;
        $this->pathLimitProcessor = new PathLimitProcessor(config('rate-limit'));
    }

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $rateLimit = $this->pathLimitProcessor->getRateLimit($request);

        $key = trim(join('.', $rateLimit->getMethods()) . '.' . $this->pathLimitProcessor->getRateLimitAlias($request) . '.' . $request->getClientIp(), '.');

        $rateLimitManager = new RateLimitInfoManager($this->rateLimitService);
        $rateLimitInfo = $rateLimitManager->getRateLimitInfo($key, $rateLimit);

        // When we exceeded our limit, return a custom error response
        if ($rateLimitInfo->getCalls() > $rateLimitInfo->getLimit()) {
            throw new ThrottleRequestsException(
                'Too Many Attempts.',
                null,
                $this->getHeaders($rateLimitInfo)
            );
        }

        $response = $next($request);

        return $this->addHeaders($response, $rateLimitInfo);
    }

    /**
     * Add the limit header information to the given response.
     *
     * @param Response $response
     * @param RateLimitInfo $rateLimitInfo
     * @return Response
     */
    protected function addHeaders(Response $response, RateLimitInfo $rateLimitInfo)
    {
        $response->headers->add(
            $this->getHeaders($rateLimitInfo)
        );

        return $response;
    }

    /**
     * Get the limit headers information.
     *
     * @param RateLimitInfo $rateLimitInfo
     * @return array
     */
    protected function getHeaders(RateLimitInfo $rateLimitInfo)
    {
        return [
            'X-RateLimit-Limit' => $rateLimitInfo->getLimit(),
            'X-RateLimit-Remaining' => $rateLimitInfo->getRemainingAttempts(),
            'Retry-After' => $rateLimitInfo->getResetTimestamp() - time(),
            'X-RateLimit-Reset' => $rateLimitInfo->getResetTimestamp()
        ];
    }
}

Kernel.php

    protected $middlewareGroups = [
        'api' => [
            //'throttle:60,1',
            'rate-limit',
        ],
    ];
    
    protected $routeMiddleware = [
        //'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'rate-limit' => \Project\Middlewares\RateLimit::class,
    ];