简体   繁体   English

Laravel 限速器

[英]Laravel RateLimiter

I'm fairly new to Laravel and am currently using an API that has a limit of 25 requests per minute.我对 Laravel 还很陌生,目前正在使用一个限制为每分钟 25 个请求的 API。 I have a controller method sendRequest() which is used by all methods to send requests to the API so I was thinking this is the place to put a rate limiter that checks if the current request can be added to the queue if the limit is not yet reached.我有一个 controller 方法sendRequest() ,所有方法都使用该方法将请求发送到 API 所以我想这是放置一个速率限制器的地方,如果限制不是,则检查当前请求是否可以添加到队列中还没有达到。

I was thinking something like this:我在想这样的事情:

protected function sendRequest(){
    if ($this->allowRequest()) {
        //proceed to api call
    }
}

protected function allowRequest() {
    $allow = false;
    //probably a do-while loop to check if the limit has been reached within the timeframe?
}

I've found this class Illuminate\Cache\RateLimiter that I think could be useful but have no idea how to use it yet.我发现这个 class Illuminate\Cache\RateLimiter我认为它可能很有用,但还不知道如何使用它。 Can anyone point me to the right direct with this?任何人都可以直接指出我的正确方向吗? So basically the request should "wait" and execute only if the 25 requests/minute limit hasn't been reached.所以基本上请求应该“等待”并且只有在没有达到 25 个请求/分钟的限制时才执行。

Thanks!谢谢!

The Illuminate\Cache\RateLimiter class has hit and tooManyAttempts methods you can use like this: Illuminate\Cache\RateLimiter class 有hittooManyAttempts方法,您可以像这样使用:

use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;

protected function sendRequest()
{
    if ($this->hasTooManyRequests()) {
        // wait
        sleep(
            $this->limiter()
                ->availableIn($this->throttleKey()) + 1 // <= optional plus 1 sec to be on safe side
        );

        // Call this function again.
        return $this->sendRequest();
    }
    
    //proceed to api call
    $response = apiCall();

    // Increment the attempts
    $this->limiter()->hit(
        $this->throttleKey(), 60 // <= 60 seconds
    );

    return $response;
}

/**
 * Determine if we made too many requests.
 *
 * @return bool
 */
protected function hasTooManyRequests()
{
    return $this->limiter()->tooManyAttempts(
        $this->throttleKey(), 25 // <= max attempts per minute
    );
}

/**
 * Get the rate limiter instance.
 *
 * @return \Illuminate\Cache\RateLimiter
 */
protected function limiter()
{
    return app(RateLimiter::class);
}

/**
 * Get the throttle key for the given request.
 *
 * @return string
 */
protected function throttleKey()
{
    return 'custom_api_request';
}

See Illuminate\Cache\RateLimiter class for more available methods.有关更多可用方法,请参阅Illuminate\Cache\RateLimiter class。

You may also check Illuminate\Foundation\Auth\ThrottlesLogins as an example to figure out how to use Illuminate\Cache\RateLimiter class.您也可以查看Illuminate\Foundation\Auth\ThrottlesLogins作为示例,了解如何使用Illuminate\Cache\RateLimiter class。

Note : The RateLimiter methods use seconds instead of minutes since Laravel >= 5.8 and got a major improvement on v8.x.注意RateLimiter方法使用秒而不是分钟,因为 Laravel >= 5.8 并且在 v8.x 上得到了重大改进

Here you'll need the shared timer like control for frequency capping of the outgoing request.在这里,您需要共享计时器,例如控制传出请求的频率上限。 Create a class that will be a singleton for Laravel application and can be shared within requests.为 Laravel 应用程序创建一个 singleton 并可以在请求中共享的 class。

class FrequencyCapper{
    protected $start, $call, $request_frequency, $limit_interval;
    
    public function __construct($frequency, $interval_in_minute){
        $this->start = time();
        $this->call = 0;
        $this->request_frequency = frequency; // frequency of call
        $this->limit_interval = $interval_in_minute; // in minutes
    } 

    protected function allowRequest(){
        $diff = time() - $this->start;
        if($diff >= 60 * $this->limit_interval){
            $this->start = time();
            $this->call = 0;
        }
    
        return $diff <  60 * $this->limit_interval && $this->call < $this->request_frequency){
            $this->call++;
            return true;
        }else{
            return false;
        }
    }
}

