projectmentor / laravel-quota

Laravel 5 threadsafe rate-limiter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

laravel-quota

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads

This is a Laravel 5 wrapper for @malkusch threadsafe PHP implementation of the Token Bucket algorithm.

Use this package in a Laravel 5 application to enforce api usage limits.

Benefits

  • Easily throttle application resources
  • Simple install and set-up
  • Bi-directional:
    • works for both inbound and outbound API requests
  • Impose multiple concurrent limits on a resource
    • periodic quota
    • bandwith rate limiter
  • Comply with third party usage limits
  • Flexible overlimit strategies:
    • sleep and block
    • throw exception
    • return status
  • Works with many kinds of resources
    • APIs
    • Files
    • Databases
    • Object instances
  • Can be used in:
    • controller methods
    • static helper methods
    • model instances
  • Threadsafe

Example non-blocking usage scenario:

Guarding an api call within a static helper function.

...
class ApiHelper {
    ...
    public static function callApi($bucket, $params) {
    
        //Enforce the quota.
        if(! $bucket->consume(1, $seconds))
          throw new TokenBucketException(
            'Overquota. Retry in: ' .$seconds . ' seconds.');
        
        //Call the api
        return APIFacade::getSomeData($params);
    }
}

Example blocking usage scenario:

Guarding an api call within a static helper function.

...
class ApiHelper {
    ...
    public static function callApi($consumer, $params) {
    
        //Enforce the quota, wait until ready.
        $consumer->consume(1);
        
        //Call the api
        return APIFacade::getSomeData($params);
    }
}

Install

To get the latest version of Laravel Quota, simply require the project using Composer:

$ composer require projectmentor/laravel-quota

Alternately, you may of course manually update composer.json and run composer update

{
    "require": {
        "projectmentor/laravel-quota": "1.0-dev"
    }
}
$ composer update

Setup

Once Laravel Quota is installed, you will need to register the service provider in your Laravel 5 project. Open config/app.php and add the following value to the providers array:

...
'providers' => [
...
    'Projectmentor\Quota\QuotaServiceProvider::class',
],
...

Next, in the same file we register the facade as a key value pair in the aliases array:

...
'aliases' => [
...
    'Quota' => Projectmentor\Quota\Facades\Quota::class,
],
...

Note: in the current release the Quota facade is not implemented.

Usage

This package uses the namespace Projectmentor\Quota.

The general idea in the first release of this package is to expose the following library classes:

namespace class name
bandwidthThrottle\tokenBucket\storage FileStorage
bandwidthThrottle\tokenBucket Rate
bandwidthThrottle\tokenBucket TokenBucket
bandwidthThrottle\tokenBucket BlockingConsumer

Both the TokenBucket and the BlockingConsumer classes may be used to limit access to a resource. For the purposes of the following discussion, let's define both classes as consumers.

FileStorage and Rate are dependencies of both types of consumer.

BlockingConsumer is a special case of TokenBucket which automatically calculates the amount of time and sleeps until the target resource becomes available.

In general, to guard a resource with a quota you might take the following steps:

  • Choose either TokenBucket or BlockingConsumer implementation.
  • Decide how to manage the state of the quota between application calls and different users. In the current release we implement locking and state management using the FileStorage class.
    • Hence, create a FileStorage instance from a file path.
  • Specify a rate limit and a time period for your quota via the Rate class.
  • Create a TokenBucket from Rate and storage.
  • Bootstrap the TokenBucket with an initial amount of tokens -or-
  • Create a BlockingConsumer and bootstrap the TokenBucket if you want blocking behavior.

Once you have created a consumer, i.e: TokenBucket or BlockingConsumer, and just prior to calling the rate-limited resource have the consumer consume() one or more tokens. If there are not enough tokens remaining in the specified time period, then the consumer can either block, or throw an exception, or, in the scope of a static function, return a fail code.

Remember: TokenBucket won't block when overquota, and you will need to specify what action to take; whereas BlockingConsumer will sleep until enough tokens are available to conform to the quota you are enforcing.

Here's a quick example of how we instantiate a TokenBucket instance using this package by exposing the underlying classes from the library:

As in the previous examples above, let's imagine we need to enforce an api quota within a static helper function. Let's explore one way we might accomplish our goal using only the exposed classes, and without relying on the service container to resolve class dependencies.

namespace YourName\YourProject\Helpers;

use \bandwidthThrottle\tokenBucket\TokenBucketException;
use \bandwidthThrottle\tokenBucket\TokenBucket;
use \bandwidthThrottle\tokenBucket\Rate;
use \bandwidthThrottle\tokenBucket\storage\FileStorage;

class ApiHelper {

    //Capacity of the bucket in tokens.
    const CAPACITY = 60;
    
    //Path to persistant storage
    const STORAGE_PATH = '/tmp/quota.bucket';

    public static function callApi($params) {
       
       //Set rate limit at 60/Second
        $capacity = self::CAPACITY;
        $period = Rate::SECOND;
        $rate = new Rate($capacity, $period);
        
        //Setup persistant storage to manage state
        $storage = new FileStorage(self::STORAGE_PATH);
        
        //Create a new bucket to access the storage and 
        //compute whether or not to grant access
        $bucket = new TokenBucket($capacity, $rate, $storage);
        
        //Bootstrap the persistant storage
        //If already bootstrapped from a previous call,
        //then no-op.
        $bucket->bootstrap($capacity);    
    
        //Enforce the quota. Don't block.
        if(! $bucket->consume(1))
            throw new TokenBucketException();
    
        //Call the api   
        return APIFacade::getSomeData($params);
    }
}

We can also use the Laravel service container create instances of some the models we might need. Head on over to the factory tests for more examples of how to create consumers.

Features roadmap

  • Facade

Hide implementation details of the underlying library using syntax like:

Quota::enforce(...)

not currently implemented.

  • Routing and Middleware

Provide the ability to automatically limit resource via controller through middleware.

not currently implemented.

  • Manage concurrent quota's

Implement a manager pattern to track and resolve multiple quota instances.

not currently implemented.

Implementation status for major features of the library

  • Storage:

    Storage Type Scope Implemented?
    FileStorage Global YES
    IPCStorage Global NO
    MemcacheStorage Global NO
    MemcachedStorage Global NO
    PDOStorage Global NO
    PHPRedisStorage Global NO
    PredisStorage Global NO
    SessionStorage Session NO
    SingleProcessStorage Request NO
  • Blocking Behavior:

    Bucket Type Blocking? Implemented?
    TokenBucket NO YES
    BlockingConsumer YES YES
  • Periodic Rates:

    Rate Implemented ?
    microsecond YES
    millisecond YES
    second YES
    minute YES
    hour YES
    day YES
    week YES
    month YES
    year YES
  • Exceptions

    Throws Implemented?
    StorageException YES
    TokenBucketException YES

Tested Laravel versions

  • 5.2

Change log

Please see CHANGELOG for more information on what has changed recently.

Testing

$ composer test

Contributing

If you are interested in helping to maintain this project, please feel free to reach out and get in touch. All constructive suggestions are welcome!

This is my very first attempt at publishing a Laravel package on github and packagist so please be kind.

Currently need help and advice with:

  • Laravel refactoring, upgrades, and optimization
  • PHP refactoring, upgrades, and optimization
  • Devops e.g: travis-ci, deploy & PR workflows, etc
  • Testing and mocking.
  • Documentation

Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email david@projectmentor.org instead of using the issue tracker.

Credits

Donations

If you find this code helpful and you feel generous show some love!

Donate

License

The MIT License (MIT). Please see License File for more information.

If you have an issue with any of this content from a licensing perspective please share your concerns.

About

Laravel 5 threadsafe rate-limiter

License:MIT License


Languages

Language:PHP 100.0%