choke

Throttling library


License
Apache-2.0

Documentation

Throttle

Erlang application to implement throttling/rate limit in Erlang.

Note: we are appraising the posibility of changing the name of the library.

Introduction

Throttle is a rebar3 library, created to avoid throttling on your API.

The application allows us to limit different contexts at different rates. For every context you can independently control different identifiers.

A use case of the library is a RESTful API. Then, you can use your resource URIs as context and users as identifiers.

Samples

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).  %% 4 attempts every 5 seconds
{ok,<0.50.0>}
3> throttle:check('context', 'id'), throttle:check('context', 'id'), throttle:check('context', 'id').
{ok,3}
4> throttle:check('context', 'id'), timer:sleep(1000),
4> throttle:check('context', 'id'), timer:sleep(1000),
4> throttle:check('context', 'id'), timer:sleep(5000),
4> throttle:peek('context', 'id').
{ok,1}
5> throttle:check('context', 'id'), timer:sleep(4000),
5> throttle:check('context', 'id'), timer:sleep(1000),
5> throttle:peek('context', 'id').
{ok,2}
6> throttle:check('context', 'id'), throttle:check('context', 'id'),
6> throttle:check('context', 'id'), throttle:check('context', 'id').
{ok,4}
7> throttle:check('context', 'id'), throttle:check('context', 'id'),
7> throttle:check('context', 'id'), throttle:check('context', 'id'),
7> throttle:check('context', 'id').
{warning,4,5000}

Each identifier could be at different contexts at the same time being the access controller different per context.

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).  %% 4 attempts every 5 seconds
{ok,<0.50.0>}
3> throttle:start_context('context1', {6, 10000}).  %% 6 attempts every 10 seconds
{ok,<0.60.0>}
3> throttle:check('context', 'id'), throttle:check('context1', 'id'),
3> io:format("Context1: ~p, Context2: ~p~n",[throttle:peek('context', 'id'), throttle:peek('context1', 'id')]).
Context1: {ok,1}, Context2: {ok,1}
ok
4> throttle:check('context', 'id'), throttle:check('context', 'id'), throttle:check('context', 'id'),
4> throttle:check('context', 'id'), throttle:check('context', 'id'), throttle:check('context', 'id'),
4> throttle:check('context1', 'id'), throttle:check('context1', 'id'), throttle:check('context1', 'id'),
4> throttle:check('context1', 'id'), throttle:check('context1', 'id'), throttle:check('context1', 'id'),
4> io:format("Context1: ~p, Context2: ~p~n",[throttle:peek('context', 'id'), throttle:peek('context1', 'id')]).
Context1: {warning,4,5000}, Context2: {ok,6}
ok

Set-Up

To start the application you need to call at the start to application:start(choke). Then you can create all the context that you want calling to throttle:start_context{'context_name', {numer_of_attemps, time_interval}}. Rates can also be set via application environment instead of calling start_context function, if you wish you could use both.

{choke, [
            {contexts, [
                        {'context1', {4, 5000}},
                        {'context2', {5, 1000}},
                        {'context3', {1000, 10000}},
                        {'context4', {4, 5000}}
                        ]
            }]
}

Functions

List of all the functions available on the library.

start_link() -> supervisor:startlink_ret().

Start the throttle supervisor. Other way of starting the throttle application is using application:start(choke). You can only start one throttle supervisor in your REST application.

Examples:

1> application:start(choke).
ok
1> throttle:start_link().
{ok,<0.50.0>}

start_context(atom(), {integer(), integer()}) -> supervisor:startchild_ret().

Create the context, the function receive the Id of the context, the first atom(), and receive a tuple, being the first integer element the number of attempts and the second integer the time per attempts.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.79.0>}

check(atom(), atom()) -> {ok, integer()} | {error | warning, integer(), integer()}.

Check the number of attempts of a specific user, second atom(), in a specific resource, first atom(). If the user has enough attempts return {ok, integer()}, being the integer the actual number of attempts in the interval. But if the user has not attempts, the first time return {warning, integer(), integer()}, being the first integer the number of attempts and the second once the time of the interval. While the user has not attempts and continue making call, the method return {error, integer(), integer()}, the meaning is the same as before.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.79.0>}
3> throttle:check('context', 'id'),
{ok,1}
4> throttle:check('context', 'id'), throttle:check('context', 'id'),
4> throttle:check('context', 'id'), throttle:check('context', 'id'),
4> io:format("Check1: ~p, Check2: ~p~n", [throttle:check('context', 'id'), 
4> throttle:check('context', 'id')]).
Check1: {warning,4,5000}, Check2+: {error,4,5000}

check(atom(), atom(), [{atom(), boolean()}]) -> {ok, integer()} | {error | warning, integer(), integer()}.

Check the number of attempts of a specific user, second atom(), in a specific resource, first atom(). If the user has enough attempts return {ok, integer()}, being the integer the actual number of attempts in the interval. But if the user has not attempts, the first time return {warning, integer(), integer()}, being the first integer the number of attempts and the second once the time of the interval. While the user has not attempts and continue making call, the method return {error, integer(), integer()}, the meaning is the same as before. The third is used to pass options to the check fucntion.

Options

  • [{strict, boolean()}]: If strict is set to true the system count the fail attemps to. Is more restritive than the false option.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.79.0>}
3> throttle:check('context', 'id', [{strict, true}]),
{ok,1}

peek(atom(), atom(),) -> {ok, integer()} | {error | warning, integer(), integer()}.

Check the number of attempts of a specific user, second atom(), in a specific resource, first atom() but without updating the internal counter. If the user has enough attempts return {ok, integer()}, being the integer() the actual number of attempts in the interval. But if the user has not attempts, the first time return {warning, integer(), integer()}, being the first integer the number of attempts and the second once the time of the interval. While the user has not attempts and continue making call, the method return {error, integer(), integer()}, the meaning is the same as before. Peek show the same result of doing a check call but without updating the internal counter.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.79.0>}
3> io:format("Peek: ~p, Check: ~p~n", [throttle:peek('context', 'id'), throttle:check('context', 'id')]).
Peek: {ok,1}, Check: {ok,1}
ok

restore(atom(), atom()) -> {ok, integer()}.

Restore the number of attempts of a specific identifier, second atom(), in a specific context, first atom(). Return {ok, 0}. Restore of number of attempts independently of the state.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.74.0>}
3> io:format("Check: ~p, Restore: ~p, Check:~p~n", [throttle:check('context', 'id'), 
3> throttle:restore('context', 'id'), throttle:check('context', 'id')]). 
Check: {ok,1}, Restore: {ok,0}, Check:{ok,1}
ok

restart(atom()) -> ok.

Restart the counter of all the identifier in a context, atom().

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.74.0>}
3> io:format("Check1: ~p, Chec2k:~p~n", [throttle:check('context', 'id1'), throttle:check('context', 'id2')]).
Check1: {ok,1}, Chec2k:{ok,1}
ok
4> throttle:restart('context').
ok
5> io:format("Check1: ~p, Chec2k:~p~n", [throttle:check('context', 'id1'), throttle:check('context', 'id2')]).
Check1: {ok,1}, Chec2k:{ok,1}
ok

stop(atom() | pid()) -> ok.

Stop the context and all his counters of the giving context.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 5000}).
{ok,<0.74.0>}
3> io:format("Check1: ~p, Chec2k:~p~n", [throttle:check('context', 'id1'), throttle:check('context', 'id2')]).
Check1: {ok,1}, Chec2k:{ok,1}
ok
4> throttle:stop('context').
ok
5> io:format("Check1: ~p, Chec2k:~p~n", [throttle:check('context', 'id1'), throttle:check('context', 'id2')]).
** exception throw: invalid_context

spec stop() -> true.

Stop the throttle applications.

Examples:

1> application:start(choke).
ok
2> application:stop(choke).
ok
1> throttle:start_link().
{ok,<0.50.0>}
2> throttle:stop().

spec get(atom(), any()) -> #{id => atom(), count => integer(), blocked => boolean()}.

Get information about a Counter inside of a Context.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 50000}).
{ok,<0.120.0>}
3> throttle:check('context', 'id').
{ok,1}
4> throttle:get('context', 'id').
#{blocked => false,count => 1,id => id}

spec get(atom()) -> [#{id => atom(), count => integer(), blocked => boolean()}].

Get information about all the Counters inide of a context.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context', {4, 50000}).
{ok,<0.120.0>}
3> throttle:check('context', 'id1'). throttle:check('context', 'id2').
{ok,1}
4> throttle:get('context').
[#{blocked => false,count => 1,id => id1}, #{blocked => false,count => 1,id => id2}]

spec get() -> [{atom(), [#{id => atom(), count => integer(), blocked => boolean()}]}].

Get all the Throttling informaticon about the counters and contexts.

Examples:

1> application:start(choke).
ok
2> throttle:start_context('context1', {4, 50000}).
{ok,<0.120.0>}
3> throttle:start_context('context2', {4, 50000}).
{ok,<0.122.0>}
4> throttle:check('context1', 'id1'). throttle:check('context1', 'id2').
{ok,1}
5> throttle:check('context2', 'id1'). throttle:check('context2', 'id2').
{ok,1}
6> throttle:get().
[{context2,[#{blocked => false,count => 1,id => id1},
            #{blocked => false,count => 1,id => id2}]},
 {context1,[#{blocked => false,count => 1,id => id1},
            #{blocked => false,count => 1,id => id2}]}]

Exceptions

The functions check/2, peek/2, restore/2, restart/1 and stop/1 throw invalid_context when the context doesn't exist on the system.