Now, attach this class as a singleton in laravel service container.现在,将此 class 作为 singleton 附加到 laravel 服务容器中。 bind singleton in App\Providers\AppServiceProvider.php 's boot method.App\Providers\AppServiceProvider.php的引导方法中绑定 singleton 。

$this->app->singleton('FrequencyCapper', function ($app) {
     return new FrequencyCapper(25, 1); //max 25 request per minute
});

Now, this class will be available to all controllers as a dependency.现在,这个 class 将作为依赖项可供所有控制器使用。 You can inject FrequencyCapper dependency to any controller method as given,您可以将FrequencyCapper依赖项注入给定的任何 controller 方法,

class MyController extends Controller{
 
    protected function sendRequest(FrequencyCapper $capper){
        if($capper->allowRequest()){
            //you can call the api
        }
    }
}

if you want, you can use microtime() instead time() in FrequencyCapper class.如果需要,您可以在FrequencyCapper class 中使用microtime()代替 time()。 If you want to throttle the incoming request to your own api, you can use laravel's throttle middleware as,如果你想限制传入的请求到你自己的 api,你可以使用 laravel 的throttle中间件,

Route::group(['prefix' => 'api', 'middleware' => 'throttle:25,1'], function(){
    //define your routes here
});

For Laravel 8 and PHP 7.4 I take the example of https://stackoverflow.com/a/62642143/3145399对于 Laravel 8 和 PHP 7.4,我以https://stackoverflow.com/a/6329642143/314为例

and make this Trait:并制作此特征:

<?php

namespace App\Traits;

use Closure;
use Illuminate\Cache\RateLimiter;

trait CustomRateLimiter {

    protected string $throttleKey = 'GeneralRateLimit';

    /**
     * Determine if we made too many requests.
     *
     * @param int $maxAttempts
     *
     * @return bool
     */
    protected function hasTooManyRequests( $maxAttempts = 10 ): bool {
        return $this->limiter()->tooManyAttempts(
            $this->throttleKey(), $maxAttempts // <= max attempts per minute
        );
    }

    /**
     * Get the rate limiter instance.
     *
     * @return RateLimiter
     */
    protected function limiter(): RateLimiter {
        return app( RateLimiter::class );
    }

    /**
     * Get the throttle key for the given request.
     *
     * @param string $key
     *
     * @return string
     */
    protected function throttleKey( $key = 'GeneralRateLimit' ): string {
        return $this->throttleKey ?? $key;
    }

    /**
     * @param Closure $callback Anonymous function to be executed - example: function(){ return realFunction();}
     * @param int $maxAttempts Maximum number of hits before process sleeps
     * @param string $throttleKey If you have different Apis, change this key to a single key.
     * @param int $decaySeconds Time that will sleep when the condition of $maxAttempts is fulfilled
     * @param int $optionalSecond Optional plus secs to be on safe side
     *
     * @return mixed
     */
    protected function sendRequest( Closure $callback, $maxAttempts = 10, $throttleKey = 'GeneralRateLimit', $decaySeconds = 1, $optionalSecond = 1 ) {
        $this->throttleKey = $throttleKey;

        if ( $this->hasTooManyRequests( $maxAttempts ) ) {
            // wait
            sleep( $this->limiter()->availableIn( $this->throttleKey() ) + $optionalSecond );

            // Call this function again.
            return $this->sendRequest( $callback, $maxAttempts, $throttleKey, $decaySeconds, $optionalSecond );
        }

        //proceed to api call
        $response = $callback();

        // Increment the attempts
        $this->limiter()->hit(
            $this->throttleKey(), $decaySeconds // <= 1 seconds
        );

        return $response;
    }

}

how to use it?如何使用它?

use App\Traits\CustomRateLimiter;
class MyClass {
    use CustomRateLimiter;
    public function realApiToCall($var1){
     // custom logic
    }

    public function apiCall($var1){
     $apiResponse = $this->sendRequest( function () use ( $var1 ) {
            return $this->realApiToCall($var1);
        }, 4, 'customKey1', 1 );
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM